Tests: Create Browser and Components for better code structure

pull/6453/head^2
Aleksander Machniak 5 years ago
parent 1edd7a4b3f
commit f72054e761

@ -0,0 +1,248 @@
<?php
namespace Tests\Browser;
use Facebook\WebDriver\WebDriverKeys;
use PHPUnit\Framework\Assert;
use Tests\Browser\Components;
/**
* Laravel Dusk Browser extensions
*/
class Browser extends \Laravel\Dusk\Browser
{
/**
* Assert specified rcmail.env value
*/
public function assertEnvEquals($key, $expected)
{
$this->assertEquals($expected, $this->getEnv($key));
return $this;
}
/**
* Assert specified checkbox state
*/
public function assertCheckboxState($selector, $state)
{
if ($state) {
$this->assertChecked($selector);
}
else {
$this->assertNotChecked($selector);
}
return $this;
}
/**
* Assert Task menu state
*/
public function assertTaskMenu($selected)
{
$this->with(new Components\Taskmenu(), function ($browser) use ($selected) {
$browser->assertMenuState($selected);
});
return $this;
}
/**
* Assert toolbar menu state
*/
public function assertToolbarMenu($active, $disabled)
{
$this->with(new Components\Toolbarmenu(), function ($browser) use ($active, $disabled) {
$browser->assertMenuState($active, $disabled);
});
return $this;
}
/**
* Close toolbar menu (on phones)
*/
public function closeToolbarMenu()
{
$this->with(new Components\Toolbarmenu(), function ($browser) {
$browser->closeMenu();
});
return $this;
}
/**
* Select taskmenu item
*/
public function clickTaskMenuItem($name)
{
$this->with(new Components\Taskmenu(), function ($browser) use ($name) {
$browser->clickMenuItem($name);
});
return $this;
}
/**
* Select toolbar menu item
*/
public function clickToolbarMenuItem($name, $dropdown_action = null)
{
$this->with(new Components\Toolbarmenu(), function ($browser) use ($name, $dropdown_action) {
$browser->clickMenuItem($name, $dropdown_action);
});
return $this;
}
/**
* Shortcut to click an element while holding CTRL key
*/
public function ctrlClick($selector)
{
$this->driver->getKeyboard()->pressKey(WebDriverKeys::LEFT_CONTROL);
$this->element($selector)->click();
$this->driver->getKeyboard()->releaseKey(WebDriverKeys::LEFT_CONTROL);
}
/**
* Log in the test user
*/
public function doLogin()
{
$this->type('_user', TESTS_USER);
$this->type('_pass', TESTS_PASS);
$this->click('button[type="submit"]');
// wait after successful login
$this->waitUntil('!rcmail.busy');
return $this;
}
/**
* Visit specified task/action with logon if needed
*/
public function go($task = 'mail', $action = null, $login = true)
{
$this->visit("/?_task=$task&_action=$action");
// check if we have a valid session
if ($login) {
$app = new Components\App();
if ($app->getEnv($this, 'task') == 'login') {
$this->doLogin();
}
}
return $this;
}
/**
* Check if in Phone mode
*/
public static function isPhone()
{
return getenv('TESTS_MODE') == 'phone';
}
/**
* Check if in Tablet mode
*/
public static function isTablet()
{
return getenv('TESTS_MODE') == 'tablet';
}
/**
* Check if in Desktop mode
*/
public static function isDesktop()
{
return !self::isPhone() && !self::isTablet();
}
/**
* Change state of the Elastic's pretty checkbox
*/
public function setCheckboxState($selector, $state)
{
// Because you can't operate on the original checkbox directly
$this->ensurejQueryIsAvailable();
if ($state) {
$run = "if (!element.prev().is(':checked')) element.click()";
}
else {
$run = "if (element.prev().is(':checked')) element.click()";
}
$this->script(
"var element = jQuery('$selector')[0] || jQuery('input[name=$selector]')[0];"
."element = jQuery(element).next('.custom-control-label'); $run;"
);
return $this;
}
/**
* Returns content of a downloaded file
*/
public function readDownloadedFile($filename)
{
$filename = TESTS_DIR . "downloads/$filename";
// Give the browser a chance to finish download
if (!file_exists($filename)) {
sleep(2);
}
Assert::assertFileExists($filename);
return file_get_contents($filename);
}
/**
* Removes downloaded file
*/
public function removeDownloadedFile($filename)
{
@unlink(TESTS_DIR . "downloads/$filename");
return $this;
}
/**
* Wait for UI (notice/confirmation/loading/error/warning) message
* and assert it's text
*/
public function waitForMessage($type, $text)
{
$selector = '#messagestack > div.' . $type;
$this->waitFor($selector)->assertSeeIn($selector, $text);
return $this;
}
/**
* Execute code within body context.
* Useful to execute code that selects elements outside of a component context
*/
public function withinBody($callback)
{
if ($this->resolver->prefix != 'body') {
$orig_prefix = $this->resolver->prefix;
$this->resolver->prefix = 'body';
}
call_user_func($callback, $this);
if (isset($orig_prefix)) {
$this->resolver->prefix = $orig_prefix;
}
return $this;
}
}

@ -0,0 +1,87 @@
<?php
namespace Tests\Browser\Components;
use Laravel\Dusk\Component as BaseComponent;
use PHPUnit\Framework\Assert;
use Tests\Browser\Browser;
class App extends BaseComponent
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '';
}
/**
* Assert that the browser page contains the component.
*
* @param Browser $browser
*
* @return void
*/
public function assert($browser)
{
$result = $browser->script("return typeof(window.rcmail)");
Assert::assertEquals('object', $result[0]);
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [
];
}
/**
* Assert value of rcmail.env entry
*/
public function assertEnv($browser, string $key, $expected)
{
Assert::assertEquals($expected, $this->getEnv($browser, $key));
}
/**
* Assert existence of defined gui_objects
*/
public function assertObjects($browser, array $names)
{
$objects = $this->getObjects($browser);
foreach ($names as $object_name) {
Assert::assertContains($object_name, $objects);
}
}
/**
* Return rcmail.env entry
*/
public function getEnv($browser, $key)
{
$result = $browser->script("return rcmail.env['$key']");
$result = $result[0];
return $result;
}
/**
* Return names of defined gui_objects
*/
public function getObjects($browser)
{
$objects = $browser->script("var i, r = []; for (i in rcmail.gui_objects) r.push(i); return r");
$objects = $objects[0];
return (array) $objects;
}
}

@ -0,0 +1,95 @@
<?php
namespace Tests\Browser\Components;
use Tests\Browser\Browser;
use Laravel\Dusk\Component as BaseComponent;
class Taskmenu extends BaseComponent
{
protected $options = ['compose', 'mail', 'contacts', 'settings', 'about', 'logout'];
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '#taskmenu';
}
/**
* Assert that the browser page contains the component.
*
* @param Browser $browser
*
* @return void
*/
public function assert($browser)
{
if ($browser->isPhone()) {
$browser->assertPresent($this->selector());
}
else {
$browser->assertVisible($this->selector());
}
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [
];
}
/**
* Assert Taskmenu state
*/
public function assertMenuState(Browser $browser, $selected)
{
// On phone the menu is invisible, open it
if ($browser->isPhone()) {
$browser->withinBody(function ($browser) {
$browser->click('.task-menu-button');
$browser->waitFor($this->selector());
});
}
foreach ($this->options as $option) {
$browser->assertVisible("a.{$option}:not(.disabled)" . ($selected == $option ? ".selected" : ":not(.selected)"));
}
// hide the menu back
if ($browser->isPhone()) {
$browser->withinBody(function ($browser) {
$browser->click('.popover a.button.cancel');
$browser->waitUntilMissing($this->selector());
});
}
}
/**
* Select Taskmenu item
*/
public function clickMenuItem(Browser $browser, $name)
{
if ($browser->isPhone()) {
$browser->withinBody(function ($browser) {
$browser->click('.task-menu-button');
});
}
$browser->click("a.{$name}");
if ($browser->isPhone()) {
$browser->withinBody(function ($browser) {
$browser->waitUntilMissing($this->selector());
});
}
}
}

@ -0,0 +1,126 @@
<?php
namespace Tests\Browser\Components;
use Tests\Browser\Browser;
use Laravel\Dusk\Component as BaseComponent;
class Toolbarmenu extends BaseComponent
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '#toolbar-menu';
}
/**
* Assert that the browser page contains the component.
*
* @param Browser $browser
*
* @return void
*/
public function assert($browser)
{
if ($browser->isPhone()) {
$browser->assertPresent($this->selector());
}
else {
$browser->assertVisible($this->selector());
}
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [
];
}
/**
* Assert toolbar menu state
*/
public function assertMenuState($browser, $active, $disabled)
{
// On phone the menu is invisible, open it
if ($browser->isPhone()) {
$browser->withinBody(function ($browser) {
$browser->click('.toolbar-menu-button');
$browser->waitFor($this->selector());
});
}
foreach ($active as $option) {
// Print action is disabled on phones
if ($option == 'print' && $browser->isPhone()) {
$browser->assertMissing("a.print");
}
else {
$browser->assertVisible("a.{$option}:not(.disabled)");
}
}
foreach ($disabled as $option) {
if ($option == 'print' && $browser->isPhone()) {
$browser->assertMissing("a.print");
}
else {
$browser->assertVisible("a.{$option}.disabled");
}
}
$this->closeMenu($browser);
}
/**
* Close toolbar menu (on phones)
*/
public function closeMenu($browser)
{
// hide the menu back
if ($browser->isPhone()) {
$browser->withinBody(function ($browser) {
$browser->script("window.UI.menu_hide('toolbar-menu')");
$browser->waitUntilMissing($this->selector());
// FIXME: For some reason sometimes .popover-overlay does not close,
// we have to remove it manually
$browser->script(
"Array.from(document.getElementsByClassName('popover-overlay')).forEach(function(elem) { elem.parentNode.removeChild(elem); })"
);
});
}
}
/**
* Select toolbar menu item
*/
public function clickMenuItem($browser, $name, $dropdown_action = null)
{
if ($browser->isPhone()) {
$browser->withinBody(function ($browser) {
$browser->click('.toolbar-menu-button');
});
}
$selector = "a.{$name}" . ($dropdown_action ? " + a.dropdown" : '');
$browser->click($selector);
if ($dropdown_action) {
$popup_id = $browser->attribute($selector, 'data-popup');
$browser->withinBody(function ($browser) use ($popup_id, $dropdown_action) {
$browser->click("#{$popup_id} li a.{$dropdown_action}");
});
}
$this->closeMenu($browser);
}
}

@ -2,7 +2,9 @@
namespace Tests\Browser\Contacts; namespace Tests\Browser\Contacts;
class Contacts extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class Contacts extends \Tests\Browser\TestCase
{ {
protected function setUp() protected function setUp()
{ {
@ -17,20 +19,17 @@ class Contacts extends \Tests\Browser\DuskTestCase
public function testContactsUI() public function testContactsUI()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('addressbook'); $browser->go('addressbook');
$browser->with(new App(), function ($browser) {
// check task // check task
$this->assertEnvEquals('task', 'addressbook'); $browser->assertEnv('task', 'addressbook');
$objects = $this->getObjects();
// these objects should be there always // these objects should be there always
$this->assertContains('qsearchbox', $objects); $browser->assertObjects(['qsearchbox', 'folderlist', 'contactslist', 'countdisplay']);
$this->assertContains('folderlist', $objects); });
$this->assertContains('contactslist', $objects);
$this->assertContains('countdisplay', $objects);
if (!$this->isDesktop()) { if (!$browser->isDesktop()) {
$browser->assertMissing('#directorylist'); $browser->assertMissing('#directorylist');
$browser->click('a.back-sidebar-button'); $browser->click('a.back-sidebar-button');
} }
@ -43,7 +42,7 @@ class Contacts extends \Tests\Browser\DuskTestCase
$browser->assertMissing('#directorylist .treetoggle.expanded'); $browser->assertMissing('#directorylist .treetoggle.expanded');
// Contacts list // Contacts list
if (!$this->isDesktop()) { if (!$browser->isDesktop()) {
$browser->assertMissing('#contacts-table'); $browser->assertMissing('#contacts-table');
$browser->click('#directorylist li:first-child'); $browser->click('#directorylist li:first-child');
$browser->waitFor('#contacts-table'); $browser->waitFor('#contacts-table');
@ -53,10 +52,10 @@ class Contacts extends \Tests\Browser\DuskTestCase
} }
// Contacts list menu // Contacts list menu
if ($this->isPhone()) { if ($browser->isPhone()) {
$this->assertToolbarMenu(['select'], []); $browser->assertToolbarMenu(['select'], []);
} }
else if ($this->isTablet()) { else if ($browser->isTablet()) {
$browser->click('.toolbar-list-button'); $browser->click('.toolbar-list-button');
$browser->assertVisible('#toolbar-list-menu a.select:not(.disabled)'); $browser->assertVisible('#toolbar-list-menu a.select:not(.disabled)');
$browser->click(); $browser->click();
@ -66,18 +65,18 @@ class Contacts extends \Tests\Browser\DuskTestCase
} }
// Toolbar menu // Toolbar menu
$this->assertToolbarMenu( $browser->assertToolbarMenu(
['create', 'search', 'import', 'export'], // active items ['create', 'search', 'import', 'export'], // active items
['print', 'delete', 'more'], // inactive items ['print', 'delete', 'more'], // inactive items
); );
// Contact frame // Contact frame
if (!$this->isPhone()) { if (!$browser->isPhone()) {
$browser->assertVisible('#contact-frame'); $browser->assertVisible('#contact-frame');
} }
// Task menu // Task menu
$this->assertTaskMenu('contacts'); $browser->assertTaskMenu('contacts');
}); });
} }
} }

@ -2,7 +2,7 @@
namespace Tests\Browser\Contacts; namespace Tests\Browser\Contacts;
class Export extends \Tests\Browser\DuskTestCase class Export extends \Tests\Browser\TestCase
{ {
/** /**
* Test exporting all contacts * Test exporting all contacts
@ -12,20 +12,21 @@ class Export extends \Tests\Browser\DuskTestCase
\bootstrap::init_db(); \bootstrap::init_db();
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('addressbook'); $browser->go('addressbook');
$this->clickToolbarMenuItem('export'); $browser->clickToolbarMenuItem('export');
});
// Parse the downloaded vCard file // Parse the downloaded vCard file
$vcard_content = $this->readDownloadedFile('contacts.vcf'); $vcard_content = $browser->readDownloadedFile('contacts.vcf');
$vcard = new \rcube_vcard(); $vcard = new \rcube_vcard();
$contacts = $vcard->import($vcard_content); $contacts = $vcard->import($vcard_content);
$this->assertCount(2, $contacts); $this->assertCount(2, $contacts);
$this->assertSame('John Doe', $contacts[0]->displayname); $this->assertSame('John Doe', $contacts[0]->displayname);
$this->assertSame('Jane Stalone', $contacts[1]->displayname); $this->assertSame('Jane Stalone', $contacts[1]->displayname);
$this->removeDownloadedFile('contacts.vcf');
$browser->removeDownloadedFile('contacts.vcf');
});
} }
/** /**
@ -35,16 +36,20 @@ class Export extends \Tests\Browser\DuskTestCase
*/ */
public function testExportSelected() public function testExportSelected()
{ {
$this->ctrlClick('#contacts-table tbody tr:first-child'); $this->browse(function ($browser) {
$this->clickToolbarMenuItem('export', 'export.select'); $browser->ctrlClick('#contacts-table tbody tr:first-child');
$vcard_content = $this->readDownloadedFile('contacts.vcf'); $browser->clickToolbarMenuItem('export', 'export.select');
$vcard_content = $browser->readDownloadedFile('contacts.vcf');
$vcard = new \rcube_vcard(); $vcard = new \rcube_vcard();
$contacts = $vcard->import($vcard_content); $contacts = $vcard->import($vcard_content);
// Parse the downloaded vCard file // Parse the downloaded vCard file
$this->assertCount(1, $contacts); $this->assertCount(1, $contacts);
$this->assertSame('John Doe', $contacts[0]->displayname); $this->assertSame('John Doe', $contacts[0]->displayname);
$this->removeDownloadedFile('contacts.vcf');
$browser->removeDownloadedFile('contacts.vcf');
});
} }
} }

