diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 535da2e09..83f945fce 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -73,6 +73,11 @@ class rcmail extends rcube static function get_instance($mode = 0, $env = '') { if (!self::$instance || !is_a(self::$instance, 'rcmail')) { + // In cli-server mode env=test + if ($env === null && php_sapi_name() == 'cli-server') { + $env = 'test'; + } + self::$instance = new rcmail($env); // init AFTER object was linked with self::$instance self::$instance->startup(); diff --git a/tests/Browser/Addressbook/Addressbook.php b/tests/Browser/Addressbook/Addressbook.php new file mode 100644 index 000000000..a73a542c0 --- /dev/null +++ b/tests/Browser/Addressbook/Addressbook.php @@ -0,0 +1,24 @@ +browse(function ($browser) { + $this->go('addressbook'); + + // check task + $this->assertEnvEquals('task', 'addressbook'); + + $objects = $this->getObjects(); + + // these objects should be there always + $this->assertContains('qsearchbox', $objects); + $this->assertContains('folderlist', $objects); + $this->assertContains('contactslist', $objects); + $this->assertContains('countdisplay', $objects); + }); + } +} diff --git a/tests/Browser/Addressbook/Import.php b/tests/Browser/Addressbook/Import.php new file mode 100644 index 000000000..4b136ccbf --- /dev/null +++ b/tests/Browser/Addressbook/Import.php @@ -0,0 +1,34 @@ +browse(function ($browser) { + $this->go('addressbook', 'import'); + + // 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); + }); + } + + public function testImport2() + { + $this->browse(function ($browser) { + $this->go('addressbook', 'import'); + + $objects = $this->getObjects(); + + // these objects should be there always + $this->assertContains('importform', $objects); + }); + } +} diff --git a/tests/Browser/DuskTestCase.php b/tests/Browser/DuskTestCase.php new file mode 100644 index 000000000..0381a0b9c --- /dev/null +++ b/tests/Browser/DuskTestCase.php @@ -0,0 +1,190 @@ +addArguments([ + '--disable-gpu', + '--headless', + '--window-size=1280,720', + ]); + + 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 = __DIR__ . '/screenshots'; + Browser::$storeConsoleLogAt = __DIR__ . '/console'; + + // 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(__DIR__ . '/screenshots')->name($pattern); + foreach ($files as $file) { + @unlink($file->getRealPath()); + } + } + catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) { + // ignore missing screenshots directory + } + } + + /** + * Assert specified rcmail.env value + */ + protected function assertEnvEquals($key, $expected) + { + $this->assertEquals($expected, $this->getEnv($key)); + } + + /** + * 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; + } + + /** + * Get HTML IDs of defined buttons for specified Roundcube action + */ + protected function getButtons($action) + { + $this->browse(function (Browser $browser) use ($action, &$buttons) { + $buttons = $browser->script("return rcmail.buttons['$action']"); + $buttons = $buttons[0]; + }); + + if (is_array($buttons)) { + foreach ($buttons as $idx => $button) { + $buttons[$idx] = $button['id']; + } + } + + return (array) $buttons; + } + + /** + * 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(); + } + }); + } + + /** + * Starts PHP server. + */ + protected static function startWebServer() + { + $path = realpath(__DIR__ . '/../../public_html'); + $cmd = ['php', '-S', 'localhost:8000']; + + static::$phpProcess = new Process($cmd, null, []); + static::$phpProcess->setWorkingDirectory($path); + static::$phpProcess->start(); + + static::afterClass(function () { + static::$phpProcess->stop(); + }); + } +} diff --git a/tests/Browser/Login.php b/tests/Browser/Login.php new file mode 100644 index 000000000..dcc20ad3c --- /dev/null +++ b/tests/Browser/Login.php @@ -0,0 +1,36 @@ +browse(function ($browser) { + $browser->visit('/'); + + $browser->assertTitleContains($this->app->config->get('product_name')); + + // task should be set to 'login' + $this->assertEnvEquals('task', 'login'); + + $browser->assertVisible('#rcmloginuser'); + $browser->assertVisible('#rcmloginpwd'); + + // test valid login + $this->go('mail'); + + // task should be set to 'mail' now + $this->assertEnvEquals('task', 'mail'); + }); + } +} diff --git a/tests/Browser/Logout.php b/tests/Browser/Logout.php new file mode 100644 index 000000000..4e563e264 --- /dev/null +++ b/tests/Browser/Logout.php @@ -0,0 +1,25 @@ +browse(function ($browser) { + $this->go('settings'); + + // wait for the menu and then click the Logout button + $browser->waitFor('#taskmenu'); + $browser->click('#taskmenu a.logout'); + + // task should be set to 'login' + $this->assertEnvEquals('task', 'login'); + + // form should exist + $browser->assertVisible('input[name="_user"]'); + $browser->assertVisible('input[name="_pass"]'); + $browser->assertMissing('#taskmenu'); + }); + } +} diff --git a/tests/Browser/Mail/CheckRecent.php b/tests/Browser/Mail/CheckRecent.php new file mode 100644 index 000000000..4af02034e --- /dev/null +++ b/tests/Browser/Mail/CheckRecent.php @@ -0,0 +1,15 @@ +browse(function ($browser) { + $this->go('mail'); + + // TODO + }); + } +} diff --git a/tests/Browser/Mail/Compose.php b/tests/Browser/Mail/Compose.php new file mode 100644 index 000000000..5d9d988e7 --- /dev/null +++ b/tests/Browser/Mail/Compose.php @@ -0,0 +1,28 @@ +browse(function ($browser) { + $this->go('mail', 'compose'); + + // check task and action + $this->assertEnvEquals('task', 'mail'); + $this->assertEnvEquals('action', 'compose'); + + $objects = $this->getObjects(); + + // 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); + }); + } +} diff --git a/tests/Browser/Mail/Getunread.php b/tests/Browser/Mail/Getunread.php new file mode 100644 index 000000000..e6300680c --- /dev/null +++ b/tests/Browser/Mail/Getunread.php @@ -0,0 +1,37 @@ +msgcount++; + } + } + + public function testGetunread() + { + $this->browse(function ($browser) { + $this->go('mail'); + + // Folders list state + $browser->waitFor('.folderlist li.inbox.unread'); + + $this->assertEquals(strval($this->msgcount), $browser->text('.folderlist li.inbox span.unreadcount')); + + // Messages list state + $this->assertCount($this->msgcount, $browser->elements('#messagelist tr.unread')); + }); + } +} diff --git a/tests/Browser/Mail/List.php b/tests/Browser/Mail/List.php new file mode 100644 index 000000000..134f0bc3f --- /dev/null +++ b/tests/Browser/Mail/List.php @@ -0,0 +1,35 @@ +browse(function ($browser) { + $this->go('mail'); + + $this->assertCount(1, $browser->elements('#messagelist tbody tr')); + + // check message list + $browser->assertVisible('#messagelist tbody tr:first-child.unread'); + + $this->assertEquals('Lines', $browser->text('#messagelist tbody tr:first-child span.subject')); + + //$browser->assertVisible('#messagelist tbody tr:first-child span.msgicon.unread'); + }); + } +} diff --git a/tests/Browser/Mail/Mail.php b/tests/Browser/Mail/Mail.php new file mode 100644 index 000000000..1e767c2b8 --- /dev/null +++ b/tests/Browser/Mail/Mail.php @@ -0,0 +1,26 @@ +browse(function ($browser) { + $this->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); + }); + } +} diff --git a/tests/Browser/README.md b/tests/Browser/README.md new file mode 100644 index 000000000..56f5d10ca --- /dev/null +++ b/tests/Browser/README.md @@ -0,0 +1,60 @@ +In-Browser Tests +================ + +The idea of these testing suite is to make it as simple as possible to execute +the tests. So, you don't have to run any additional services, nor download +and install anything manually. + +The tests are using [Laravel Dusk][laravel-dusk] and Chrome WebDriver. +PHP server is used to serve Roundcube instance on tests run. + + +INSTALLATION +------------ + +Installation: + +1. Add `"laravel/dusk": "~5.7.0"` to your composer.json file and run `composer update`. +2. Install Chrome WebDriver for the version of Chrome/Chromium in your system. Yes, + you have to have Chrome/Chromium installed. +``` +php tests/Browser/install.php [version]` +``` +3. Configure the test account and Roundcube instance. + +Create a config file named `config-test.inc.php` in the Roundcube config dir. +That file should provide specific `db_dsnw` and +`default_host` values for testing purposes as well as the credentials of a +valid IMAP user account used for running the tests with. + +Add these config options used by the Browser tests: + +```php + // Unit tests settings + $config['tests_username'] = 'roundcube.test@example.org'; + $config['tests_password'] = ''; +``` + +WARNING +------- +Please note that the configured IMAP account as well as the Roundcube database +configred in `db_dsnw` will be wiped and filled with test data in every test +run. Under no circumstances you should use credentials of a production database +or email account! + +Please, keep the file as simple as possible, i.e. containing only database +and imap/smtp settings needed for the test user authentication. We would +want to test default configuration. Especially only Elastic skin is supported. + + +EXECUTING THE TESTS +------------------- + +To run the test suite call `phpunit` from the tests/Browser directory: + +``` + cd /tests/Browser + phpunit # or ../../vendor/bin/phpunit +``` + +[laravel-dusk]: https://github.com/laravel/dusk diff --git a/tests/Browser/Settings/About.php b/tests/Browser/Settings/About.php new file mode 100644 index 000000000..f687a9d1e --- /dev/null +++ b/tests/Browser/Settings/About.php @@ -0,0 +1,19 @@ +browse(function ($browser) { + $this->go('settings', 'about'); + + // check task and action + $this->assertEnvEquals('task', 'settings'); + $this->assertEnvEquals('action', 'about'); + + $browser->assertVisible('#pluginlist'); + }); + } +} diff --git a/tests/Browser/Settings/Folders.php b/tests/Browser/Settings/Folders.php new file mode 100644 index 000000000..82c0b7d2e --- /dev/null +++ b/tests/Browser/Settings/Folders.php @@ -0,0 +1,23 @@ +browse(function ($browser) { + $this->go('settings', 'folders'); + + // task should be set to 'settings' and action to 'folders' + $this->assertEnvEquals('task', 'settings'); + $this->assertEnvEquals('action', 'folders'); + + $objects = $this->getObjects(); + + // these objects should be there always + $this->assertContains('quotadisplay', $objects); + $this->assertContains('subscriptionlist', $objects); + }); + } +} diff --git a/tests/Browser/Settings/Identities.php b/tests/Browser/Settings/Identities.php new file mode 100644 index 000000000..d885f9edb --- /dev/null +++ b/tests/Browser/Settings/Identities.php @@ -0,0 +1,22 @@ +browse(function ($browser) { + $this->go('settings', 'identities'); + + // check task and action + $this->assertEnvEquals('task', 'settings'); + $this->assertEnvEquals('action', 'identities'); + + $objects = $this->getObjects(); + + // these objects should be there always + $this->assertContains('identitieslist', $objects); + }); + } +} diff --git a/tests/Browser/Settings/Settings.php b/tests/Browser/Settings/Settings.php new file mode 100644 index 000000000..cfef01ceb --- /dev/null +++ b/tests/Browser/Settings/Settings.php @@ -0,0 +1,20 @@ +browse(function ($browser) { + $this->go('settings'); + + // task should be set to 'settings' + $this->assertEnvEquals('task', 'settings'); + + $objects = $this->getObjects(); + + $this->assertContains('sectionslist', $objects); + }); + } +} diff --git a/tests/Browser/bootstrap.php b/tests/Browser/bootstrap.php new file mode 100644 index 000000000..f8803c420 --- /dev/null +++ b/tests/Browser/bootstrap.php @@ -0,0 +1,174 @@ + | + | Author: Aleksander Machniak | + +-----------------------------------------------------------------------+ +*/ + +if (php_sapi_name() != 'cli') + die("Not in shell mode (php-cli)"); + +if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(__DIR__ . '/../../') . '/' ); + +require_once(INSTALL_PATH . 'program/include/iniset.php'); + +$rcmail = rcmail::get_instance(0, 'test'); + +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'); + + +/** + * Utilities for test environment setup + */ +class bootstrap +{ + static $imap_ready = null; + + /** + * Wipe and re-initialize database + */ + public static function init_db() + { + $rcmail = rcmail::get_instance(); + $dsn = rcube_db::parse_dsn($rcmail->config->get('db_dsnw')); + + if ($dsn['phptype'] == 'mysql' || $dsn['phptype'] == 'mysqli') { + // drop all existing tables first + $db = $rcmail->get_dbh(); + $db->query("SET FOREIGN_KEY_CHECKS=0"); + $sql_res = $db->query("SHOW TABLES"); + while ($sql_arr = $db->fetch_array($sql_res)) { + $table = reset($sql_arr); + $db->query("DROP TABLE $table"); + } + + // init database with schema + system(sprintf('cat %s %s | mysql -h %s -u %s --password=%s %s', + realpath(INSTALL_PATH . '/SQL/mysql.initial.sql'), + realpath(TESTS_DIR . 'data/mysql.sql'), + escapeshellarg($dsn['hostspec']), + escapeshellarg($dsn['username']), + escapeshellarg($dsn['password']), + escapeshellarg($dsn['database']) + )); + } + else if ($dsn['phptype'] == 'sqlite') { + // delete database file -- will be re-initialized on first access + system(sprintf('rm -f %s', escapeshellarg($dsn['database']))); + } + } + + /** + * Wipe the configured IMAP account and fill with test data + */ + public static function init_imap() + { + if (!TESTS_USER) { + return false; + } + else if (self::$imap_ready !== null) { + return self::$imap_ready; + } + + self::connect_imap(TESTS_USER, TESTS_PASS); + self::purge_mailbox('INBOX'); + self::ensure_mailbox('Archive', true); + + return self::$imap_ready; + } + + /** + * Authenticate to IMAP with the given credentials + */ + public static function connect_imap($username, $password, $host = null) + { + $rcmail = rcmail::get_instance(); + $imap = $rcmail->get_storage(); + + if ($imap->is_connected()) { + $imap->close(); + self::$imap_ready = false; + } + + $imap_host = $host ?: $rcmail->config->get('default_host'); + $a_host = parse_url($imap_host); + if ($a_host['host']) { + $imap_host = $a_host['host']; + $imap_ssl = isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls')); + $imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : 143); + } + else { + $imap_port = 143; + $imap_ssl = false; + } + + if (!$imap->connect($imap_host, $username, $password, $imap_port, $imap_ssl)) { + die("IMAP error: unable to authenticate with user " . TESTS_USER); + } + + self::$imap_ready = true; + } + + /** + * Import the given file into IMAP + */ + public static function import_message($filename, $mailbox = 'INBOX') + { + if (!self::init_imap()) { + die(__METHOD__ . ': IMAP connection unavailable'); + } + + $imap = rcmail::get_instance()->get_storage(); + $imap->save_message($mailbox, file_get_contents($filename)); + } + + /** + * Delete all messages from the given mailbox + */ + public static function purge_mailbox($mailbox) + { + if (!self::init_imap()) { + die(__METHOD__ . ': IMAP connection unavailable'); + } + + $imap = rcmail::get_instance()->get_storage(); + $imap->delete_message('*', $mailbox); + } + + /** + * Make sure the given mailbox exists in IMAP + */ + public static function ensure_mailbox($mailbox, $empty = false) + { + if (!self::init_imap()) { + die(__METHOD__ . ': IMAP connection unavailable'); + } + + $imap = rcmail::get_instance()->get_storage(); + + $folders = $imap->list_folders(); + if (!in_array($mailbox, $folders)) { + $imap->create_folder($mailbox, true); + } + else if ($empty) { + $imap->delete_message('*', $mailbox); + } + } +} diff --git a/tests/Selenium/data/mail/list_00.eml b/tests/Browser/data/mail/list_00.eml similarity index 100% rename from tests/Selenium/data/mail/list_00.eml rename to tests/Browser/data/mail/list_00.eml diff --git a/tests/Selenium/data/mysql.sql b/tests/Browser/data/mysql.sql similarity index 100% rename from tests/Selenium/data/mysql.sql rename to tests/Browser/data/mysql.sql diff --git a/tests/Browser/install.php b/tests/Browser/install.php new file mode 100644 index 000000000..4fba9022d --- /dev/null +++ b/tests/Browser/install.php @@ -0,0 +1,67 @@ + | + +-----------------------------------------------------------------------+ +*/ + +if (php_sapi_name() != 'cli') + die("Not in shell mode (php-cli)"); + +if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(__DIR__ . '/../../') . '/' ); + +require_once(INSTALL_PATH . 'program/include/iniset.php'); + +class Installer extends Laravel\Dusk\Console\ChromeDriverCommand +{ + /** + * Execute the console command. + * + * @param string $version + * + * @return void + */ + public function install($version = null) + { + $version = trim($this->getUrl(sprintf($this->versionUrl, $version ?: $this->latestVersion()))); + $currentOS = Laravel\Dusk\OperatingSystem::id(); + + foreach ($this->slugs as $os => $slug) { + if ($os === $currentOS) { + $archive = $this->download($version, $slug); + $binary = $this->extract($archive); + + $this->rename($binary, $os); + } + } + + echo "ChromeDriver binary successfully installed for version $version.\n"; + } + + /** + * Get the contents of a URL + * + * @param string $url URL + * + * @return string|bool + */ + protected function getUrl(string $url) + { + return file_get_contents($url); + } +} + +$installer = new Installer; +$installer->install($argv[1]); diff --git a/tests/Selenium/phpunit.xml b/tests/Browser/phpunit.xml similarity index 80% rename from tests/Selenium/phpunit.xml rename to tests/Browser/phpunit.xml index fe0c7016c..eb6ea316c 100644 --- a/tests/Selenium/phpunit.xml +++ b/tests/Browser/phpunit.xml @@ -2,28 +2,26 @@ bootstrap="bootstrap.php" colors="true"> - - Login.php - Mail/Mail.php - Mail/CheckRecent.php - Mail/Compose.php - Mail/Getunread.php - Mail/List.php - Logout.php + + Login.php + Logout.php - Login.php Addressbook/Addressbook.php Addressbook/Import.php - Logout.php - Login.php Settings/About.php Settings/Folders.php Settings/Identities.php Settings/Settings.php - Logout.php + + + Mail/Mail.php + Mail/CheckRecent.php + Mail/Compose.php + Mail/Getunread.php + Mail/List.php diff --git a/tests/Selenium/Addressbook/Addressbook.php b/tests/Selenium/Addressbook/Addressbook.php deleted file mode 100644 index 9a22b6e13..000000000 --- a/tests/Selenium/Addressbook/Addressbook.php +++ /dev/null @@ -1,21 +0,0 @@ -go('addressbook'); - - // check task - $env = $this->get_env(); - $this->assertEquals('addressbook', $env['task']); - - $objects = $this->get_objects(); - - // these objects should be there always - $this->assertContains('qsearchbox', $objects); - $this->assertContains('folderlist', $objects); - $this->assertContains('contactslist', $objects); - $this->assertContains('countdisplay', $objects); - } -} diff --git a/tests/Selenium/Addressbook/Import.php b/tests/Selenium/Addressbook/Import.php deleted file mode 100644 index 13d81740f..000000000 --- a/tests/Selenium/Addressbook/Import.php +++ /dev/null @@ -1,29 +0,0 @@ -go('addressbook', 'import'); - - // check task and action - $env = $this->get_env(); - $this->assertEquals('addressbook', $env['task']); - $this->assertEquals('import', $env['action']); - - $objects = $this->get_objects(); - - // these objects should be there always - $this->assertContains('importform', $objects); - } - - public function testImport2() - { - $this->go('addressbook', 'import'); - - $objects = $this->get_objects(); - - // these objects should be there always - $this->assertContains('importform', $objects); - } -} diff --git a/tests/Selenium/Login.php b/tests/Selenium/Login.php deleted file mode 100644 index 6910b43e6..000000000 --- a/tests/Selenium/Login.php +++ /dev/null @@ -1,28 +0,0 @@ -url(TESTS_URL); - - // task should be set to 'login' - $env = $this->get_env(); - $this->assertEquals('login', $env['task']); - - // test valid login - $this->login(); - - // task should be set to 'mail' now - $env = $this->get_env(); - $this->assertEquals('mail', $env['task']); - } -} diff --git a/tests/Selenium/Logout.php b/tests/Selenium/Logout.php deleted file mode 100644 index 95eeda57c..000000000 --- a/tests/Selenium/Logout.php +++ /dev/null @@ -1,20 +0,0 @@ -go('mail'); - - $this->click_button('logout'); - - sleep(TESTS_SLEEP); - - // task should be set to 'login' - $env = $this->get_env(); - $this->assertEquals('login', $env['task']); - - // form should exist - $user_input = $this->byCssSelector('form input[name="_user"]'); - } -} diff --git a/tests/Selenium/Mail/CheckRecent.php b/tests/Selenium/Mail/CheckRecent.php deleted file mode 100644 index 865421c2d..000000000 --- a/tests/Selenium/Mail/CheckRecent.php +++ /dev/null @@ -1,14 +0,0 @@ -go('mail'); - - $res = $this->ajaxResponse('check-recent', "rcmail.command('checkmail')"); - - $this->assertEquals('check-recent', $res['action']); - $this->assertRegExp('/this\.set_unread_count/', $res['exec']); - } -} diff --git a/tests/Selenium/Mail/Compose.php b/tests/Selenium/Mail/Compose.php deleted file mode 100644 index e707ef17d..000000000 --- a/tests/Selenium/Mail/Compose.php +++ /dev/null @@ -1,25 +0,0 @@ -go('mail', 'compose'); - - // check task and action - $env = $this->get_env(); - $this->assertEquals('mail', $env['task']); - $this->assertEquals('compose', $env['action']); - - $objects = $this->get_objects(); - - // 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); - } -} diff --git a/tests/Selenium/Mail/Getunread.php b/tests/Selenium/Mail/Getunread.php deleted file mode 100644 index c18ddc0dd..000000000 --- a/tests/Selenium/Mail/Getunread.php +++ /dev/null @@ -1,37 +0,0 @@ -msgcount++; - } - } - - public function testGetunread() - { - $this->go('mail'); - - $res = $this->ajaxResponse('getunread', "rcmail.http_request('getunread')"); - $this->assertEquals('getunread', $res['action']); - - $env = $this->get_env(); - $this->assertEquals($env['unread_counts']['INBOX'], $this->msgcount); - - $li = $this->byCssSelector('.folderlist li.inbox'); - $this->assertHasClass('unread', $li); - - $badge = $this->byCssSelector('.folderlist li.inbox span.unreadcount'); - $this->assertEquals(strval($this->msgcount), $this->getText($badge)); - } -} diff --git a/tests/Selenium/Mail/List.php b/tests/Selenium/Mail/List.php deleted file mode 100644 index dc2857777..000000000 --- a/tests/Selenium/Mail/List.php +++ /dev/null @@ -1,50 +0,0 @@ -go('mail'); - - $res = $this->ajaxResponse('list', "rcmail.command('list')"); - - $this->assertEquals('list', $res['action']); - $this->assertRegExp('/this\.set_pagetitle/', $res['exec']); - $this->assertRegExp('/this\.set_unread_count/', $res['exec']); - $this->assertRegExp('/this\.set_rowcount/', $res['exec']); - $this->assertRegExp('/this\.set_message_coltypes/', $res['exec']); - - $this->assertContains('current_page', $res['env']); - $this->assertContains('exists', $res['env']); - $this->assertContains('pagecount', $res['env']); - $this->assertContains('pagesize', $res['env']); - $this->assertContains('messagecount', $res['env']); - $this->assertContains('mailbox', $res['env']); - - $this->assertEquals($res['env']['mailbox'], 'INBOX'); - $this->assertEquals($res['env']['messagecount'], 1); - - // check message list - $row = $this->byCssSelector('.messagelist tbody tr:first-child'); - $this->assertHasClass('unread', $row); - - $subject = $this->byCssSelector('.messagelist tbody tr:first-child td.subject'); - $this->assertEquals('Lines', $this->getText($subject)); - - $icon = $this->byCssSelector('.messagelist tbody tr:first-child td.status span'); - $this->assertHasClass('unread', $icon); - } -} diff --git a/tests/Selenium/Mail/Mail.php b/tests/Selenium/Mail/Mail.php deleted file mode 100644 index 98413787b..000000000 --- a/tests/Selenium/Mail/Mail.php +++ /dev/null @@ -1,23 +0,0 @@ -go('mail'); - - // check task - $env = $this->get_env(); - $this->assertEquals('mail', $env['task']); - - $objects = $this->get_objects(); - - // 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); - } -} diff --git a/tests/Selenium/README.md b/tests/Selenium/README.md deleted file mode 100644 index 5610fae71..000000000 --- a/tests/Selenium/README.md +++ /dev/null @@ -1,49 +0,0 @@ -Running Selenium Tests -====================== - -In order to run the Selenium-based web tests, some configuration for the -Roundcube test instance need to be created. Along with the default config for a -given Roundcube instance, you should provide a config specifically for running -tests. To do so, create a config file named `config-test.inc.php` in the -regular Roundcube config dir. That should provide specific `db_dsnw` and -`default_host` values for testing purposes as well as the credentials of a -valid IMAP user account used for running the tests with. - -Add these config options used by the Selenium tests: - -```php - // Unit tests settings - $config['tests_username'] = 'roundcube.test@example.org'; - $config['tests_password'] = ''; - $config['tests_url'] = 'http://localhost/roundcube/index-test.php'; -``` - -The `tests_url` should point to Roundcube's index-test.php file accessible by -the Selenium web browser. - -WARNING -------- -Please note that the configured IMAP account as well as the Roundcube database -configred in `db_dsnw` will be wiped and filled with test data in every test -run. Under no circumstances you should use credentials of a production database -or email account! - - -Run the tests -------------- - -First you need to start a Selenium server. We recommend to use the -[Selenium Standalone Server][selenium-server] but the tests will also run on a -Selenium Grid. The tests are based in [PHPUnit_Selenium][phpunit] which can be -installed through [PEAR][pear-phpunit]. - -To start the test suite call `phpunit` from the Selenium directory: - -``` - cd /tests/Selenium - phpunit -``` - -[phpunit]: http://phpunit.de/manual/4.0/en/selenium.html -[pear-phpunit]: http://pear.phpunit.de/ -[selenium-server]: http://docs.seleniumhq.org/download/ diff --git a/tests/Selenium/Settings/About.php b/tests/Selenium/Settings/About.php deleted file mode 100644 index 4cd49431a..000000000 --- a/tests/Selenium/Settings/About.php +++ /dev/null @@ -1,15 +0,0 @@ -url(TESTS_URL . '?_task=settings&_action=about'); - sleep(TESTS_SLEEP); - - // check task and action - $env = $this->get_env(); - $this->assertEquals('settings', $env['task']); - $this->assertEquals('about', $env['action']); - } -} diff --git a/tests/Selenium/Settings/Folders.php b/tests/Selenium/Settings/Folders.php deleted file mode 100644 index fa64e45d6..000000000 --- a/tests/Selenium/Settings/Folders.php +++ /dev/null @@ -1,20 +0,0 @@ -go('settings', 'folders'); - - // task should be set to 'settings' and action to 'folders' - $env = $this->get_env(); - $this->assertEquals('settings', $env['task']); - $this->assertEquals('folders', $env['action']); - - $objects = $this->get_objects(); - - // these objects should be there always - $this->assertContains('quotadisplay', $objects); - $this->assertContains('subscriptionlist', $objects); - } -} diff --git a/tests/Selenium/Settings/Identities.php b/tests/Selenium/Settings/Identities.php deleted file mode 100644 index 869018b09..000000000 --- a/tests/Selenium/Settings/Identities.php +++ /dev/null @@ -1,19 +0,0 @@ -go('settings', 'identities'); - - // check task and action - $env = $this->get_env(); - $this->assertEquals('settings', $env['task']); - $this->assertEquals('identities', $env['action']); - - $objects = $this->get_objects(); - - // these objects should be there always - $this->assertContains('identitieslist', $objects); - } -} diff --git a/tests/Selenium/Settings/Settings.php b/tests/Selenium/Settings/Settings.php deleted file mode 100644 index 08d8339f1..000000000 --- a/tests/Selenium/Settings/Settings.php +++ /dev/null @@ -1,17 +0,0 @@ -go('settings'); - - // task should be set to 'settings' - $env = $this->get_env(); - $this->assertEquals('settings', $env['task']); - - $objects = $this->get_objects(); - - $this->assertContains('sectionslist', $objects); - } -} diff --git a/tests/Selenium/bootstrap.php b/tests/Selenium/bootstrap.php deleted file mode 100644 index 47e53757f..000000000 --- a/tests/Selenium/bootstrap.php +++ /dev/null @@ -1,348 +0,0 @@ - | - | Author: Aleksander Machniak | - +-----------------------------------------------------------------------+ -*/ - -if (php_sapi_name() != 'cli') - die("Not in shell mode (php-cli)"); - -if (!defined('INSTALL_PATH')) define('INSTALL_PATH', realpath(__DIR__ . '/../../') . '/' ); - -define('TESTS_DIR', realpath(__DIR__ . '/../') . '/'); - -if (@is_dir(TESTS_DIR . 'config')) { - define('RCUBE_CONFIG_DIR', TESTS_DIR . 'config'); -} - -require_once(INSTALL_PATH . 'program/include/iniset.php'); - -// Extend include path so some plugin test won't fail -$include_path = ini_get('include_path') . PATH_SEPARATOR . TESTS_DIR . '..'; -if (set_include_path($include_path) === false) { - die("Fatal error: ini_set/set_include_path does not work."); -} - -$rcmail = rcmail::get_instance(0, 'test'); - -define('TESTS_URL', $rcmail->config->get('tests_url')); -define('TESTS_BROWSER', $rcmail->config->get('tests_browser', 'firefox')); -define('TESTS_USER', $rcmail->config->get('tests_username')); -define('TESTS_PASS', $rcmail->config->get('tests_password')); -define('TESTS_SLEEP', $rcmail->config->get('tests_sleep', 5)); - -PHPUnit_Extensions_Selenium2TestCase::shareSession(true); - - -/** - * satisfy PHPUnit - */ -class bootstrap -{ - static $imap_ready = null; - - /** - * Wipe and re-initialize (mysql) database - */ - public static function init_db() - { - $rcmail = rcmail::get_instance(); - $dsn = rcube_db::parse_dsn($rcmail->config->get('db_dsnw')); - - if ($dsn['phptype'] == 'mysql' || $dsn['phptype'] == 'mysqli') { - // drop all existing tables first - $db = $rcmail->get_dbh(); - $db->query("SET FOREIGN_KEY_CHECKS=0"); - $sql_res = $db->query("SHOW TABLES"); - while ($sql_arr = $db->fetch_array($sql_res)) { - $table = reset($sql_arr); - $db->query("DROP TABLE $table"); - } - - // init database with schema - system(sprintf('cat %s %s | mysql -h %s -u %s --password=%s %s', - realpath(INSTALL_PATH . '/SQL/mysql.initial.sql'), - realpath(TESTS_DIR . 'Selenium/data/mysql.sql'), - escapeshellarg($dsn['hostspec']), - escapeshellarg($dsn['username']), - escapeshellarg($dsn['password']), - escapeshellarg($dsn['database']) - )); - } - else if ($dsn['phptype'] == 'sqlite') { - // delete database file -- will be re-initialized on first access - system(sprintf('rm -f %s', escapeshellarg($dsn['database']))); - } - } - - /** - * Wipe the configured IMAP account and fill with test data - */ - public static function init_imap() - { - if (!TESTS_USER) { - return false; - } - else if (self::$imap_ready !== null) { - return self::$imap_ready; - } - - self::connect_imap(TESTS_USER, TESTS_PASS); - self::purge_mailbox('INBOX'); - self::ensure_mailbox('Archive', true); - - return self::$imap_ready; - } - - /** - * Authenticate to IMAP with the given credentials - */ - public static function connect_imap($username, $password, $host = null) - { - $rcmail = rcmail::get_instance(); - $imap = $rcmail->get_storage(); - - if ($imap->is_connected()) { - $imap->close(); - self::$imap_ready = false; - } - - $imap_host = $host ?: $rcmail->config->get('default_host'); - $a_host = parse_url($imap_host); - if ($a_host['host']) { - $imap_host = $a_host['host']; - $imap_ssl = isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls')); - $imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : 143); - } - else { - $imap_port = 143; - $imap_ssl = false; - } - - if (!$imap->connect($imap_host, $username, $password, $imap_port, $imap_ssl)) { - die("IMAP error: unable to authenticate with user " . TESTS_USER); - } - - self::$imap_ready = true; - } - - /** - * Import the given file into IMAP - */ - public static function import_message($filename, $mailbox = 'INBOX') - { - if (!self::init_imap()) { - die(__METHOD__ . ': IMAP connection unavailable'); - } - - $imap = rcmail::get_instance()->get_storage(); - $imap->save_message($mailbox, file_get_contents($filename)); - } - - /** - * Delete all messages from the given mailbox - */ - public static function purge_mailbox($mailbox) - { - if (!self::init_imap()) { - die(__METHOD__ . ': IMAP connection unavailable'); - } - - $imap = rcmail::get_instance()->get_storage(); - $imap->delete_message('*', $mailbox); - } - - /** - * Make sure the given mailbox exists in IMAP - */ - public static function ensure_mailbox($mailbox, $empty = false) - { - if (!self::init_imap()) { - die(__METHOD__ . ': IMAP connection unavailable'); - } - - $imap = rcmail::get_instance()->get_storage(); - - $folders = $imap->list_folders(); - if (!in_array($mailbox, $folders)) { - $imap->create_folder($mailbox, true); - } - else if ($empty) { - $imap->delete_message('*', $mailbox); - } - } -} - -// @TODO: make sure mailbox has some content (always the same) or is empty -// @TODO: plugins: enable all? - -/** - * Base class for all tests in this directory - */ -class Selenium_Test extends PHPUnit_Extensions_Selenium2TestCase -{ - protected $login_data = null; - - protected function setUp() - { - $this->setBrowser(TESTS_BROWSER); - $this->login_data = array(TESTS_USER, TESTS_PASS); - - // Set root to our index.html, for better performance - // See https://github.com/sebastianbergmann/phpunit-selenium/issues/217 - $baseurl = preg_replace('!/index(-.+)?\.php^!', '', TESTS_URL); - $this->setBrowserUrl($baseurl . '/tests/Selenium'); - } - - protected function login($username = null, $password = null) - { - if (!empty($username)) { - $this->login_data = array($username, $password); - } - - $this->go('mail', null, true); - } - - protected function do_login() - { - $user_input = $this->byCssSelector('form input[name="_user"]'); - $pass_input = $this->byCssSelector('form input[name="_pass"]'); - $submit = $this->byCssSelector('form input[type="submit"]'); - - $user_input->value($this->login_data[0]); - $pass_input->value($this->login_data[1]); - - // submit login form - $submit->click(); - - // wait after successful login - sleep(TESTS_SLEEP); - } - - protected function go($task = 'mail', $action = null, $login = true) - { - $this->url(TESTS_URL . '?_task=' . $task); - - // wait for interface load (initial ajax requests, etc.) - sleep(TESTS_SLEEP); - - // check if we have a valid session - $env = $this->get_env(); - if ($login && $env['task'] == 'login') { - $this->do_login(); - } - - if ($action) { - $this->click_button($action); - sleep(TESTS_SLEEP); - } - } - - protected function get_env() - { - return $this->execute(array( - 'script' => 'return window.rcmail ? rcmail.env : {};', - 'args' => array(), - )); - } - - protected function get_buttons($action) - { - $buttons = $this->execute(array( - 'script' => "return rcmail.buttons['$action'];", - 'args' => array(), - )); - - if (is_array($buttons)) { - foreach ($buttons as $idx => $button) { - $buttons[$idx] = $button['id']; - } - } - - return (array) $buttons; - } - - protected function get_objects() - { - return $this->execute(array( - 'script' => "var i,r = []; for (i in rcmail.gui_objects) r.push(i); return r;", - 'args' => array(), - )); - } - - protected function click_button($action) - { - $buttons = $this->get_buttons($action); - $id = array_shift($buttons); - - // this doesn't work for me - $this->byId($id)->click(); - } - - protected function ajaxResponse($action, $script = '', $button = false) - { - if (!$script && !$button) { - $script = "rcmail.command('$action')"; - } - - $script = - "if (!window.test_ajax_response) { - window.test_ajax_response_object = {}; - function test_ajax_response(response) - { - if (response.response && response.response.action) { - window.test_ajax_response_object[response.response.action] = response.response; - } - } - rcmail.addEventListener('responsebefore', test_ajax_response); - } - window.test_ajax_response_object['$action'] = null; - $script; - "; - - // run request - $this->execute(array( - 'script' => $script, - 'args' => array(), - )); - - if ($button) { - $this->click_button($action); - } - - // wait - sleep(TESTS_SLEEP); - - // get response - $response = $this->execute(array( - 'script' => "return window.test_ajax_response_object ? test_ajax_response_object['$action'] : {};", - 'args' => array(), - )); - - return $response; - } - - protected function getText($element) - { - return $element->text() ?: $element->attribute('textContent'); - } - - protected function assertHasClass($classname, $element) - { - $this->assertContains($classname, $element->attribute('class')); - } -} diff --git a/tests/Selenium/index.html b/tests/Selenium/index.html deleted file mode 100644 index 7aa65f829..000000000 --- a/tests/Selenium/index.html +++ /dev/null @@ -1,8 +0,0 @@ - - - Roundcube Webmail Tests - - -Testing... - -