Rewrite Selenium tests with use of laravel/dusk
parent
47d9ed6d0c
commit
0b9ff6426e
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Addressbook;
|
||||||
|
|
||||||
|
class Addressbook extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
public function testAddressbook()
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Addressbook;
|
||||||
|
|
||||||
|
class Import extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
public function testImport()
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,190 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Facebook\WebDriver\Chrome\ChromeOptions;
|
||||||
|
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||||
|
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Laravel\Dusk\Chrome\SupportsChrome;
|
||||||
|
use Laravel\Dusk\Concerns\ProvidesBrowser;
|
||||||
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
abstract class DuskTestCase extends TestCase
|
||||||
|
{
|
||||||
|
use ProvidesBrowser,
|
||||||
|
SupportsChrome;
|
||||||
|
|
||||||
|
protected $app;
|
||||||
|
protected static $phpProcess;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare for Dusk test execution.
|
||||||
|
*
|
||||||
|
* @beforeClass
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function prepare()
|
||||||
|
{
|
||||||
|
static::startWebServer();
|
||||||
|
static::startChromeDriver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the RemoteWebDriver instance.
|
||||||
|
*
|
||||||
|
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
|
||||||
|
*/
|
||||||
|
protected function driver()
|
||||||
|
{
|
||||||
|
$options = (new ChromeOptions())->addArguments([
|
||||||
|
'--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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
class Login extends DuskTestCase
|
||||||
|
{
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
\bootstrap::init_db();
|
||||||
|
\bootstrap::init_imap();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLogin()
|
||||||
|
{
|
||||||
|
// first test, we're already on the login page
|
||||||
|
$this->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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
class Logout extends DuskTestCase
|
||||||
|
{
|
||||||
|
public function testLogout()
|
||||||
|
{
|
||||||
|
$this->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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Mail;
|
||||||
|
|
||||||
|
class CheckRecent extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
public function testCheckRecent()
|
||||||
|
{
|
||||||
|
$this->browse(function ($browser) {
|
||||||
|
$this->go('mail');
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Mail;
|
||||||
|
|
||||||
|
class Compose extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
public function testCompose()
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Mail;
|
||||||
|
|
||||||
|
class Getunread extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
protected $msgcount = 0;
|
||||||
|
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
\bootstrap::init_imap();
|
||||||
|
\bootstrap::purge_mailbox('INBOX');
|
||||||
|
|
||||||
|
// import email messages
|
||||||
|
foreach (glob(TESTS_DIR . 'data/mail/list_*.eml') as $f) {
|
||||||
|
\bootstrap::import_message($f, 'INBOX');
|
||||||
|
$this->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'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Mail;
|
||||||
|
|
||||||
|
class MailList extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
protected function setUp()
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
\bootstrap::init_imap();
|
||||||
|
\bootstrap::purge_mailbox('INBOX');
|
||||||
|
|
||||||
|
// import email messages
|
||||||
|
foreach (glob(TESTS_DIR . 'data/mail/list_00.eml') as $f) {
|
||||||
|
\bootstrap::import_message($f, 'INBOX');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testList()
|
||||||
|
{
|
||||||
|
$this->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');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Mail;
|
||||||
|
|
||||||
|
class Mail extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
public function testMail()
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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'] = '<test-account-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 <roundcube-dir>/tests/Browser
|
||||||
|
phpunit # or ../../vendor/bin/phpunit
|
||||||
|
```
|
||||||
|
|
||||||
|
[laravel-dusk]: https://github.com/laravel/dusk
|
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Settings;
|
||||||
|
|
||||||
|
class About extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
public function testAbout()
|
||||||
|
{
|
||||||
|
$this->browse(function ($browser) {
|
||||||
|
$this->go('settings', 'about');
|
||||||
|
|
||||||
|
// check task and action
|
||||||
|
$this->assertEnvEquals('task', 'settings');
|
||||||
|
$this->assertEnvEquals('action', 'about');
|
||||||
|
|
||||||
|
$browser->assertVisible('#pluginlist');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Settings;
|
||||||
|
|
||||||
|
class Folders extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
public function testFolders()
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Settings;
|
||||||
|
|
||||||
|
class Identities extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
public function testIdentities()
|
||||||
|
{
|
||||||
|
$this->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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Settings;
|
||||||
|
|
||||||
|
class Settings extends \Tests\Browser\DuskTestCase
|
||||||
|
{
|
||||||
|
public function testSettings()
|
||||||
|
{
|
||||||
|
$this->browse(function ($browser) {
|
||||||
|
$this->go('settings');
|
||||||
|
|
||||||
|
// task should be set to 'settings'
|
||||||
|
$this->assertEnvEquals('task', 'settings');
|
||||||
|
|
||||||
|
$objects = $this->getObjects();
|
||||||
|
|
||||||
|
$this->assertContains('sectionslist', $objects);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,174 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
+-----------------------------------------------------------------------+
|
||||||
|
| This file is part of the Roundcube Webmail client |
|
||||||
|
| |
|
||||||
|
| Copyright (C) The Roundcube Dev Team |
|
||||||
|
| |
|
||||||
|
| Licensed under the GNU General Public License version 3 or |
|
||||||
|
| any later version with exceptions for skins & plugins. |
|
||||||
|
| See the README file for a full license statement. |
|
||||||
|
| |
|
||||||
|
| PURPOSE: |
|
||||||
|
| Environment initialization script for functional tests |
|
||||||
|
+-----------------------------------------------------------------------+
|
||||||
|
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
||||||
|
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||||
|
+-----------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
+-----------------------------------------------------------------------+
|
||||||
|
| This file is part of the Roundcube Webmail client |
|
||||||
|
| |
|
||||||
|
| Copyright (C) The Roundcube Dev Team |
|
||||||
|
| |
|
||||||
|
| Licensed under the GNU General Public License version 3 or |
|
||||||
|
| any later version with exceptions for skins & plugins. |
|
||||||
|
| See the README file for a full license statement. |
|
||||||
|
| |
|
||||||
|
| PURPOSE: |
|
||||||
|
| Chrome WebDriver download tool |
|
||||||
|
+-----------------------------------------------------------------------+
|
||||||
|
| Author: Aleksander Machniak <alec@alec.pl> |
|
||||||
|
+-----------------------------------------------------------------------+
|
||||||
|
*/
|
||||||
|
|
||||||
|
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]);
|
@ -1,21 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Addressbook_Addressbook extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testAddressbook()
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Addressbook_Import extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testImport()
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Login extends Selenium_Test
|
|
||||||
{
|
|
||||||
protected function setUp()
|
|
||||||
{
|
|
||||||
bootstrap::init_db();
|
|
||||||
bootstrap::init_imap();
|
|
||||||
parent::setUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testLogin()
|
|
||||||
{
|
|
||||||
// first test, we're already on the login page
|
|
||||||
$this->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']);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Logout extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testLogout()
|
|
||||||
{
|
|
||||||
$this->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"]');
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Mail_CheckRecent extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testCheckRecent()
|
|
||||||
{
|
|
||||||
$this->go('mail');
|
|
||||||
|
|
||||||
$res = $this->ajaxResponse('check-recent', "rcmail.command('checkmail')");
|
|
||||||
|
|
||||||
$this->assertEquals('check-recent', $res['action']);
|
|
||||||
$this->assertRegExp('/this\.set_unread_count/', $res['exec']);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Mail_Compose extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testCompose()
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Mail_Getunread extends Selenium_Test
|
|
||||||
{
|
|
||||||
protected $msgcount = 0;
|
|
||||||
|
|
||||||
protected function setUp()
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
bootstrap::init_imap();
|
|
||||||
bootstrap::purge_mailbox('INBOX');
|
|
||||||
|
|
||||||
// import email messages
|
|
||||||
foreach (glob(TESTS_DIR . 'Selenium/data/mail/list_*.eml') as $f) {
|
|
||||||
bootstrap::import_message($f, 'INBOX');
|
|
||||||
$this->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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Mail_List extends Selenium_Test
|
|
||||||
{
|
|
||||||
protected function setUp()
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
bootstrap::init_imap();
|
|
||||||
bootstrap::purge_mailbox('INBOX');
|
|
||||||
|
|
||||||
// import email messages
|
|
||||||
foreach (glob(TESTS_DIR . 'Selenium/data/mail/list_00.eml') as $f) {
|
|
||||||
bootstrap::import_message($f, 'INBOX');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testList()
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Mail_Mail extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testMail()
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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'] = '<test-account-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 <roundcube-dir>/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/
|
|
@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Settings_About extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testAbout()
|
|
||||||
{
|
|
||||||
$this->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']);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Settings_Folders extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testFolders()
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Settings_Identities extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testIdentities()
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
class Selenium_Settings_Settings extends Selenium_Test
|
|
||||||
{
|
|
||||||
public function testSettings()
|
|
||||||
{
|
|
||||||
$this->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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,348 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
+-----------------------------------------------------------------------+
|
|
||||||
| This file is part of the Roundcube Webmail client |
|
|
||||||
| |
|
|
||||||
| Copyright (C) The Roundcube Dev Team |
|
|
||||||
| |
|
|
||||||
| Licensed under the GNU General Public License version 3 or |
|
|
||||||
| any later version with exceptions for skins & plugins. |
|
|
||||||
| See the README file for a full license statement. |
|
|
||||||
| |
|
|
||||||
| PURPOSE: |
|
|
||||||
| Environment initialization script for unit tests |
|
|
||||||
+-----------------------------------------------------------------------+
|
|
||||||
| Author: Thomas Bruederli <roundcube@gmail.com> |
|
|
||||||
| Author: Aleksander Machniak <alec@alec.pl> |
|
|
||||||
+-----------------------------------------------------------------------+
|
|
||||||
*/
|
|
||||||
|
|
||||||
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'));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Roundcube Webmail Tests</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
Testing...
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in New Issue