@ -2,7 +2,9 @@
namespace Tests\Browser\Contacts; namespace Tests\Browser\Contacts;
class Import extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class Import extends \Tests\Browser\TestCase
{ {
/** /**
* Test basic elements of contacts import UI * Test basic elements of contacts import UI
@ -12,9 +14,9 @@ class Import extends \Tests\Browser\DuskTestCase
\bootstrap::init_db(); \bootstrap::init_db();
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('addressbook'); $browser->go('addressbook');
$this->clickToolbarMenuItem('import'); $browser->clickToolbarMenuItem('import');
$browser->assertSeeIn('.ui-dialog-title', 'Import contacts'); $browser->assertSeeIn('.ui-dialog-title', 'Import contacts');
$browser->assertVisible('.ui-dialog button.mainaction.import'); $browser->assertVisible('.ui-dialog button.mainaction.import');
@ -22,13 +24,12 @@ class Import extends \Tests\Browser\DuskTestCase
$browser->withinFrame('.ui-dialog iframe', function ($browser) { $browser->withinFrame('.ui-dialog iframe', function ($browser) {
// check task and action // check task and action
$this->assertEnvEquals('task', 'addressbook'); $browser->with(new App(), function ($browser) {
$this->assertEnvEquals('action', 'import'); $browser->assertEnv('task', 'addressbook');
$browser->assertEnv('action', 'import');
$objects = $this->getObjects();
// these objects should be there always // these objects should be there always
$this->assertContains('importform', $objects); $browser->assertObjects(['importform']);
});
$browser->assertSee('You can upload'); $browser->assertSee('You can upload');
$browser->assertVisible('#rcmImportForm'); $browser->assertVisible('#rcmImportForm');
@ -54,7 +55,7 @@ class Import extends \Tests\Browser\DuskTestCase
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
// Open the dialog again // Open the dialog again
$this->clickToolbarMenuItem('import'); $browser->clickToolbarMenuItem('import');
$browser->assertSeeIn('.ui-dialog-title', 'Import contacts'); $browser->assertSeeIn('.ui-dialog-title', 'Import contacts');
// Submit the form with no file attached // Submit the form with no file attached

@ -1,460 +0,0 @@
<?php
namespace Tests\Browser;
use PHPUnit\Framework\TestCase;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Chrome\SupportsChrome;
use Laravel\Dusk\Concerns\ProvidesBrowser;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process;
abstract class DuskTestCase extends TestCase
{
use ProvidesBrowser,
SupportsChrome;
protected $app;
protected static $phpProcess;
/**
* Prepare for Dusk test execution.
*
* @beforeClass
* @return void
*/
public static function prepare()
{
static::startWebServer();
static::startChromeDriver();
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
$options = (new ChromeOptions())->addArguments([
'--lang=en_US',
'--disable-gpu',
'--headless',
]);
// For file download handling
$prefs = [
'profile.default_content_settings.popups' => 0,
'download.default_directory' => TESTS_DIR . 'downloads',
];
$options->setExperimentalOption('prefs', $prefs);
if (getenv('TESTS_MODE') == 'phone') {
// Fake User-Agent string for mobile mode
$ua = 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Mobile Safari/537.36';
$options->setExperimentalOption('mobileEmulation', ['userAgent' => $ua]);
$options->addArguments(['--window-size=375,667']);
}
else if (getenv('TESTS_MODE') == 'tablet') {
// Fake User-Agent string for mobile mode
$ua = 'Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36';
$options->setExperimentalOption('mobileEmulation', ['userAgent' => $ua]);
$options->addArguments(['--window-size=1024,768']);
}
else {
$options->addArguments(['--window-size=1280,720']);
}
// Make sure downloads dir exists and is empty
if (!file_exists(TESTS_DIR . 'downloads')) {
mkdir(TESTS_DIR . 'downloads', 0777, true);
}
else {
foreach (glob(TESTS_DIR . 'downloads/*') as $file) {
@unlink($file);
}
}
return RemoteWebDriver::create(
'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY,
$options
)
);
}
/**
* Set up the test run
*
* @return void
*/
protected function setUp()
{
parent::setUp();
$this->app = \rcmail::get_instance();
Browser::$baseUrl = 'http://localhost:8000';
Browser::$storeScreenshotsAt = TESTS_DIR . 'screenshots';
Browser::$storeConsoleLogAt = TESTS_DIR . 'console';
// This folder must exist in case Browser will try to write logs to it
if (!is_dir(Browser::$storeConsoleLogAt)) {
mkdir(Browser::$storeConsoleLogAt, 0777, true);
}
// Purge screenshots from the last test run
$pattern = sprintf('failure-%s_%s-*',
str_replace("\\", '_', get_class($this)),
$this->getName(false)
);
try {
$files = Finder::create()->files()->in(Browser::$storeScreenshotsAt)->name($pattern);
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
// ignore missing screenshots directory
}
// Purge console logs from the last test run
$pattern = sprintf('%s_%s-*',
str_replace("\\", '_', get_class($this)),
$this->getName(false)
);
try {
$files = Finder::create()->files()->in(Browser::$storeConsoleLogAt)->name($pattern);
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
// ignore missing screenshots directory
}
}
/**
* Check if in Phone mode
*/
public static function isPhone()
{
return getenv('TESTS_MODE') == 'phone';
}
/**
* Check if in Tablet mode
*/
public static function isTablet()
{
return getenv('TESTS_MODE') == 'tablet';
}
/**
* Check if in Desktop mode
*/
public static function isDesktop()
{
return !self::isPhone() && !self::isTablet();
}
/**
* Assert specified rcmail.env value
*/
protected function assertEnvEquals($key, $expected)
{
$this->assertEquals($expected, $this->getEnv($key));
}
/**
* Assert specified checkbox state
*/
protected function assertCheckboxState($selector, $state)
{
$this->browse(function (Browser $browser) use ($selector, $state) {
if ($state) {
$browser->assertChecked($selector);
}
else {
$browser->assertNotChecked($selector);
}
});
}
/**
* Assert Task menu state
*/
protected function assertTaskMenu($selected)
{
$this->browse(function (Browser $browser) use ($selected) {
// On phone the menu is invisible, open it
if ($this->isPhone()) {
$browser->click('.task-menu-button');
$browser->waitFor('#taskmenu');
}
$browser->with('#taskmenu', function(Browser $browser) use ($selected) {
$options = ['compose', 'mail', 'contacts', 'settings', 'about', 'logout'];
foreach ($options as $option) {
$browser->assertVisible("a.{$option}:not(.disabled)" . ($selected == $option ? ".selected" : ":not(.selected)"));
}
});
// hide the menu back
if ($this->isPhone()) {
$browser->click('.popover a.button.cancel');
$browser->waitUntilMissing('#taskmenu');
}
});
}
/**
* Assert toolbar menu state
*/
protected function assertToolbarMenu($active, $disabled)
{
$this->browse(function (Browser $browser) use ($active, $disabled) {
// On phone the menu is invisible, open it
if ($this->isPhone()) {
$browser->click('.toolbar-menu-button');
$browser->waitFor('#toolbar-menu');
}
$browser->with('#toolbar-menu', function(Browser $browser) use ($active, $disabled) {
foreach ($active as $option) {
// Print action is disabled on phones
if ($option == 'print' && $this->isPhone()) {
$browser->assertMissing("a.print");
}
else {
$browser->assertVisible("a.{$option}:not(.disabled)");
}
}
foreach ($disabled as $option) {
if ($option == 'print' && $this->isPhone()) {
$browser->assertMissing("a.print");
}
else {
$browser->assertVisible("a.{$option}.disabled");
}
}
});
$this->closeToolbarMenu();
});
}
/**
* Close toolbar menu (on phones)
*/
protected function closeToolbarMenu()
{
// hide the menu back
if ($this->isPhone()) {
$this->browse(function (Browser $browser) {
$browser->script("window.UI.menu_hide('toolbar-menu')");
$browser->waitUntilMissing('#toolbar-menu');
// FIXME: For some reason sometimes .popover-overlay does not close,
// we have to remove it manually
$browser->script(
"Array.from(document.getElementsByClassName('popover-overlay')).forEach(function(elem) { elem.parentNode.removeChild(elem); })"
);
});
}
}
/**
* Select taskmenu item
*/
protected function clickTaskMenuItem($name)
{
$this->browse(function (Browser $browser) use ($name) {
if ($this->isPhone()) {
$browser->click('.task-menu-button');
}
$browser->click("#taskmenu a.{$name}");
if ($this->isPhone()) {
$browser->waitUntilMissing('#taskmenu');
}
});
}
/**
* Select toolbar menu item
*/
protected function clickToolbarMenuItem($name, $dropdown_action = null)
{
$this->browse(function (Browser $browser) use ($name, $dropdown_action) {
if ($this->isPhone()) {
$browser->click('.toolbar-menu-button');
}
$selector = "#toolbar-menu a.{$name}" . ($dropdown_action ? " + a.dropdown" : '');
$browser->click($selector);
if ($dropdown_action) {
$popup_id = $browser->attribute($selector, 'data-popup');
$browser->click("#{$popup_id} li a.{$dropdown_action}");
}
if ($this->isPhone()) {
$this->closeToolbarMenu();
}
});
}
protected function ctrlClick($selector)
{
$this->browse(function (Browser $browser) use ($selector) {
$browser->driver->getKeyboard()->pressKey(\Facebook\WebDriver\WebDriverKeys::LEFT_CONTROL);
$browser->element('#contacts-table tbody tr:first-child')->click();
$browser->driver->getKeyboard()->releaseKey(\Facebook\WebDriver\WebDriverKeys::LEFT_CONTROL);
});
}
/**
* Get content of rcmail.env entry
*/
protected function getEnv($key)
{
$this->browse(function (Browser $browser) use ($key, &$result) {
$result = $browser->script("return rcmail.env['$key']");
$result = $result[0];
});
return $result;
}
/**
* Return names of defined gui_objects
*/
protected function getObjects()
{
$this->browse(function (Browser $browser) use (&$objects) {
$objects = $browser->script("var i, r = []; for (i in rcmail.gui_objects) r.push(i); return r");
$objects = $objects[0];
});
return (array) $objects;
}
/**
* Log in the test user
*/
protected function doLogin()
{
$this->browse(function (Browser $browser) {
$browser->type('_user', TESTS_USER);
$browser->type('_pass', TESTS_PASS);
$browser->click('button[type="submit"]');
// wait after successful login
//$browser->waitForReload();
$browser->waitUntil('!rcmail.busy');
});
}
/**
* Visit specified task/action with logon if needed
*/
protected function go($task = 'mail', $action = null, $login = true)
{
$this->browse(function (Browser $browser) use ($task, $action, $login) {
$browser->visit("/?_task=$task&_action=$action");
// check if we have a valid session
if ($login && $this->getEnv('task') == 'login') {
$this->doLogin();
}
});
}
/**
* Change state of the Elastic's pretty checkbox
*/
protected function setCheckboxState($selector, $state)
{
// Because you can't operate on the original checkbox directly
$this->browse(function (Browser $browser) use ($selector, $state) {
$browser->ensurejQueryIsAvailable();
if ($state) {
$run = "if (!element.prev().is(':checked')) element.click()";
}
else {
$run = "if (element.prev().is(':checked')) element.click()";
}
$browser->script(
"var element = jQuery('$selector')[0] || jQuery('input[name=$selector]')[0];"
."element = jQuery(element).next('.custom-control-label'); $run;"
);
});
}
/**
* Wait for UI (notice/confirmation/loading/error/warning) message
* and assert it's text
*/
protected function waitForMessage($type, $text)
{
$selector = '#messagestack > div.' . $type;
$this->browse(function ($browser) use ($selector, $text) {
$browser->waitFor($selector)->assertSeeIn($selector, $text);
});
}
/**
* Returns content of a downloaded file
*/
protected function readDownloadedFile($filename)
{
$filename = TESTS_DIR . "downloads/$filename";
// Give the browser a chance to finish download
if (!file_exists($filename)) {
sleep(2);
}
$this->assertFileExists($filename);
return file_get_contents($filename);
}
/**
* Removes downloaded file
*/
protected function removeDownloadedFile($filename)
{
@unlink(TESTS_DIR . "downloads/$filename");
}
/**
* Starts PHP server.
*/
protected static function startWebServer()
{
$path = realpath(__DIR__ . '/../../public_html');
$cmd = ['php', '-S', 'localhost:8000'];
$env = [];
static::$phpProcess = new Process($cmd, null, $env);
static::$phpProcess->setWorkingDirectory($path);
static::$phpProcess->start();
static::afterClass(function () {
static::$phpProcess->stop();
});
}
}

@ -2,7 +2,9 @@
namespace Tests\Browser; namespace Tests\Browser;
class Login extends DuskTestCase use Tests\Browser\Components\App;
class Login extends TestCase
{ {
protected function setUp() protected function setUp()
{ {
@ -21,7 +23,9 @@ class Login extends DuskTestCase
$browser->assertTitleContains($this->app->config->get('product_name')); $browser->assertTitleContains($this->app->config->get('product_name'));
// task should be set to 'login' // task should be set to 'login'
$this->assertEnvEquals('task', 'login'); $browser->with(new App(), function ($browser) {
$browser->assertEnv('task', 'login');
});
// Logon form // Logon form
$browser->assertVisible('#logo'); $browser->assertVisible('#logo');
@ -38,10 +42,12 @@ class Login extends DuskTestCase
} }
// test valid login // test valid login
$this->go('mail'); $browser->go('mail');
// task should be set to 'mail' now // task should be set to 'mail' now
$this->assertEnvEquals('task', 'mail'); $browser->with(new App(), function ($browser) {
$browser->assertEnv('task', 'mail');
});
}); });
} }
} }

@ -2,18 +2,22 @@
namespace Tests\Browser; namespace Tests\Browser;
class Logout extends DuskTestCase use Tests\Browser\Components\App;
class Logout extends TestCase
{ {
public function testLogout() public function testLogout()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('settings'); $browser->go('settings');
// click the Logout button in taskmenu // click the Logout button in taskmenu
$this->clickTaskMenuItem('logout'); $browser->clickTaskMenuItem('logout');
// task should be set to 'login' // task should be set to 'login'
$this->assertEnvEquals('task', 'login'); $browser->with(new App(), function ($browser) {
$browser->assertEnv('task', 'login');
});
// form should exist // form should exist
$browser->assertVisible('input[name="_user"]'); $browser->assertVisible('input[name="_user"]');

@ -2,46 +2,50 @@
namespace Tests\Browser\Mail; namespace Tests\Browser\Mail;
class Compose extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class Compose extends \Tests\Browser\TestCase
{ {
public function testCompose() public function testCompose()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('mail'); $browser->go('mail');
$this->clickTaskMenuItem('compose'); $browser->clickTaskMenuItem('compose');
// check task and action // check task and action
$this->assertEnvEquals('task', 'mail'); $browser->with(new App(), function ($browser) {
$this->assertEnvEquals('action', 'compose'); $browser->assertEnv('task', 'mail');
$browser->assertEnv('action', 'compose');
$objects = $this->getObjects();
// these objects should be there always // these objects should be there always
$this->assertContains('qsearchbox', $objects); $browser->assertObjects([
$this->assertContains('addressbookslist', $objects); 'qsearchbox',
$this->assertContains('contactslist', $objects); 'addressbookslist',
$this->assertContains('messageform', $objects); 'contactslist',
$this->assertContains('attachmentlist', $objects); 'messageform',
$this->assertContains('filedrop', $objects); 'attachmentlist',
$this->assertContains('uploadform', $objects); 'filedrop',
'uploadform'
]);
});
// Toolbar menu // Toolbar menu
$this->assertToolbarMenu( $browser->assertToolbarMenu(
['save.draft', 'responses', 'spellcheck'], // active items ['save.draft', 'responses', 'spellcheck'], // active items
['signature'], // inactive items ['signature'], // inactive items
); );
if ($this->isPhone()) { if ($browser->isPhone()) {
$this->assertToolbarMenu(['options'], []); $browser->assertToolbarMenu(['options'], []);
} }
else { else {
$this->assertToolbarMenu(['attach'], []); $browser->assertToolbarMenu(['attach'], []);
$browser->assertMissing('#toolbar-menu a.options'); $browser->assertMissing('#toolbar-menu a.options');
} }
// Task menu // Task menu
$this->assertTaskMenu('compose'); $browser->assertTaskMenu('compose');
// Header inputs // Header inputs
$browser->assertVisible('#_from'); $browser->assertVisible('#_from');
@ -52,8 +56,8 @@ class Compose extends \Tests\Browser\DuskTestCase
$browser->assertVisible('#composebodycontainer.html-editor'); $browser->assertVisible('#composebodycontainer.html-editor');
$browser->assertVisible('#composebodycontainer > textarea'); $browser->assertVisible('#composebodycontainer > textarea');
if ($this->isPhone()) { if ($browser->isPhone()) {
$this->clickToolbarMenuItem('options'); $browser->clickToolbarMenuItem('options');
} }
// Compose options // Compose options

@ -2,7 +2,7 @@
namespace Tests\Browser\Mail; namespace Tests\Browser\Mail;
class Getunread extends \Tests\Browser\DuskTestCase class Getunread extends \Tests\Browser\TestCase
{ {
protected $msgcount = 0; protected $msgcount = 0;
@ -23,14 +23,14 @@ class Getunread extends \Tests\Browser\DuskTestCase
public function testGetunread() public function testGetunread()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('mail'); $browser->go('mail');
$browser->waitFor('#messagelist tbody tr'); $browser->waitFor('#messagelist tbody tr');
// Messages list state // Messages list state
$this->assertCount($this->msgcount, $browser->elements('#messagelist tbody tr.unread')); $this->assertCount($this->msgcount, $browser->elements('#messagelist tbody tr.unread'));
if (!$this->isDesktop()) { if (!$browser->isDesktop()) {
$browser->click('.back-sidebar-button'); $browser->click('.back-sidebar-button');
} }

@ -2,7 +2,7 @@
namespace Tests\Browser\Mail; namespace Tests\Browser\Mail;
class MailList extends \Tests\Browser\DuskTestCase class MailList extends \Tests\Browser\TestCase
{ {
protected function setUp() protected function setUp()
{ {
@ -20,7 +20,7 @@ class MailList extends \Tests\Browser\DuskTestCase
public function testList() public function testList()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('mail'); $browser->go('mail');
$this->assertCount(1, $browser->elements('#messagelist tbody tr')); $this->assertCount(1, $browser->elements('#messagelist tbody tr'));
@ -35,7 +35,7 @@ class MailList extends \Tests\Browser\DuskTestCase
// List toolbar menu // List toolbar menu
$browser->assertVisible('#layout-list .header a.toolbar-button.refresh:not(.disabled)'); $browser->assertVisible('#layout-list .header a.toolbar-button.refresh:not(.disabled)');
if ($this->isDesktop()) { if ($browser->isDesktop()) {
$browser->with('#toolbar-list-menu', function ($browser) { $browser->with('#toolbar-list-menu', function ($browser) {
$browser->assertVisible('a.select:not(.disabled)'); $browser->assertVisible('a.select:not(.disabled)');
$browser->assertVisible('a.options:not(.disabled)'); $browser->assertVisible('a.options:not(.disabled)');
@ -49,7 +49,7 @@ class MailList extends \Tests\Browser\DuskTestCase
} }
}); });
} }
else if ($this->isTablet()) { else if ($browser->isTablet()) {
$browser->click('.toolbar-list-button'); $browser->click('.toolbar-list-button');
$browser->with('#toolbar-list-menu', function ($browser) { $browser->with('#toolbar-list-menu', function ($browser) {
@ -84,7 +84,7 @@ class MailList extends \Tests\Browser\DuskTestCase
} }
}); });
$this->closeToolbarMenu(); $browser->closeToolbarMenu();
} }
}); });
} }
@ -95,11 +95,11 @@ class MailList extends \Tests\Browser\DuskTestCase
public function testListSelection() public function testListSelection()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
if ($this->isPhone()) { if ($browser->isPhone()) {
$browser->click('.toolbar-menu-button'); $browser->click('.toolbar-menu-button');
$browser->click('#toolbar-menu a.select'); $browser->click('#toolbar-menu a.select');
} }
else if ($this->isTablet()) { else if ($browser->isTablet()) {
$browser->click('.toolbar-list-button'); $browser->click('.toolbar-list-button');
$browser->click('#toolbar-list-menu a.select'); $browser->click('#toolbar-list-menu a.select');
} }

