Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2534453
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
28 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/tests/Browser/Components/App.php b/tests/Browser/Components/App.php
index 7b863ab28..87373b050 100644
--- a/tests/Browser/Components/App.php
+++ b/tests/Browser/Components/App.php
@@ -1,119 +1,119 @@
<?php
namespace Tests\Browser\Components;
use Laravel\Dusk\Component;
use PHPUnit\Framework\Assert;
use Tests\Browser\Browser;
class App extends Component
{
/**
* Get the root selector for the component.
*
* @return string
*/
public function selector()
{
return '';
}
/**
* Assert that the browser page contains the component.
*
* @param Browser $browser
*
* @return void
*/
public function assert($browser)
{
// Assume the app (window.rcmail) is always available
// we can't assert that before we visit the page
// i.e. you will not be able to use gotoAction()
// method if you try to assert that fact.
}
/**
* Get the element shortcuts for the component.
*
* @return array
*/
public function elements()
{
return [
];
}
/**
* Assert value of rcmail.env entry
*
* @param Browser $browser Browser object
* @param array|string $key Env key name or array of key->expected pairs
* @param mixed $expected Expected value when $key is a string
*/
public function assertEnv($browser, $key, $expected = null)
{
if (is_array($key)) {
foreach ($key as $name => $expected) {
Assert::assertEquals($expected, $this->getEnv($browser, $name));
}
}
else {
Assert::assertEquals($expected, $this->getEnv($browser, $key));
}
}
/**
* Assert existence of defined gui_objects
*/
public function assertObjects($browser, array $names)
{
$objects = $this->getObjects($browser);
foreach ($names as $object_name) {
Assert::assertContains($object_name, $objects);
}
}
/**
* Return rcmail.env entry
*/
public function getEnv($browser, $key)
{
return $browser->driver->executeScript("return rcmail.env['$key']");
}
/**
* Return names of defined gui_objects
*/
public function getObjects($browser)
{
return (array) $browser->driver->executeScript("var i, r = []; for (i in rcmail.gui_objects) r.push(i); return r");
}
/**
* Visit specified task/action with logon if needed
*/
public function gotoAction($browser, $task = 'mail', $action = null, $login = true)
{
- $browser->visit("/?_task={$task}&_action={$action}");
+ $browser->visit("?_task={$task}&_action={$action}");
// check if we have a valid session
if ($login && $this->getEnv($browser, 'task') == 'login') {
$this->doLogin($browser);
}
}
/**
* Log in the test user
*/
protected function doLogin($browser)
{
$browser->type('_user', TESTS_USER);
$browser->type('_pass', TESTS_PASS);
$browser->click('button[type="submit"]');
// wait after successful login
$browser->waitUntil('!rcmail.busy');
}
}
diff --git a/tests/Browser/README.md b/tests/Browser/README.md
index 036c0e27b..47ff66fad 100644
--- a/tests/Browser/README.md
+++ b/tests/Browser/README.md
@@ -1,69 +1,72 @@
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:
0. Note that the suite has been tested with PHP 7.3 and 7.4.
1. Install Laravel Dusk with all dependencies.
```
composer require "laravel/dusk:~5.7.0"
```
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.
+NOTE: Do not use devel_mode=true (i.e. you should build Elastic styles),
+it makes chrome-driver to behave weird, timing out when using iframes (which we do a lot).
+
NOTE: See `.ci` directory for sample config and scripts we use for in-browser
tests on Travis.
EXECUTING THE TESTS
-------------------
To run the test suite call `phpunit` from the tests/Browser directory:
```
cd <roundcube-dir>/tests/Browser
TESTS_MODE=desktop phpunit
TESTS_MODE=phone phpunit
TESTS_MODE=tablet phpunit
```
[laravel-dusk]: https://github.com/laravel/dusk
diff --git a/tests/Browser/Settings/IdentitiesTest.php b/tests/Browser/Settings/IdentitiesTest.php
index c07e71008..2a251de9d 100644
--- a/tests/Browser/Settings/IdentitiesTest.php
+++ b/tests/Browser/Settings/IdentitiesTest.php
@@ -1,35 +1,263 @@
<?php
namespace Tests\Browser\Settings;
use Tests\Browser\Components\App;
+use Tests\Browser\Components\Dialog;
+use Tests\Browser\Components\Popupmenu;
class IdentitiesTest extends \Tests\Browser\TestCase
{
+ public static function setUpBeforeClass()
+ {
+ \bootstrap::init_db();
+ }
+
public function testIdentities()
{
$this->browse(function ($browser) {
$browser->go('settings', 'identities');
// check task and action
$browser->with(new App(), function ($browser) {
$browser->assertEnv('task', 'settings');
$browser->assertEnv('action', 'identities');
// these objects should be there always
$browser->assertObjects(['identitieslist']);
});
if ($browser->isDesktop()) {
$browser->assertVisible('#settings-menu li.identities.selected');
}
// Identities list
$browser->assertVisible('#identities-table tr:first-child.focused');
$browser->assertSeeIn('#identities-table tr:first-child td.mail', TESTS_USER);
// Toolbar menu
$browser->assertToolbarMenu(['create'], ['delete']);
});
}
+
+ /**
+ * Test identity creation
+ */
+ public function testIdentityCreate()
+ {
+ $this->browse(function ($browser) {
+ $browser->go('settings', 'identities');
+
+ if ($browser->isPhone()) {
+ $browser->assertVisible('.floating-action-buttons a.create:not(.disabled)')
+ ->click('.floating-action-buttons a.create')
+ ->waitFor('#preferences-frame');
+ }
+ else {
+ $browser->clickToolbarMenuItem('create');
+ }
+
+ $browser->withinFrame('#preferences-frame', function($browser) {
+ $browser->waitFor('form')
+ ->with('form fieldset:nth-of-type(1)', function ($browser) {
+ $browser->assertSeeIn('legend', 'Settings')
+ ->assertVisible('input[name=_name]')
+ ->assertValue('input[name=_name]', '')
+ ->assertSeeIn('label[for=rcmfd_name]', 'Display Name')
+ ->assertVisible('input[name=_email]')
+ ->assertValue('input[name=_email]', '')
+ ->assertSeeIn('label[for=rcmfd_email]', 'Email')
+ ->assertVisible('input[name=_organization]')
+ ->assertValue('input[name=_organization]', '')
+ ->assertSeeIn('label[for=rcmfd_organization]', 'Organization')
+ ->assertVisible('input[name=_reply-to]')
+ ->assertValue('input[name=_reply-to]', '')
+ ->assertSeeIn('label[for=rcmfd_reply-to]', 'Reply-To')
+ ->assertVisible('input[name=_bcc]')
+ ->assertValue('input[name=_bcc]', '')
+ ->assertSeeIn('label[for=rcmfd_bcc]', 'Bcc')
+ ->assertCheckboxState('input[name=_standard]', false)
+ ->assertSeeIn('label[for=rcmfd_standard]', 'Set default');
+ })
+ ->with('form fieldset:nth-of-type(2)', function ($browser) {
+ $browser->assertSeeIn('legend', 'Signature')
+ ->assertVisible('textarea[name=_signature]')
+ ->assertValue('textarea[name=_signature]', '');
+ })
+ ->type('_name', 'My Test')
+ ->type('_email', 'mynew@identity.com')
+ ->type('_organization', 'My Organization')
+ ->type('_reply-to', 'replyto@domain.tld')
+ ->type('_bcc', 'bcc@domain.tld')
+ ->setCheckboxState('input[name=_standard]', true)
+ ->type('_signature', 'My signature');
+
+ if (!$browser->isPhone()) {
+ $browser->click('.formbuttons button.submit');
+ }
+ });
+
+ if ($browser->isPhone()) {
+ $browser->assertVisible('#layout-content .header a.back-list-button')
+ ->whenAvailable('#layout-content .footer .buttons', function ($browser) {
+ $browser->click('a.button.submit');
+ });
+ }
+
+ $browser->waitForMessage('confirmation', 'Successfully saved.')
+ ->closeMessage('confirmation')
+ ->waitFor('#preferences-frame');
+
+ $browser->withinFrame('#preferences-frame', function($browser) {
+ $browser->whenAvailable('form', function ($browser) {
+ $browser->assertValue('input[name=_name]', 'My Test')
+ ->assertValue('input[name=_email]', 'mynew@identity.com')
+ ->assertValue('input[name=_organization]', 'My Organization')
+ ->assertValue('input[name=_reply-to]', 'replyto@domain.tld')
+ ->assertValue('input[name=_bcc]', 'bcc@domain.tld')
+ ->assertValue('textarea[name=_signature]', 'My signature')
+ ->assertCheckboxState('input[name=_standard]', true);
+ });
+ });
+
+ // Toolbar menu (Delete button is active now)
+ $browser->assertToolbarMenu(['create', 'delete']);
+
+ if ($browser->isPhone()) {
+ $browser->click('#layout-content .header a.back-list-button')
+ ->waitFor('#identities-table');
+ }
+
+ // Identities list
+ $browser->with('#identities-table', function ($browser) {
+ $browser->assertElementsCount('tbody tr', 2)
+ ->assertSeeIn('tbody tr:nth-child(2)', 'My Test');
+ });
+ });
+ }
+
+ /**
+ * Test identity deletion
+ *
+ * @depends testIdentityCreate
+ */
+ public function testIdentityDelete()
+ {
+ $this->browse(function ($browser) {
+ $browser->click('#identities-table tbody tr:first-child')
+ ->waitFor('#preferences-frame')
+ ->clickToolbarMenuItem('delete');
+
+ $browser->with(new Dialog(), function ($browser) {
+ $browser->assertDialogTitle('Are you sure...')
+ ->assertDialogContent('Do you really want to delete this identity?')
+ ->assertButton('mainaction.delete', 'Delete')
+ ->assertButton('cancel', 'Cancel')
+ ->clickButton('mainaction.delete');
+ });
+
+ $browser->waitForMessage('confirmation', 'Successfully deleted.')
+ ->closeMessage('confirmation');
+
+ // Preview frame should reset to the watermark page
+ $browser->withinFrame('#preferences-frame', function($browser) {
+ $browser->waitUntilMissing('> div');
+ });
+
+ $browser->waitFor('#layout-list')
+ ->assertElementsCount('#identities-table tbody tr', 1);
+
+ // Toolbar menu (Delete button is inactive again)
+ $browser->assertToolbarMenu(['create'], ['delete']);
+ });
+ }
+
+ /**
+ * Test identity update
+ *
+ * @depends testIdentityDelete
+ */
+ public function testIdentityUpdate()
+ {
+ $this->browse(function ($browser) {
+ $browser->click('#identities-table tbody tr:last-child')
+ ->waitFor('#preferences-frame')
+ ->waitUntilMissing('#messagestack div.loading');
+
+ $browser->withinFrame('#preferences-frame', function($browser) {
+ $browser->whenAvailable('form', function ($browser) {
+ $browser->type('[name=_name]', 'Default')
+ ->type('[name=_organization]', 'Default Org');
+ });
+
+ if (!$browser->isPhone()) {
+ $browser->click('.formbuttons button.submit');
+ }
+ });
+
+ if ($browser->isPhone()) {
+ $browser->whenAvailable('#layout-content .footer', function ($browser) {
+ $browser->assertVisible('a.button.prev.disabled')
+ ->assertVisible('a.button.next.disabled')
+ ->click('a.button.submit');
+ });
+ }
+
+ $browser->waitForMessage('confirmation', 'Successfully saved.')
+ ->closeMessage('confirmation')
+ ->waitFor('#preferences-frame');
+
+ if ($browser->isPhone()) {
+ $browser->click('#layout-content .header a.back-list-button');
+ }
+
+ $browser->whenAvailable('#identities-table', function ($browser) {
+ $browser->assertSeeIn('tbody tr:last-child', 'Default <mynew@identity.com>');
+ });
+ });
+ }
+
+ /**
+ * Test identities in mail composer
+ *
+ * @depends testIdentityUpdate
+ */
+ public function testIdentitiesInComposer()
+ {
+ // Add one more identity
+ (new \rcube_user(1))->insert_identity([
+ 'email' => 'another@domain.tld',
+ ]);
+
+ $this->browse(function ($browser) {
+ if ($browser->isPhone()) {
+ $browser->click('a.back-sidebar-button');
+ }
+
+ // Goto Compose and test the identity selector
+ $browser->clickTaskMenuItem('compose')
+ ->waitFor('#compose-content')
+ ->assertElementsCount('select[name=_from] > option', 2)
+ ->assertSeeIn('select[name=_from] > option[selected]', 'mynew@identity.com');
+
+ $this->assertTrue(trim($browser->value('#_bcc'), ', ') === 'bcc@domain.tld');
+ $this->assertTrue(trim($browser->value('#_replyto'), ', ') === 'replyto@domain.tld');
+ $this->assertTrue(strpos($browser->value('#composebody'), 'My signature') !== false);
+
+ // TODO: Recipient input, HTML mode, identity change
+
+ // Test "unsaved changes" dialog
+ $browser->type('#compose-subject', 'subject')
+ ->click('#compose_from a.edit')
+ ->with(new Dialog(), function ($browser) {
+ $browser->assertDialogTitle('Are you sure...')
+ ->assertDialogContent('The message has not been sent and has unsaved changes. Do you want to discard your changes?')
+ ->assertButton('mainaction.discard', 'Discard')
+ ->assertButton('cancel', 'Cancel')
+ ->clickButton('discard');
+ });
+
+ $browser->waitFor('#identities-table');
+ });
+ }
}
diff --git a/tests/Browser/Settings/ResponsesTest.php b/tests/Browser/Settings/ResponsesTest.php
index 32f564793..c82dcf987 100644
--- a/tests/Browser/Settings/ResponsesTest.php
+++ b/tests/Browser/Settings/ResponsesTest.php
@@ -1,262 +1,262 @@
<?php
namespace Tests\Browser\Settings;
use Tests\Browser\Components\App;
use Tests\Browser\Components\Dialog;
use Tests\Browser\Components\Popupmenu;
class ResponsesTest extends \Tests\Browser\TestCase
{
public static function setUpBeforeClass()
{
\bootstrap::init_db();
}
public function testResponses()
{
$this->browse(function ($browser) {
$browser->go('settings', 'responses');
$browser->with(new App(), function ($browser) {
// check task and action
$browser->assertEnv('task', 'settings');
$browser->assertEnv('action', 'responses');
// these objects should be there always
$browser->assertObjects(['responseslist']);
});
if ($browser->isDesktop()) {
$browser->assertVisible('#settings-menu li.responses.selected');
}
// Responses list
$browser->assertPresent('#responses-table')
->assertMissing('#responses-table tr');
// Toolbar menu
$browser->assertToolbarMenu(['create'], ['delete']);
});
}
/**
* Test response creation
*/
public function testResponseCreate()
{
$this->browse(function ($browser) {
$browser->go('settings', 'responses');
if ($browser->isPhone()) {
$browser->assertVisible('.floating-action-buttons a.create:not(.disabled)')
->click('.floating-action-buttons a.create')
->waitFor('#preferences-frame');
}
else {
$browser->clickToolbarMenuItem('create');
}
$browser->withinFrame('#preferences-frame', function($browser) {
$browser->waitFor('form')
->with('form', function ($browser) {
$browser->assertVisible('input[name=_name]')
->assertValue('input[name=_name]', '')
->assertSeeIn('label[for=ffname]', 'Name')
->assertVisible('textarea[name=_text]')
->assertValue('textarea[name=_text]', '')
->assertSeeIn('label[for=fftext]', 'Response Text');
})
->type('_name', 'Test')
->type('_text', 'Response Body');
if (!$browser->isPhone()) {
$browser->click('.formbuttons button.submit');
}
});
if ($browser->isPhone()) {
$browser->assertVisible('#layout-content .header a.back-list-button')
->waitFor('#layout-content .footer .buttons')
->click('#layout-content .footer .buttons a.button.submit');
}
$browser->waitForMessage('confirmation', 'Successfully saved.')
->closeMessage('confirmation')
->waitFor('#preferences-frame');
$browser->withinFrame('#preferences-frame', function($browser) {
$browser->waitFor('form')
->with('form', function ($browser) {
$browser->assertVisible('input[name=_name]')
->assertValue('input[name=_name]', 'Test')
->assertValue('textarea[name=_text]', 'Response Body');
});
});
if ($browser->isPhone()) {
$browser->click('#layout-content .header a.back-list-button')
->waitFor('#responses-table');
}
// Responses list
$browser->with('#responses-table', function ($browser) {
$browser->assertElementsCount('tbody tr', 1)
->assertSeeIn('tbody tr:nth-child(1)', 'Test');
});
if ($browser->isPhone()) {
$browser->click('#responses-table tbody tr:first-child')
->waitFor('#preferences-frame');
}
// Toolbar menu (Delete button is active now)
$browser->assertToolbarMenu(['create', 'delete']);
});
}
/**
* Test response deletion
*
* @depends testResponseCreate
*/
public function testResponseDelete()
{
$this->browse(function ($browser) {
$browser->clickToolbarMenuItem('delete');
$browser->with(new Dialog(), function ($browser) {
$browser->assertDialogTitle('Are you sure...')
->assertDialogContent('Do you really want to delete this response text?')
->assertButton('mainaction.delete', 'Delete')
->assertButton('cancel', 'Cancel')
->clickButton('mainaction.delete');
});
$browser->waitForMessage('confirmation', 'Successfully deleted.')
->closeMessage('confirmation');
// Preview frame should reset to the watermark page
$browser->withinFrame('#preferences-frame', function($browser) {
$browser->waitUntilMissing('> div');
});
$browser->waitFor('#layout-list')
->assertElementsCount('#responses-table tbody tr', 0);
// Toolbar menu (Delete button is inactive again)
$browser->assertToolbarMenu(['create'], ['delete']);
});
}
/**
* Test responses in mail composer
*
* @depends testResponseDelete
*/
public function testResponsesInComposer()
{
// Quickly create a set of responses
- $responses = array(
- array('name' => 'Test 1', 'text' => 'Response 1', 'format' => 'text', 'key' => substr(md5('Test 1'), 0, 16)),
- array('name' => 'Test 2', 'text' => 'Response 2', 'format' => 'text', 'key' => substr(md5('Test 2'), 0, 16)),
- );
+ $responses = [
+ ['name' => 'Test 1', 'text' => 'Response 1', 'format' => 'text', 'key' => substr(md5('Test 1'), 0, 16)],
+ ['name' => 'Test 2', 'text' => 'Response 2', 'format' => 'text', 'key' => substr(md5('Test 2'), 0, 16)],
+ ];
- (new \rcube_user(1))->save_prefs(array('compose_responses' => $responses));
+ (new \rcube_user(1))->save_prefs(['compose_responses' => $responses]);
$this->browse(function ($browser) {
if ($browser->isPhone()) {
$browser->click('a.back-sidebar-button');
}
// Goto Compose and test the responses menu
$browser->clickTaskMenuItem('compose')
->waitFor('#compose-content')
->clickToolbarMenuItem('responses')
->with(new Popupmenu('responses-menu'), function ($browser) {
$browser->assertMenuState(['create.responses', 'edit.responses'])
->with('#responseslist', function ($browser) {
$browser->assertElementsCount('li', 2)
->assertSeeIn('li:nth-child(1) a.insertresponse', 'Test 1')
->assertSeeIn('li:nth-child(2) a.insertresponse', 'Test 2');
})
->closeMenu();
});
// Insert a response to the message body
$browser->type('#composebody', 'Body and ')
->clickToolbarMenuItem('responses')
->waitFor('#responseslist')
->click('#responseslist li:nth-child(1) a.insertresponse')
->waitUntilMissing('#responses-menu')
->assertValue('#composebody', 'Body and Response 1')
->waitForMessage('confirmation', 'Response inserted successfully.')
->closeMessage('confirmation');
// TODO: Test HTML mode, test response creation
});
}
/**
* Test response update
*
* @depends testResponsesInComposer
*/
public function testResponseUpdate()
{
$this->browse(function ($browser) {
// We're in mail compose, use responses menu to goto Settings > Responses
$browser->clickToolbarMenuItem('responses')
->waitFor('#responses-menu')
->click('#responses-menu a.edit.responses')
->with(new Dialog(), function ($browser) {
$browser->assertDialogTitle('Are you sure...')
->assertDialogContent('The message has not been sent and has unsaved changes. Do you want to discard your changes?')
->assertButton('mainaction.discard', 'Discard')
->assertButton('cancel', 'Cancel')
->clickButton('discard');
});
$browser->waitFor('#responses-table')
->assertSeeIn('#responses-table tbody tr:first-child td', 'Test 1')
->click('#responses-table tbody tr:first-child')
->waitFor('#preferences-frame');
$browser->withinFrame('#preferences-frame', function($browser) {
$browser->waitFor('form')
->with('form', function ($browser) {
$browser->assertValue('[name=_name]', 'Test 1')
->assertValue('[name=_text]', 'Response 1')
->type('[name=_name]', 'Test 11')
->type('[name=_text]', 'Response 11');
});
if (!$browser->isPhone()) {
$browser->click('.formbuttons button.submit');
}
});
if ($browser->isPhone()) {
$browser->waitFor('#layout-content .footer')
->with('#layout-content .footer', function ($browser) {
$browser->assertVisible('a.button.prev.disabled')
->assertVisible('a.button.next:not(.disabled)')
->click('a.button.submit');
});
}
$browser->waitForMessage('confirmation', 'Successfully saved.')
->closeMessage('confirmation');
if ($browser->isPhone()) {
$browser->click('#layout-content .header a.back-list-button')
->waitFor('#responses-table');
}
// Responses list
$browser->with('#responses-table', function ($browser) {
$browser->assertSeeIn('tbody tr:nth-child(1)', 'Test 11');
});
});
}
}
diff --git a/tests/Browser/phpunit.xml b/tests/Browser/phpunit.xml
index 2c5cdcf71..453492bfa 100644
--- a/tests/Browser/phpunit.xml
+++ b/tests/Browser/phpunit.xml
@@ -1,19 +1,18 @@
<phpunit backupGlobals="false"
bootstrap="bootstrap.php"
colors="true">
<testsuites>
<testsuite name="Logon">
<directory suffix="Test.php">Logon</directory>
</testsuite>
<testsuite name="Contacts">
<directory suffix="Test.php">Contacts</directory>
</testsuite>
<testsuite name="Settings">
<directory suffix="Test.php">Settings</directory>
- <directory suffix="Test.php">Settings/Preferences</directory>
</testsuite>
<testsuite name="Mail">
<directory suffix="Test.php">Mail</directory>
</testsuite>
</testsuites>
</phpunit>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Feb 6, 1:16 PM (29 m, 27 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
428273
Default Alt Text
(28 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment