From f72054e761422be9867a88fab33d36d58073ba81 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sat, 4 Jan 2020 14:01:16 +0100 Subject: [PATCH] Tests: Create Browser and Components for better code structure --- tests/Browser/Browser.php | 248 ++++++++++ tests/Browser/Components/App.php | 87 ++++ tests/Browser/Components/Taskmenu.php | 95 ++++ tests/Browser/Components/Toolbarmenu.php | 126 +++++ tests/Browser/Contacts/Contacts.php | 37 +- tests/Browser/Contacts/Export.php | 47 +- tests/Browser/Contacts/Import.php | 23 +- tests/Browser/DuskTestCase.php | 460 ------------------ tests/Browser/{ => Logon}/Login.php | 14 +- tests/Browser/{ => Logon}/Logout.php | 12 +- tests/Browser/Mail/Compose.php | 48 +- tests/Browser/Mail/Getunread.php | 6 +- tests/Browser/Mail/List.php | 14 +- tests/Browser/Mail/Mail.php | 41 +- tests/Browser/Settings/About.php | 14 +- tests/Browser/Settings/Folders.php | 23 +- tests/Browser/Settings/Identities.php | 22 +- tests/Browser/Settings/Preferences.php | 14 +- .../Browser/Settings/Preferences/General.php | 42 +- tests/Browser/Settings/Responses.php | 24 +- tests/Browser/Settings/Settings.php | 12 +- tests/Browser/TestCase.php | 169 +++++++ tests/Browser/bootstrap.php | 6 +- tests/Browser/phpunit.xml | 4 +- 24 files changed, 949 insertions(+), 639 deletions(-) create mode 100644 tests/Browser/Browser.php create mode 100644 tests/Browser/Components/App.php create mode 100644 tests/Browser/Components/Taskmenu.php create mode 100644 tests/Browser/Components/Toolbarmenu.php delete mode 100644 tests/Browser/DuskTestCase.php rename tests/Browser/{ => Logon}/Login.php (77%) rename tests/Browser/{ => Logon}/Logout.php (60%) create mode 100644 tests/Browser/TestCase.php diff --git a/tests/Browser/Browser.php b/tests/Browser/Browser.php new file mode 100644 index 000000000..91ff17b15 --- /dev/null +++ b/tests/Browser/Browser.php @@ -0,0 +1,248 @@ +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; + } +} diff --git a/tests/Browser/Components/App.php b/tests/Browser/Components/App.php new file mode 100644 index 000000000..fbc2744de --- /dev/null +++ b/tests/Browser/Components/App.php @@ -0,0 +1,87 @@ +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; + } +} diff --git a/tests/Browser/Components/Taskmenu.php b/tests/Browser/Components/Taskmenu.php new file mode 100644 index 000000000..499f8ca59 --- /dev/null +++ b/tests/Browser/Components/Taskmenu.php @@ -0,0 +1,95 @@ +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()); + }); + } + } +} diff --git a/tests/Browser/Components/Toolbarmenu.php b/tests/Browser/Components/Toolbarmenu.php new file mode 100644 index 000000000..39b0e3c80 --- /dev/null +++ b/tests/Browser/Components/Toolbarmenu.php @@ -0,0 +1,126 @@ +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); + } +} diff --git a/tests/Browser/Contacts/Contacts.php b/tests/Browser/Contacts/Contacts.php index b706074da..74890d996 100644 --- a/tests/Browser/Contacts/Contacts.php +++ b/tests/Browser/Contacts/Contacts.php @@ -2,7 +2,9 @@ namespace Tests\Browser\Contacts; -class Contacts extends \Tests\Browser\DuskTestCase +use Tests\Browser\Components\App; + +class Contacts extends \Tests\Browser\TestCase { protected function setUp() { @@ -17,20 +19,17 @@ class Contacts extends \Tests\Browser\DuskTestCase public function testContactsUI() { $this->browse(function ($browser) { - $this->go('addressbook'); - - // check task - $this->assertEnvEquals('task', 'addressbook'); + $browser->go('addressbook'); - $objects = $this->getObjects(); + $browser->with(new App(), function ($browser) { + // check task + $browser->assertEnv('task', 'addressbook'); - // these objects should be there always - $this->assertContains('qsearchbox', $objects); - $this->assertContains('folderlist', $objects); - $this->assertContains('contactslist', $objects); - $this->assertContains('countdisplay', $objects); + // these objects should be there always + $browser->assertObjects(['qsearchbox', 'folderlist', 'contactslist', 'countdisplay']); + }); - if (!$this->isDesktop()) { + if (!$browser->isDesktop()) { $browser->assertMissing('#directorylist'); $browser->click('a.back-sidebar-button'); } @@ -43,7 +42,7 @@ class Contacts extends \Tests\Browser\DuskTestCase $browser->assertMissing('#directorylist .treetoggle.expanded'); // Contacts list - if (!$this->isDesktop()) { + if (!$browser->isDesktop()) { $browser->assertMissing('#contacts-table'); $browser->click('#directorylist li:first-child'); $browser->waitFor('#contacts-table'); @@ -53,10 +52,10 @@ class Contacts extends \Tests\Browser\DuskTestCase } // Contacts list menu - if ($this->isPhone()) { - $this->assertToolbarMenu(['select'], []); + if ($browser->isPhone()) { + $browser->assertToolbarMenu(['select'], []); } - else if ($this->isTablet()) { + else if ($browser->isTablet()) { $browser->click('.toolbar-list-button'); $browser->assertVisible('#toolbar-list-menu a.select:not(.disabled)'); $browser->click(); @@ -66,18 +65,18 @@ class Contacts extends \Tests\Browser\DuskTestCase } // Toolbar menu - $this->assertToolbarMenu( + $browser->assertToolbarMenu( ['create', 'search', 'import', 'export'], // active items ['print', 'delete', 'more'], // inactive items ); // Contact frame - if (!$this->isPhone()) { + if (!$browser->isPhone()) { $browser->assertVisible('#contact-frame'); } // Task menu - $this->assertTaskMenu('contacts'); + $browser->assertTaskMenu('contacts'); }); } } diff --git a/tests/Browser/Contacts/Export.php b/tests/Browser/Contacts/Export.php index 5d757df2d..cfe3977de 100644 --- a/tests/Browser/Contacts/Export.php +++ b/tests/Browser/Contacts/Export.php @@ -2,7 +2,7 @@ namespace Tests\Browser\Contacts; -class Export extends \Tests\Browser\DuskTestCase +class Export extends \Tests\Browser\TestCase { /** * Test exporting all contacts @@ -12,20 +12,21 @@ class Export extends \Tests\Browser\DuskTestCase \bootstrap::init_db(); $this->browse(function ($browser) { - $this->go('addressbook'); + $browser->go('addressbook'); - $this->clickToolbarMenuItem('export'); - }); + $browser->clickToolbarMenuItem('export'); + + // Parse the downloaded vCard file + $vcard_content = $browser->readDownloadedFile('contacts.vcf'); + $vcard = new \rcube_vcard(); + $contacts = $vcard->import($vcard_content); - // Parse the downloaded vCard file - $vcard_content = $this->readDownloadedFile('contacts.vcf'); - $vcard = new \rcube_vcard(); - $contacts = $vcard->import($vcard_content); + $this->assertCount(2, $contacts); + $this->assertSame('John Doe', $contacts[0]->displayname); + $this->assertSame('Jane Stalone', $contacts[1]->displayname); - $this->assertCount(2, $contacts); - $this->assertSame('John Doe', $contacts[0]->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() { - $this->ctrlClick('#contacts-table tbody tr:first-child'); - $this->clickToolbarMenuItem('export', 'export.select'); + $this->browse(function ($browser) { + $browser->ctrlClick('#contacts-table tbody tr:first-child'); - $vcard_content = $this->readDownloadedFile('contacts.vcf'); - $vcard = new \rcube_vcard(); - $contacts = $vcard->import($vcard_content); + $browser->clickToolbarMenuItem('export', 'export.select'); - // Parse the downloaded vCard file - $this->assertCount(1, $contacts); - $this->assertSame('John Doe', $contacts[0]->displayname); - $this->removeDownloadedFile('contacts.vcf'); + $vcard_content = $browser->readDownloadedFile('contacts.vcf'); + $vcard = new \rcube_vcard(); + $contacts = $vcard->import($vcard_content); + + // Parse the downloaded vCard file + $this->assertCount(1, $contacts); + $this->assertSame('John Doe', $contacts[0]->displayname); + + $browser->removeDownloadedFile('contacts.vcf'); + }); } } diff --git a/tests/Browser/Contacts/Import.php b/tests/Browser/Contacts/Import.php index fd04df0cd..2070114a1 100644 --- a/tests/Browser/Contacts/Import.php +++ b/tests/Browser/Contacts/Import.php @@ -2,7 +2,9 @@ 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 @@ -12,9 +14,9 @@ class Import extends \Tests\Browser\DuskTestCase \bootstrap::init_db(); $this->browse(function ($browser) { - $this->go('addressbook'); + $browser->go('addressbook'); - $this->clickToolbarMenuItem('import'); + $browser->clickToolbarMenuItem('import'); $browser->assertSeeIn('.ui-dialog-title', 'Import contacts'); $browser->assertVisible('.ui-dialog button.mainaction.import'); @@ -22,13 +24,12 @@ class Import extends \Tests\Browser\DuskTestCase $browser->withinFrame('.ui-dialog iframe', function ($browser) { // check task and action - $this->assertEnvEquals('task', 'addressbook'); - $this->assertEnvEquals('action', 'import'); - - $objects = $this->getObjects(); - - // these objects should be there always - $this->assertContains('importform', $objects); + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'addressbook'); + $browser->assertEnv('action', 'import'); + // these objects should be there always + $browser->assertObjects(['importform']); + }); $browser->assertSee('You can upload'); $browser->assertVisible('#rcmImportForm'); @@ -54,7 +55,7 @@ class Import extends \Tests\Browser\DuskTestCase { $this->browse(function ($browser) { // Open the dialog again - $this->clickToolbarMenuItem('import'); + $browser->clickToolbarMenuItem('import'); $browser->assertSeeIn('.ui-dialog-title', 'Import contacts'); // Submit the form with no file attached diff --git a/tests/Browser/DuskTestCase.php b/tests/Browser/DuskTestCase.php deleted file mode 100644 index 15fe07b95..000000000 --- a/tests/Browser/DuskTestCase.php +++ /dev/null @@ -1,460 +0,0 @@ -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(); - }); - } -} diff --git a/tests/Browser/Login.php b/tests/Browser/Logon/Login.php similarity index 77% rename from tests/Browser/Login.php rename to tests/Browser/Logon/Login.php index ea2321f9d..306939569 100644 --- a/tests/Browser/Login.php +++ b/tests/Browser/Logon/Login.php @@ -2,7 +2,9 @@ namespace Tests\Browser; -class Login extends DuskTestCase +use Tests\Browser\Components\App; + +class Login extends TestCase { protected function setUp() { @@ -21,7 +23,9 @@ class Login extends DuskTestCase $browser->assertTitleContains($this->app->config->get('product_name')); // task should be set to 'login' - $this->assertEnvEquals('task', 'login'); + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'login'); + }); // Logon form $browser->assertVisible('#logo'); @@ -38,10 +42,12 @@ class Login extends DuskTestCase } // test valid login - $this->go('mail'); + $browser->go('mail'); // task should be set to 'mail' now - $this->assertEnvEquals('task', 'mail'); + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'mail'); + }); }); } } diff --git a/tests/Browser/Logout.php b/tests/Browser/Logon/Logout.php similarity index 60% rename from tests/Browser/Logout.php rename to tests/Browser/Logon/Logout.php index c601ca887..96105d61c 100644 --- a/tests/Browser/Logout.php +++ b/tests/Browser/Logon/Logout.php @@ -2,18 +2,22 @@ namespace Tests\Browser; -class Logout extends DuskTestCase +use Tests\Browser\Components\App; + +class Logout extends TestCase { public function testLogout() { $this->browse(function ($browser) { - $this->go('settings'); + $browser->go('settings'); // click the Logout button in taskmenu - $this->clickTaskMenuItem('logout'); + $browser->clickTaskMenuItem('logout'); // task should be set to 'login' - $this->assertEnvEquals('task', 'login'); + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'login'); + }); // form should exist $browser->assertVisible('input[name="_user"]'); diff --git a/tests/Browser/Mail/Compose.php b/tests/Browser/Mail/Compose.php index 8b536aa9e..2d8054cce 100644 --- a/tests/Browser/Mail/Compose.php +++ b/tests/Browser/Mail/Compose.php @@ -2,46 +2,50 @@ namespace Tests\Browser\Mail; -class Compose extends \Tests\Browser\DuskTestCase +use Tests\Browser\Components\App; + +class Compose extends \Tests\Browser\TestCase { public function testCompose() { $this->browse(function ($browser) { - $this->go('mail'); + $browser->go('mail'); - $this->clickTaskMenuItem('compose'); + $browser->clickTaskMenuItem('compose'); // check task and action - $this->assertEnvEquals('task', 'mail'); - $this->assertEnvEquals('action', 'compose'); - - $objects = $this->getObjects(); + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'mail'); + $browser->assertEnv('action', 'compose'); - // these objects should be there always - $this->assertContains('qsearchbox', $objects); - $this->assertContains('addressbookslist', $objects); - $this->assertContains('contactslist', $objects); - $this->assertContains('messageform', $objects); - $this->assertContains('attachmentlist', $objects); - $this->assertContains('filedrop', $objects); - $this->assertContains('uploadform', $objects); + // these objects should be there always + $browser->assertObjects([ + 'qsearchbox', + 'addressbookslist', + 'contactslist', + 'messageform', + 'attachmentlist', + 'filedrop', + 'uploadform' + ]); + }); // Toolbar menu - $this->assertToolbarMenu( + $browser->assertToolbarMenu( ['save.draft', 'responses', 'spellcheck'], // active items ['signature'], // inactive items ); - if ($this->isPhone()) { - $this->assertToolbarMenu(['options'], []); + if ($browser->isPhone()) { + $browser->assertToolbarMenu(['options'], []); } else { - $this->assertToolbarMenu(['attach'], []); + $browser->assertToolbarMenu(['attach'], []); $browser->assertMissing('#toolbar-menu a.options'); } // Task menu - $this->assertTaskMenu('compose'); + $browser->assertTaskMenu('compose'); // Header inputs $browser->assertVisible('#_from'); @@ -52,8 +56,8 @@ class Compose extends \Tests\Browser\DuskTestCase $browser->assertVisible('#composebodycontainer.html-editor'); $browser->assertVisible('#composebodycontainer > textarea'); - if ($this->isPhone()) { - $this->clickToolbarMenuItem('options'); + if ($browser->isPhone()) { + $browser->clickToolbarMenuItem('options'); } // Compose options diff --git a/tests/Browser/Mail/Getunread.php b/tests/Browser/Mail/Getunread.php index 2fa714bc8..28f6cc4ed 100644 --- a/tests/Browser/Mail/Getunread.php +++ b/tests/Browser/Mail/Getunread.php @@ -2,7 +2,7 @@ namespace Tests\Browser\Mail; -class Getunread extends \Tests\Browser\DuskTestCase +class Getunread extends \Tests\Browser\TestCase { protected $msgcount = 0; @@ -23,14 +23,14 @@ class Getunread extends \Tests\Browser\DuskTestCase public function testGetunread() { $this->browse(function ($browser) { - $this->go('mail'); + $browser->go('mail'); $browser->waitFor('#messagelist tbody tr'); // Messages list state $this->assertCount($this->msgcount, $browser->elements('#messagelist tbody tr.unread')); - if (!$this->isDesktop()) { + if (!$browser->isDesktop()) { $browser->click('.back-sidebar-button'); } diff --git a/tests/Browser/Mail/List.php b/tests/Browser/Mail/List.php index 183eb342f..f56985d97 100644 --- a/tests/Browser/Mail/List.php +++ b/tests/Browser/Mail/List.php @@ -2,7 +2,7 @@ namespace Tests\Browser\Mail; -class MailList extends \Tests\Browser\DuskTestCase +class MailList extends \Tests\Browser\TestCase { protected function setUp() { @@ -20,7 +20,7 @@ class MailList extends \Tests\Browser\DuskTestCase public function testList() { $this->browse(function ($browser) { - $this->go('mail'); + $browser->go('mail'); $this->assertCount(1, $browser->elements('#messagelist tbody tr')); @@ -35,7 +35,7 @@ class MailList extends \Tests\Browser\DuskTestCase // List toolbar menu $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->assertVisible('a.select: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->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() { $this->browse(function ($browser) { - if ($this->isPhone()) { + if ($browser->isPhone()) { $browser->click('.toolbar-menu-button'); $browser->click('#toolbar-menu a.select'); } - else if ($this->isTablet()) { + else if ($browser->isTablet()) { $browser->click('.toolbar-list-button'); $browser->click('#toolbar-list-menu a.select'); } diff --git a/tests/Browser/Mail/Mail.php b/tests/Browser/Mail/Mail.php index 64cff0f96..8c7806ca6 100644 --- a/tests/Browser/Mail/Mail.php +++ b/tests/Browser/Mail/Mail.php @@ -2,27 +2,30 @@ namespace Tests\Browser\Mail; -class Mail extends \Tests\Browser\DuskTestCase +use Tests\Browser\Components\App; + +class Mail extends \Tests\Browser\TestCase { public function testMailUI() { $this->browse(function ($browser) { - $this->go('mail'); + $browser->go('mail'); // check task - $this->assertEnvEquals('task', 'mail'); - - $objects = $this->getObjects(); - - // these objects should be there always - $this->assertContains('qsearchbox', $objects); - $this->assertContains('mailboxlist', $objects); - $this->assertContains('messagelist', $objects); - $this->assertContains('quotadisplay', $objects); - $this->assertContains('search_filter', $objects); - $this->assertContains('countdisplay', $objects); - - if (!$this->isDesktop()) { + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'mail'); + // these objects should be there always + $browser->assertObjects([ + 'qsearchbox', + 'mailboxlist', + 'messagelist', + 'quotadisplay', + 'search_filter', + 'countdisplay', + ]); + }); + + if (!$browser->isDesktop()) { $browser->click('.back-sidebar-button'); } @@ -31,23 +34,23 @@ class Mail extends \Tests\Browser\DuskTestCase // Folders list $browser->assertVisible('#mailboxlist li.mailbox.inbox.selected'); - if (!$this->isDesktop()) { + if (!$browser->isDesktop()) { $browser->click('.back-list-button'); } // Mail preview frame - if (!$this->isPhone()) { + if (!$browser->isPhone()) { $browser->assertVisible('#messagecontframe'); } // Toolbar menu - $this->assertToolbarMenu( + $browser->assertToolbarMenu( ['more'], // active items ['reply', 'reply-all', 'forward', 'delete', 'markmessage'], // inactive items ); // Task menu - $this->assertTaskMenu('mail'); + $browser->assertTaskMenu('mail'); }); } } diff --git a/tests/Browser/Settings/About.php b/tests/Browser/Settings/About.php index 5d065653d..667c5078d 100644 --- a/tests/Browser/Settings/About.php +++ b/tests/Browser/Settings/About.php @@ -2,22 +2,26 @@ namespace Tests\Browser\Settings; -class About extends \Tests\Browser\DuskTestCase +use Tests\Browser\Components\App; + +class About extends \Tests\Browser\TestCase { public function testAbout() { $this->browse(function ($browser) { - $this->go('settings'); + $browser->go('settings'); - $this->clickTaskMenuItem('about'); + $browser->clickTaskMenuItem('about'); $browser->assertSeeIn('.ui-dialog-title', 'About'); $browser->assertVisible('.ui-dialog #aboutframe'); $browser->withinFrame('#aboutframe', function ($browser) { // check task and action - $this->assertEnvEquals('task', 'settings'); - $this->assertEnvEquals('action', 'about'); + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'settings'); + $browser->assertEnv('action', 'about'); + }); $browser->assertSee($this->app->config->get('product_name')); $browser->assertVisible('#pluginlist'); diff --git a/tests/Browser/Settings/Folders.php b/tests/Browser/Settings/Folders.php index ca3ccff1a..73a2fc812 100644 --- a/tests/Browser/Settings/Folders.php +++ b/tests/Browser/Settings/Folders.php @@ -2,24 +2,25 @@ namespace Tests\Browser\Settings; -class Folders extends \Tests\Browser\DuskTestCase +use Tests\Browser\Components\App; + +class Folders extends \Tests\Browser\TestCase { public function testFolders() { $this->browse(function ($browser) { - $this->go('settings', 'folders'); + $browser->go('settings', 'folders'); // task should be set to 'settings' and action to 'folders' - $this->assertEnvEquals('task', 'settings'); - $this->assertEnvEquals('action', 'folders'); - - $objects = $this->getObjects(); + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'settings'); + $browser->assertEnv('action', 'folders'); - // these objects should be there always - $this->assertContains('quotadisplay', $objects); - $this->assertContains('subscriptionlist', $objects); + // these objects should be there always + $browser->assertObjects(['quotadisplay', 'subscriptionlist']); + }); - if ($this->isDesktop()) { + if ($browser->isDesktop()) { $browser->assertVisible('#settings-menu li.folders.selected'); } @@ -27,7 +28,7 @@ class Folders extends \Tests\Browser\DuskTestCase $browser->assertVisible('#subscription-table li.mailbox.inbox'); // Toolbar menu - $this->assertToolbarMenu(['create'], ['delete', 'purge']); + $browser->assertToolbarMenu(['create'], ['delete', 'purge']); }); } } diff --git a/tests/Browser/Settings/Identities.php b/tests/Browser/Settings/Identities.php index 62c95e80c..a65f5f25a 100644 --- a/tests/Browser/Settings/Identities.php +++ b/tests/Browser/Settings/Identities.php @@ -2,23 +2,25 @@ namespace Tests\Browser\Settings; -class Identities extends \Tests\Browser\DuskTestCase +use Tests\Browser\Components\App; + +class Identities extends \Tests\Browser\TestCase { public function testIdentities() { $this->browse(function ($browser) { - $this->go('settings', 'identities'); + $browser->go('settings', 'identities'); // check task and action - $this->assertEnvEquals('task', 'settings'); - $this->assertEnvEquals('action', 'identities'); - - $objects = $this->getObjects(); + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'settings'); + $browser->assertEnv('action', 'identities'); - // these objects should be there always - $this->assertContains('identitieslist', $objects); + // these objects should be there always + $browser->assertObjects(['identitieslist']); + }); - if ($this->isDesktop()) { + if ($browser->isDesktop()) { $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); // Toolbar menu - $this->assertToolbarMenu(['create'], ['delete']); + $browser->assertToolbarMenu(['create'], ['delete']); }); } } diff --git a/tests/Browser/Settings/Preferences.php b/tests/Browser/Settings/Preferences.php index bb9a1fc48..692252500 100644 --- a/tests/Browser/Settings/Preferences.php +++ b/tests/Browser/Settings/Preferences.php @@ -2,21 +2,23 @@ namespace Tests\Browser\Settings; -class Preferences extends \Tests\Browser\DuskTestCase +use Tests\Browser\Components\App; + +class Preferences extends \Tests\Browser\TestCase { public function testPreferences() { $this->browse(function ($browser) { - $this->go('settings'); - - $objects = $this->getObjects(); + $browser->go('settings'); - $this->assertContains('sectionslist', $objects); + $browser->with(new App(), function ($browser) { + $browser->assertObjects(['sectionslist']); + }); $browser->assertVisible('#settings-menu li.preferences.selected'); // On phone/tablet #sections-table is initially hidden - if ($this->isPhone() || $this->isTablet()) { + if (!$browser->isDesktop()) { $browser->assertMissing('#sections-table'); $browser->click('#settings-menu li.preferences'); $browser->waitFor('#sections-table'); diff --git a/tests/Browser/Settings/Preferences/General.php b/tests/Browser/Settings/Preferences/General.php index 24b4f77b9..378766322 100644 --- a/tests/Browser/Settings/Preferences/General.php +++ b/tests/Browser/Settings/Preferences/General.php @@ -2,7 +2,9 @@ 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() { @@ -16,9 +18,9 @@ class General extends \Tests\Browser\DuskTestCase public function testGeneral() { $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->waitFor('#sections-table'); } @@ -27,20 +29,22 @@ class General extends \Tests\Browser\DuskTestCase $browser->click('#sections-table tr.general'); - if ($this->isPhone()) { + if ($browser->isPhone()) { $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.next:not(.disabled)'); } $browser->withinFrame('#preferences-frame', function ($browser) { - if (!$this->isPhone()) { + if (!$browser->isPhone()) { $browser->waitFor('.formbuttons button.submit'); } // check task and action - $this->assertEnvEquals('task', 'settings'); - $this->assertEnvEquals('action', 'edit-prefs'); + $browser->with(new App(), function ($browser) { + $browser->assertEnv('task', 'settings'); + $browser->assertEnv('action', 'edit-prefs'); + }); // Main Options fieldset $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->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'); - $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->assertVisible('select[name=_refresh_interval]'); @@ -86,7 +90,7 @@ class General extends \Tests\Browser\DuskTestCase $browser->assertSeeIn('legend', 'Browser Options'); $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('_refresh_interval', $this->settings['refresh_interval']); - $this->setCheckboxState('_pretty_date', $this->settings['pretty_date']); - $this->setCheckboxState('_display_next', $this->settings['display_next']); - $this->setCheckboxState('_standard_windows', $this->settings['standard_windows']); + $browser->setCheckboxState('_pretty_date', $this->settings['pretty_date']); + $browser->setCheckboxState('_display_next', $this->settings['display_next']); + $browser->setCheckboxState('_standard_windows', $this->settings['standard_windows']); // Submit form - if (!$this->isPhone()) { + if (!$browser->isPhone()) { $browser->click('.formbuttons button.submit'); } }); - if ($this->isPhone()) { + if ($browser->isPhone()) { $browser->click('#layout-content .footer a.submit'); } - $this->waitForMessage('confirmation', 'Successfully saved'); + $browser->waitForMessage('confirmation', 'Successfully saved'); // Verify if every option has been updated $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('_refresh_interval', $this->settings['refresh_interval']); - $this->assertCheckboxState('_pretty_date', $this->settings['pretty_date']); - $this->assertCheckboxState('_display_next', $this->settings['display_next']); - $this->assertCheckboxState('_standard_windows', $this->settings['standard_windows']); + $browser->assertCheckboxState('_pretty_date', $this->settings['pretty_date']); + $browser->assertCheckboxState('_display_next', $this->settings['display_next']); + $browser->assertCheckboxState('_standard_windows', $this->settings['standard_windows']); // Assert the options have been saved in database properly $prefs = \bootstrap::get_prefs(); diff --git a/tests/Browser/Settings/Responses.php b/tests/Browser/Settings/Responses.php index 5c3a09501..844923730 100644 --- a/tests/Browser/Settings/Responses.php +++ b/tests/Browser/Settings/Responses.php @@ -2,23 +2,25 @@ namespace Tests\Browser\Settings; -class Responses extends \Tests\Browser\DuskTestCase +use Tests\Browser\Components\App; + +class Responses extends \Tests\Browser\TestCase { public function testIdentities() { $this->browse(function ($browser) { - $this->go('settings', 'responses'); - - // check task and action - $this->assertEnvEquals('task', 'settings'); - $this->assertEnvEquals('action', 'responses'); + $browser->go('settings', 'responses'); - $objects = $this->getObjects(); + $browser->with(new App(), function ($browser) { + // check task and action + $browser->assertEnv('task', 'settings'); + $browser->assertEnv('action', 'responses'); - // these objects should be there always - $this->assertContains('responseslist', $objects); + // these objects should be there always + $browser->assertObjects(['responseslist']); + }); - if ($this->isDesktop()) { + if ($browser->isDesktop()) { $browser->assertVisible('#settings-menu li.responses.selected'); } @@ -27,7 +29,7 @@ class Responses extends \Tests\Browser\DuskTestCase $browser->assertMissing('#responses-table tr'); // Toolbar menu - $this->assertToolbarMenu(['create'], ['delete']); + $browser->assertToolbarMenu(['create'], ['delete']); }); } } diff --git a/tests/Browser/Settings/Settings.php b/tests/Browser/Settings/Settings.php index a5fd9d889..2f4e95a38 100644 --- a/tests/Browser/Settings/Settings.php +++ b/tests/Browser/Settings/Settings.php @@ -2,15 +2,19 @@ namespace Tests\Browser\Settings; -class Settings extends \Tests\Browser\DuskTestCase +use Tests\Browser\Components\App; + +class Settings extends \Tests\Browser\TestCase { public function testSettings() { $this->browse(function ($browser) { - $this->go('settings'); + $browser->go('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'); @@ -23,7 +27,7 @@ class Settings extends \Tests\Browser\DuskTestCase }); // Task menu - $this->assertTaskMenu('settings'); + $browser->assertTaskMenu('settings'); }); } } diff --git a/tests/Browser/TestCase.php b/tests/Browser/TestCase.php new file mode 100644 index 000000000..04e4a31f2 --- /dev/null +++ b/tests/Browser/TestCase.php @@ -0,0 +1,169 @@ +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(); + }); + } +} diff --git a/tests/Browser/bootstrap.php b/tests/Browser/bootstrap.php index 6d8c036b5..49db75944 100644 --- a/tests/Browser/bootstrap.php +++ b/tests/Browser/bootstrap.php @@ -31,7 +31,11 @@ define('TESTS_DIR', realpath(__DIR__) . '/'); define('TESTS_USER', $rcmail->config->get('tests_username')); 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'); /** diff --git a/tests/Browser/phpunit.xml b/tests/Browser/phpunit.xml index ada5031c6..3c31c2d80 100644 --- a/tests/Browser/phpunit.xml +++ b/tests/Browser/phpunit.xml @@ -3,8 +3,8 @@ colors="true"> - Login.php - Logout.php + Logon/Login.php + Logon/Logout.php Contacts/Contacts.php