@ -2,27 +2,30 @@
namespace Tests\Browser\Mail; namespace Tests\Browser\Mail;
class Mail extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class Mail extends \Tests\Browser\TestCase
{ {
public function testMailUI() public function testMailUI()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('mail'); $browser->go('mail');
// check task // check task
$this->assertEnvEquals('task', 'mail'); $browser->with(new App(), function ($browser) {
$browser->assertEnv('task', 'mail');
$objects = $this->getObjects();
// these objects should be there always // these objects should be there always
$this->assertContains('qsearchbox', $objects); $browser->assertObjects([
$this->assertContains('mailboxlist', $objects); 'qsearchbox',
$this->assertContains('messagelist', $objects); 'mailboxlist',
$this->assertContains('quotadisplay', $objects); 'messagelist',
$this->assertContains('search_filter', $objects); 'quotadisplay',
$this->assertContains('countdisplay', $objects); 'search_filter',
'countdisplay',
if (!$this->isDesktop()) { ]);
});
if (!$browser->isDesktop()) {
$browser->click('.back-sidebar-button'); $browser->click('.back-sidebar-button');
} }
@ -31,23 +34,23 @@ class Mail extends \Tests\Browser\DuskTestCase
// Folders list // Folders list
$browser->assertVisible('#mailboxlist li.mailbox.inbox.selected'); $browser->assertVisible('#mailboxlist li.mailbox.inbox.selected');
if (!$this->isDesktop()) { if (!$browser->isDesktop()) {
$browser->click('.back-list-button'); $browser->click('.back-list-button');
} }
// Mail preview frame // Mail preview frame
if (!$this->isPhone()) { if (!$browser->isPhone()) {
$browser->assertVisible('#messagecontframe'); $browser->assertVisible('#messagecontframe');
} }
// Toolbar menu // Toolbar menu
$this->assertToolbarMenu( $browser->assertToolbarMenu(
['more'], // active items ['more'], // active items
['reply', 'reply-all', 'forward', 'delete', 'markmessage'], // inactive items ['reply', 'reply-all', 'forward', 'delete', 'markmessage'], // inactive items
); );
// Task menu // Task menu
$this->assertTaskMenu('mail'); $browser->assertTaskMenu('mail');
}); });
} }
} }

@ -2,22 +2,26 @@
namespace Tests\Browser\Settings; namespace Tests\Browser\Settings;
class About extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class About extends \Tests\Browser\TestCase
{ {
public function testAbout() public function testAbout()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('settings'); $browser->go('settings');
$this->clickTaskMenuItem('about'); $browser->clickTaskMenuItem('about');
$browser->assertSeeIn('.ui-dialog-title', 'About'); $browser->assertSeeIn('.ui-dialog-title', 'About');
$browser->assertVisible('.ui-dialog #aboutframe'); $browser->assertVisible('.ui-dialog #aboutframe');
$browser->withinFrame('#aboutframe', function ($browser) { $browser->withinFrame('#aboutframe', function ($browser) {
// check task and action // check task and action
$this->assertEnvEquals('task', 'settings'); $browser->with(new App(), function ($browser) {
$this->assertEnvEquals('action', 'about'); $browser->assertEnv('task', 'settings');
$browser->assertEnv('action', 'about');
});
$browser->assertSee($this->app->config->get('product_name')); $browser->assertSee($this->app->config->get('product_name'));
$browser->assertVisible('#pluginlist'); $browser->assertVisible('#pluginlist');

@ -2,24 +2,25 @@
namespace Tests\Browser\Settings; namespace Tests\Browser\Settings;
class Folders extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class Folders extends \Tests\Browser\TestCase
{ {
public function testFolders() public function testFolders()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('settings', 'folders'); $browser->go('settings', 'folders');
// task should be set to 'settings' and action to 'folders' // task should be set to 'settings' and action to 'folders'
$this->assertEnvEquals('task', 'settings'); $browser->with(new App(), function ($browser) {
$this->assertEnvEquals('action', 'folders'); $browser->assertEnv('task', 'settings');
$browser->assertEnv('action', 'folders');
$objects = $this->getObjects();
// these objects should be there always // these objects should be there always
$this->assertContains('quotadisplay', $objects); $browser->assertObjects(['quotadisplay', 'subscriptionlist']);
$this->assertContains('subscriptionlist', $objects); });
if ($this->isDesktop()) { if ($browser->isDesktop()) {
$browser->assertVisible('#settings-menu li.folders.selected'); $browser->assertVisible('#settings-menu li.folders.selected');
} }
@ -27,7 +28,7 @@ class Folders extends \Tests\Browser\DuskTestCase
$browser->assertVisible('#subscription-table li.mailbox.inbox'); $browser->assertVisible('#subscription-table li.mailbox.inbox');
// Toolbar menu // Toolbar menu
$this->assertToolbarMenu(['create'], ['delete', 'purge']); $browser->assertToolbarMenu(['create'], ['delete', 'purge']);
}); });
} }
} }

@ -2,23 +2,25 @@
namespace Tests\Browser\Settings; namespace Tests\Browser\Settings;
class Identities extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class Identities extends \Tests\Browser\TestCase
{ {
public function testIdentities() public function testIdentities()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('settings', 'identities'); $browser->go('settings', 'identities');
// check task and action // check task and action
$this->assertEnvEquals('task', 'settings'); $browser->with(new App(), function ($browser) {
$this->assertEnvEquals('action', 'identities'); $browser->assertEnv('task', 'settings');
$browser->assertEnv('action', 'identities');
$objects = $this->getObjects();
// these objects should be there always // these objects should be there always
$this->assertContains('identitieslist', $objects); $browser->assertObjects(['identitieslist']);
});
if ($this->isDesktop()) { if ($browser->isDesktop()) {
$browser->assertVisible('#settings-menu li.identities.selected'); $browser->assertVisible('#settings-menu li.identities.selected');
} }
@ -27,7 +29,7 @@ class Identities extends \Tests\Browser\DuskTestCase
$browser->assertSeeIn('#identities-table tr:first-child td.mail', TESTS_USER); $browser->assertSeeIn('#identities-table tr:first-child td.mail', TESTS_USER);
// Toolbar menu // Toolbar menu
$this->assertToolbarMenu(['create'], ['delete']); $browser->assertToolbarMenu(['create'], ['delete']);
}); });
} }
} }

@ -2,21 +2,23 @@
namespace Tests\Browser\Settings; namespace Tests\Browser\Settings;
class Preferences extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class Preferences extends \Tests\Browser\TestCase
{ {
public function testPreferences() public function testPreferences()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('settings'); $browser->go('settings');
$objects = $this->getObjects();
$this->assertContains('sectionslist', $objects); $browser->with(new App(), function ($browser) {
$browser->assertObjects(['sectionslist']);
});
$browser->assertVisible('#settings-menu li.preferences.selected'); $browser->assertVisible('#settings-menu li.preferences.selected');
// On phone/tablet #sections-table is initially hidden // On phone/tablet #sections-table is initially hidden
if ($this->isPhone() || $this->isTablet()) { if (!$browser->isDesktop()) {
$browser->assertMissing('#sections-table'); $browser->assertMissing('#sections-table');
$browser->click('#settings-menu li.preferences'); $browser->click('#settings-menu li.preferences');
$browser->waitFor('#sections-table'); $browser->waitFor('#sections-table');

@ -2,7 +2,9 @@
namespace Tests\Browser\Settings\Preferences; namespace Tests\Browser\Settings\Preferences;
class General extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class General extends \Tests\Browser\TestCase
{ {
protected function tearDown() protected function tearDown()
{ {
@ -16,9 +18,9 @@ class General extends \Tests\Browser\DuskTestCase
public function testGeneral() public function testGeneral()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('settings'); $browser->go('settings');
if ($this->isPhone() || $this->isTablet()) { if (!$browser->isDesktop()) {
$browser->click('#settings-menu li.preferences'); $browser->click('#settings-menu li.preferences');
$browser->waitFor('#sections-table'); $browser->waitFor('#sections-table');
} }
@ -27,20 +29,22 @@ class General extends \Tests\Browser\DuskTestCase
$browser->click('#sections-table tr.general'); $browser->click('#sections-table tr.general');
if ($this->isPhone()) { if ($browser->isPhone()) {
$browser->waitFor('#layout-content .footer a.button.submit:not(.disabled)'); $browser->waitFor('#layout-content .footer a.button.submit:not(.disabled)');
$browser->assertVisible('#layout-content .footer a.button.prev.disabled'); $browser->assertVisible('#layout-content .footer a.button.prev.disabled');
$browser->assertVisible('#layout-content .footer a.button.next:not(.disabled)'); $browser->assertVisible('#layout-content .footer a.button.next:not(.disabled)');
} }
$browser->withinFrame('#preferences-frame', function ($browser) { $browser->withinFrame('#preferences-frame', function ($browser) {
if (!$this->isPhone()) { if (!$browser->isPhone()) {
$browser->waitFor('.formbuttons button.submit'); $browser->waitFor('.formbuttons button.submit');
} }
// check task and action // check task and action
$this->assertEnvEquals('task', 'settings'); $browser->with(new App(), function ($browser) {
$this->assertEnvEquals('action', 'edit-prefs'); $browser->assertEnv('task', 'settings');
$browser->assertEnv('action', 'edit-prefs');
});
// Main Options fieldset // Main Options fieldset
$browser->with('form.propform fieldset.main', function ($browser) { $browser->with('form.propform fieldset.main', function ($browser) {
@ -64,10 +68,10 @@ class General extends \Tests\Browser\DuskTestCase
$browser->assertSelected('select[name=_date_format]', $this->app->config->get('date_format')); $browser->assertSelected('select[name=_date_format]', $this->app->config->get('date_format'));
$browser->assertSeeIn('label[for=rcmfd_prettydate]', 'Pretty dates'); $browser->assertSeeIn('label[for=rcmfd_prettydate]', 'Pretty dates');
$this->assertCheckboxState('_pretty_date', $this->app->config->get('prettydate')); $browser->assertCheckboxState('_pretty_date', $this->app->config->get('prettydate'));
$browser->assertSeeIn('label[for=rcmfd_displaynext]', 'Display next'); $browser->assertSeeIn('label[for=rcmfd_displaynext]', 'Display next');
$this->assertCheckboxState('_display_next', $this->app->config->get('display_next')); $browser->assertCheckboxState('_display_next', $this->app->config->get('display_next'));
$browser->assertSeeIn('label[for=rcmfd_refresh_interval]', 'Refresh'); $browser->assertSeeIn('label[for=rcmfd_refresh_interval]', 'Refresh');
$browser->assertVisible('select[name=_refresh_interval]'); $browser->assertVisible('select[name=_refresh_interval]');
@ -86,7 +90,7 @@ class General extends \Tests\Browser\DuskTestCase
$browser->assertSeeIn('legend', 'Browser Options'); $browser->assertSeeIn('legend', 'Browser Options');
$browser->assertSeeIn('label[for=rcmfd_standard_windows]', 'Handle popups'); $browser->assertSeeIn('label[for=rcmfd_standard_windows]', 'Handle popups');
$this->assertCheckboxState('_standard_windows', $this->app->config->get('standard_windows')); $browser->assertCheckboxState('_standard_windows', $this->app->config->get('standard_windows'));
}); });
}); });
}); });
@ -120,21 +124,21 @@ class General extends \Tests\Browser\DuskTestCase
$browser->select('_date_format', $this->settings['date_format']); $browser->select('_date_format', $this->settings['date_format']);
$browser->select('_refresh_interval', $this->settings['refresh_interval']); $browser->select('_refresh_interval', $this->settings['refresh_interval']);
$this->setCheckboxState('_pretty_date', $this->settings['pretty_date']); $browser->setCheckboxState('_pretty_date', $this->settings['pretty_date']);
$this->setCheckboxState('_display_next', $this->settings['display_next']); $browser->setCheckboxState('_display_next', $this->settings['display_next']);
$this->setCheckboxState('_standard_windows', $this->settings['standard_windows']); $browser->setCheckboxState('_standard_windows', $this->settings['standard_windows']);
// Submit form // Submit form
if (!$this->isPhone()) { if (!$browser->isPhone()) {
$browser->click('.formbuttons button.submit'); $browser->click('.formbuttons button.submit');
} }
}); });
if ($this->isPhone()) { if ($browser->isPhone()) {
$browser->click('#layout-content .footer a.submit'); $browser->click('#layout-content .footer a.submit');
} }
$this->waitForMessage('confirmation', 'Successfully saved'); $browser->waitForMessage('confirmation', 'Successfully saved');
// Verify if every option has been updated // Verify if every option has been updated
$browser->withinFrame('#preferences-frame', function ($browser) { $browser->withinFrame('#preferences-frame', function ($browser) {
@ -143,9 +147,9 @@ class General extends \Tests\Browser\DuskTestCase
$browser->assertSelected('_date_format', $this->settings['date_format']); $browser->assertSelected('_date_format', $this->settings['date_format']);
$browser->assertSelected('_refresh_interval', $this->settings['refresh_interval']); $browser->assertSelected('_refresh_interval', $this->settings['refresh_interval']);
$this->assertCheckboxState('_pretty_date', $this->settings['pretty_date']); $browser->assertCheckboxState('_pretty_date', $this->settings['pretty_date']);
$this->assertCheckboxState('_display_next', $this->settings['display_next']); $browser->assertCheckboxState('_display_next', $this->settings['display_next']);
$this->assertCheckboxState('_standard_windows', $this->settings['standard_windows']); $browser->assertCheckboxState('_standard_windows', $this->settings['standard_windows']);
// Assert the options have been saved in database properly // Assert the options have been saved in database properly
$prefs = \bootstrap::get_prefs(); $prefs = \bootstrap::get_prefs();

@ -2,23 +2,25 @@
namespace Tests\Browser\Settings; namespace Tests\Browser\Settings;
class Responses extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class Responses extends \Tests\Browser\TestCase
{ {
public function testIdentities() public function testIdentities()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('settings', 'responses'); $browser->go('settings', 'responses');
$browser->with(new App(), function ($browser) {
// check task and action // check task and action
$this->assertEnvEquals('task', 'settings'); $browser->assertEnv('task', 'settings');
$this->assertEnvEquals('action', 'responses'); $browser->assertEnv('action', 'responses');
$objects = $this->getObjects();
// these objects should be there always // these objects should be there always
$this->assertContains('responseslist', $objects); $browser->assertObjects(['responseslist']);
});
if ($this->isDesktop()) { if ($browser->isDesktop()) {
$browser->assertVisible('#settings-menu li.responses.selected'); $browser->assertVisible('#settings-menu li.responses.selected');
} }
@ -27,7 +29,7 @@ class Responses extends \Tests\Browser\DuskTestCase
$browser->assertMissing('#responses-table tr'); $browser->assertMissing('#responses-table tr');
// Toolbar menu // Toolbar menu
$this->assertToolbarMenu(['create'], ['delete']); $browser->assertToolbarMenu(['create'], ['delete']);
}); });
} }
} }

@ -2,15 +2,19 @@
namespace Tests\Browser\Settings; namespace Tests\Browser\Settings;
class Settings extends \Tests\Browser\DuskTestCase use Tests\Browser\Components\App;
class Settings extends \Tests\Browser\TestCase
{ {
public function testSettings() public function testSettings()
{ {
$this->browse(function ($browser) { $this->browse(function ($browser) {
$this->go('settings'); $browser->go('settings');
// task should be set to 'settings' // task should be set to 'settings'
$this->assertEnvEquals('task', 'settings'); $browser->with(new App(), function ($browser) {
$browser->assertEnv('task', 'settings');
});
$browser->assertSeeIn('#layout-sidebar .header', 'Settings'); $browser->assertSeeIn('#layout-sidebar .header', 'Settings');
@ -23,7 +27,7 @@ class Settings extends \Tests\Browser\DuskTestCase
}); });
// Task menu // Task menu
$this->assertTaskMenu('settings'); $browser->assertTaskMenu('settings');
}); });
} }
} }

@ -0,0 +1,169 @@
<?php
namespace Tests\Browser;
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Laravel\Dusk\Chrome\SupportsChrome;
use Laravel\Dusk\Concerns\ProvidesBrowser;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process;
abstract class TestCase extends PHPUnitTestCase
{
use ProvidesBrowser,
SupportsChrome;
protected $app;
protected static $phpProcess;
/**
* Replace Dusk's Browser with our (extended) Browser
*/
protected function newBrowser($driver)
{
return new Browser($driver);
}
/**
* Prepare for Dusk test execution.
*
* @beforeClass
* @return void
*/
public static function prepare()
{
static::startWebServer();
static::startChromeDriver();
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
$options = (new ChromeOptions())->addArguments([
'--lang=en_US',
'--disable-gpu',
'--headless',
]);
// For file download handling
$prefs = [
'profile.default_content_settings.popups' => 0,
'download.default_directory' => TESTS_DIR . 'downloads',
];
$options->setExperimentalOption('prefs', $prefs);
if (getenv('TESTS_MODE') == 'phone') {
// Fake User-Agent string for mobile mode
$ua = 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Mobile Safari/537.36';
$options->setExperimentalOption('mobileEmulation', ['userAgent' => $ua]);
$options->addArguments(['--window-size=375,667']);
}
else if (getenv('TESTS_MODE') == 'tablet') {
// Fake User-Agent string for mobile mode
$ua = 'Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36';
$options->setExperimentalOption('mobileEmulation', ['userAgent' => $ua]);
$options->addArguments(['--window-size=1024,768']);
}
else {
$options->addArguments(['--window-size=1280,720']);
}
// Make sure downloads dir exists and is empty
if (!file_exists(TESTS_DIR . 'downloads')) {
mkdir(TESTS_DIR . 'downloads', 0777, true);
}
else {
foreach (glob(TESTS_DIR . 'downloads/*') as $file) {
@unlink($file);
}
}
return RemoteWebDriver::create(
'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY,
$options
)
);
}
/**
* Set up the test run
*
* @return void
*/
protected function setUp()
{
parent::setUp();
$this->app = \rcmail::get_instance();
Browser::$baseUrl = 'http://localhost:8000';
Browser::$storeScreenshotsAt = TESTS_DIR . 'screenshots';
Browser::$storeConsoleLogAt = TESTS_DIR . 'console';
// This folder must exist in case Browser will try to write logs to it
if (!is_dir(Browser::$storeConsoleLogAt)) {
mkdir(Browser::$storeConsoleLogAt, 0777, true);
}
// Purge screenshots from the last test run
$pattern = sprintf('failure-%s_%s-*',
str_replace("\\", '_', get_class($this)),
$this->getName(false)
);
try {
$files = Finder::create()->files()->in(Browser::$storeScreenshotsAt)->name($pattern);
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
// ignore missing screenshots directory
}
// Purge console logs from the last test run
$pattern = sprintf('%s_%s-*',
str_replace("\\", '_', get_class($this)),
$this->getName(false)
);
try {
$files = Finder::create()->files()->in(Browser::$storeConsoleLogAt)->name($pattern);
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
// ignore missing screenshots directory
}
}
/**
* Starts PHP server.
*/
protected static function startWebServer()
{
$path = realpath(__DIR__ . '/../../public_html');
$cmd = ['php', '-S', 'localhost:8000'];
$env = [];
static::$phpProcess = new Process($cmd, null, $env);
static::$phpProcess->setWorkingDirectory($path);
static::$phpProcess->start();
static::afterClass(function () {
static::$phpProcess->stop();
});
}
}

@ -31,7 +31,11 @@ define('TESTS_DIR', realpath(__DIR__) . '/');
define('TESTS_USER', $rcmail->config->get('tests_username')); define('TESTS_USER', $rcmail->config->get('tests_username'));
define('TESTS_PASS', $rcmail->config->get('tests_password')); define('TESTS_PASS', $rcmail->config->get('tests_password'));
require_once(__DIR__ . '/DuskTestCase.php'); require_once(__DIR__ . '/Browser.php');
require_once(__DIR__ . '/TestCase.php');
require_once(__DIR__ . '/Components/App.php');
require_once(__DIR__ . '/Components/Taskmenu.php');
require_once(__DIR__ . '/Components/Toolbarmenu.php');
/** /**

@ -3,8 +3,8 @@
colors="true"> colors="true">
<testsuites> <testsuites>
<testsuite name="Logon"> <testsuite name="Logon">
<file>Login.php</file> <file>Logon/Login.php</file>
<file>Logout.php</file> <file>Logon/Logout.php</file>
</testsuite> </testsuite>
<testsuite name="Contacts"> <testsuite name="Contacts">
<file>Contacts/Contacts.php</file> <file>Contacts/Contacts.php</file>

Loading…
Cancel
Save