Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2571748
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
22 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/tests/Browser/Contacts/Export.php b/tests/Browser/Contacts/Export.php
new file mode 100644
index 000000000..5d757df2d
--- /dev/null
+++ b/tests/Browser/Contacts/Export.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Tests\Browser\Contacts;
+
+class Export extends \Tests\Browser\DuskTestCase
+{
+ /**
+ * Test exporting all contacts
+ */
+ public function testExportAll()
+ {
+ \bootstrap::init_db();
+
+ $this->browse(function ($browser) {
+ $this->go('addressbook');
+
+ $this->clickToolbarMenuItem('export');
+ });
+
+ // Parse the downloaded vCard file
+ $vcard_content = $this->readDownloadedFile('contacts.vcf');
+ $vcard = new \rcube_vcard();
+ $contacts = $vcard->import($vcard_content);
+
+ $this->assertCount(2, $contacts);
+ $this->assertSame('John Doe', $contacts[0]->displayname);
+ $this->assertSame('Jane Stalone', $contacts[1]->displayname);
+ $this->removeDownloadedFile('contacts.vcf');
+ }
+
+ /**
+ * Test exporting selected contacts
+ *
+ * @depends testExportAll
+ */
+ public function testExportSelected()
+ {
+ $this->ctrlClick('#contacts-table tbody tr:first-child');
+ $this->clickToolbarMenuItem('export', 'export.select');
+
+ $vcard_content = $this->readDownloadedFile('contacts.vcf');
+ $vcard = new \rcube_vcard();
+ $contacts = $vcard->import($vcard_content);
+
+ // Parse the downloaded vCard file
+ $this->assertCount(1, $contacts);
+ $this->assertSame('John Doe', $contacts[0]->displayname);
+ $this->removeDownloadedFile('contacts.vcf');
+ }
+}
diff --git a/tests/Browser/Contacts/Import.php b/tests/Browser/Contacts/Import.php
index 50514f23b..fd04df0cd 100644
--- a/tests/Browser/Contacts/Import.php
+++ b/tests/Browser/Contacts/Import.php
@@ -1,103 +1,105 @@
<?php
namespace Tests\Browser\Contacts;
class Import extends \Tests\Browser\DuskTestCase
{
/**
* Test basic elements of contacts import UI
*/
public function testImportUI()
{
+ \bootstrap::init_db();
+
$this->browse(function ($browser) {
$this->go('addressbook');
$this->clickToolbarMenuItem('import');
$browser->assertSeeIn('.ui-dialog-title', 'Import contacts');
$browser->assertVisible('.ui-dialog button.mainaction.import');
$browser->assertVisible('.ui-dialog button.cancel');
$browser->withinFrame('.ui-dialog iframe', function ($browser) {
// check task and action
$this->assertEnvEquals('task', 'addressbook');
$this->assertEnvEquals('action', 'import');
$objects = $this->getObjects();
// these objects should be there always
$this->assertContains('importform', $objects);
$browser->assertSee('You can upload');
$browser->assertVisible('#rcmImportForm');
$browser->assertVisible('#rcmImportForm select');
$browser->assertVisible('#rcmImportForm .custom-switch');
// FIXME: selecting the file input directly does not work
$browser->assertVisible('#rcmImportForm .custom-file');
$browser->assertSelected('#rcmImportForm select', 0);
});
// Close the dialog
$browser->click('.ui-dialog button.cancel');
$browser->assertMissing('.ui-dialog');
});
}
/**
* Import contacts from a vCard file
*
* @depends testImportUI
*/
public function testImportProcess()
{
$this->browse(function ($browser) {
// Open the dialog again
$this->clickToolbarMenuItem('import');
$browser->assertSeeIn('.ui-dialog-title', 'Import contacts');
// Submit the form with no file attached
$browser->click('.ui-dialog button.mainaction');
$browser->waitForText('Attention');
$browser->assertSee('Please select a file');
$browser->driver->getKeyboard()->sendKeys(\Facebook\WebDriver\WebDriverKeys::ESCAPE);
$this->assertCount(1, $browser->elements('.ui-dialog'));
$browser->withinFrame('.ui-dialog iframe', function ($browser) {
$browser->attach('.custom-file input', TESTS_DIR . 'data/contacts.vcf');
});
$browser->click('.ui-dialog button.mainaction');
$browser->withinFrame('.ui-dialog iframe', function ($browser) {
$browser->waitForText('Successfully imported 2 contacts:');
});
// Close the dialog
$browser->click('.ui-dialog button.cancel');
// Expected existing contacts + imported
$browser->waitFor('#contacts-table tr');
$this->assertCount(4, $browser->elements('#contacts-table tbody tr'));
$browser->assertSeeIn('#rcmcountdisplay', '1 – 4 of 4');
});
}
/**
* Test imported contact
*
* @depends testImportProcess
*/
public function testImportResult()
{
$this->browse(function ($browser) {
// Open the dialog again
$browser->click('#contacts-table tr:last-child');
$browser->withinFrame('#contact-frame', function ($browser) {
$browser->waitFor('a.email'); // wait for iframe to load
$browser->assertSeeIn('.names', 'Sylvester Stalone');
$browser->assertSeeIn('a.email', 's.stalone@rambo.tv');
});
});
}
}
diff --git a/tests/Browser/DuskTestCase.php b/tests/Browser/DuskTestCase.php
index 5398d455d..15fe07b95 100644
--- a/tests/Browser/DuskTestCase.php
+++ b/tests/Browser/DuskTestCase.php
@@ -1,422 +1,460 @@
<?php
namespace Tests\Browser;
use PHPUnit\Framework\TestCase;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Chrome\SupportsChrome;
use Laravel\Dusk\Concerns\ProvidesBrowser;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process;
abstract class DuskTestCase extends TestCase
{
use ProvidesBrowser,
SupportsChrome;
protected $app;
protected static $phpProcess;
/**
* Prepare for Dusk test execution.
*
* @beforeClass
* @return void
*/
public static function prepare()
{
static::startWebServer();
static::startChromeDriver();
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
$options = (new ChromeOptions())->addArguments([
'--lang=en_US',
'--disable-gpu',
'--headless',
]);
+ // For file download handling
+ $prefs = [
+ 'profile.default_content_settings.popups' => 0,
+ 'download.default_directory' => TESTS_DIR . 'downloads',
+ ];
+
+ $options->setExperimentalOption('prefs', $prefs);
+
if (getenv('TESTS_MODE') == 'phone') {
// Fake User-Agent string for mobile mode
$ua = 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Mobile Safari/537.36';
$options->setExperimentalOption('mobileEmulation', ['userAgent' => $ua]);
$options->addArguments(['--window-size=375,667']);
}
else if (getenv('TESTS_MODE') == 'tablet') {
// Fake User-Agent string for mobile mode
$ua = 'Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36';
$options->setExperimentalOption('mobileEmulation', ['userAgent' => $ua]);
$options->addArguments(['--window-size=1024,768']);
}
else {
$options->addArguments(['--window-size=1280,720']);
}
+ // Make sure downloads dir exists and is empty
+ if (!file_exists(TESTS_DIR . 'downloads')) {
+ mkdir(TESTS_DIR . 'downloads', 0777, true);
+ }
+ else {
+ foreach (glob(TESTS_DIR . 'downloads/*') as $file) {
+ @unlink($file);
+ }
+ }
+
return RemoteWebDriver::create(
'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY,
$options
)
);
}
/**
* Set up the test run
*
* @return void
*/
protected function setUp()
{
parent::setUp();
$this->app = \rcmail::get_instance();
Browser::$baseUrl = 'http://localhost:8000';
- Browser::$storeScreenshotsAt = __DIR__ . '/screenshots';
- Browser::$storeConsoleLogAt = __DIR__ . '/console';
+ Browser::$storeScreenshotsAt = TESTS_DIR . 'screenshots';
+ Browser::$storeConsoleLogAt = TESTS_DIR . 'console';
// This folder must exist in case Browser will try to write logs to it
if (!is_dir(Browser::$storeConsoleLogAt)) {
mkdir(Browser::$storeConsoleLogAt, 0777, true);
}
// Purge screenshots from the last test run
$pattern = sprintf('failure-%s_%s-*',
str_replace("\\", '_', get_class($this)),
$this->getName(false)
);
try {
$files = Finder::create()->files()->in(Browser::$storeScreenshotsAt)->name($pattern);
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
// ignore missing screenshots directory
}
// Purge console logs from the last test run
$pattern = sprintf('%s_%s-*',
str_replace("\\", '_', get_class($this)),
$this->getName(false)
);
try {
$files = Finder::create()->files()->in(Browser::$storeConsoleLogAt)->name($pattern);
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
// ignore missing screenshots directory
}
}
/**
* Check if in Phone mode
*/
public static function isPhone()
{
return getenv('TESTS_MODE') == 'phone';
}
/**
* Check if in Tablet mode
*/
public static function isTablet()
{
return getenv('TESTS_MODE') == 'tablet';
}
/**
* Check if in Desktop mode
*/
public static function isDesktop()
{
return !self::isPhone() && !self::isTablet();
}
/**
* Assert specified rcmail.env value
*/
protected function assertEnvEquals($key, $expected)
{
$this->assertEquals($expected, $this->getEnv($key));
}
/**
* Assert specified checkbox state
*/
protected function assertCheckboxState($selector, $state)
{
$this->browse(function (Browser $browser) use ($selector, $state) {
if ($state) {
$browser->assertChecked($selector);
}
else {
$browser->assertNotChecked($selector);
}
});
}
/**
* Assert Task menu state
*/
protected function assertTaskMenu($selected)
{
$this->browse(function (Browser $browser) use ($selected) {
// On phone the menu is invisible, open it
if ($this->isPhone()) {
$browser->click('.task-menu-button');
$browser->waitFor('#taskmenu');
}
$browser->with('#taskmenu', function(Browser $browser) use ($selected) {
$options = ['compose', 'mail', 'contacts', 'settings', 'about', 'logout'];
foreach ($options as $option) {
$browser->assertVisible("a.{$option}:not(.disabled)" . ($selected == $option ? ".selected" : ":not(.selected)"));
}
});
// hide the menu back
if ($this->isPhone()) {
$browser->click('.popover a.button.cancel');
$browser->waitUntilMissing('#taskmenu');
}
});
}
/**
* Assert toolbar menu state
*/
protected function assertToolbarMenu($active, $disabled)
{
$this->browse(function (Browser $browser) use ($active, $disabled) {
// On phone the menu is invisible, open it
if ($this->isPhone()) {
$browser->click('.toolbar-menu-button');
$browser->waitFor('#toolbar-menu');
}
$browser->with('#toolbar-menu', function(Browser $browser) use ($active, $disabled) {
foreach ($active as $option) {
// Print action is disabled on phones
if ($option == 'print' && $this->isPhone()) {
$browser->assertMissing("a.print");
}
else {
$browser->assertVisible("a.{$option}:not(.disabled)");
}
}
foreach ($disabled as $option) {
if ($option == 'print' && $this->isPhone()) {
$browser->assertMissing("a.print");
}
else {
$browser->assertVisible("a.{$option}.disabled");
}
}
});
$this->closeToolbarMenu();
});
}
/**
* Close toolbar menu (on phones)
*/
protected function closeToolbarMenu()
{
// hide the menu back
if ($this->isPhone()) {
$this->browse(function (Browser $browser) {
$browser->script("window.UI.menu_hide('toolbar-menu')");
$browser->waitUntilMissing('#toolbar-menu');
// FIXME: For some reason sometimes .popover-overlay does not close,
// we have to remove it manually
$browser->script(
"Array.from(document.getElementsByClassName('popover-overlay')).forEach(function(elem) { elem.parentNode.removeChild(elem); })"
);
});
}
}
/**
* Select taskmenu item
*/
protected function clickTaskMenuItem($name)
{
$this->browse(function (Browser $browser) use ($name) {
if ($this->isPhone()) {
$browser->click('.task-menu-button');
}
$browser->click("#taskmenu a.{$name}");
if ($this->isPhone()) {
$browser->waitUntilMissing('#taskmenu');
}
});
}
/**
* Select toolbar menu item
*/
- protected function clickToolbarMenuItem($name)
+ protected function clickToolbarMenuItem($name, $dropdown_action = null)
{
- $this->browse(function (Browser $browser) use ($name) {
+ $this->browse(function (Browser $browser) use ($name, $dropdown_action) {
if ($this->isPhone()) {
$browser->click('.toolbar-menu-button');
}
- $browser->click("#toolbar-menu a.{$name}");
+ $selector = "#toolbar-menu a.{$name}" . ($dropdown_action ? " + a.dropdown" : '');
+
+ $browser->click($selector);
+
+ if ($dropdown_action) {
+ $popup_id = $browser->attribute($selector, 'data-popup');
+ $browser->click("#{$popup_id} li a.{$dropdown_action}");
+ }
if ($this->isPhone()) {
$this->closeToolbarMenu();
-// $browser->waitUntilMissing('#toolbar-menu');
}
});
}
+ protected function ctrlClick($selector)
+ {
+ $this->browse(function (Browser $browser) use ($selector) {
+ $browser->driver->getKeyboard()->pressKey(\Facebook\WebDriver\WebDriverKeys::LEFT_CONTROL);
+ $browser->element('#contacts-table tbody tr:first-child')->click();
+ $browser->driver->getKeyboard()->releaseKey(\Facebook\WebDriver\WebDriverKeys::LEFT_CONTROL);
+ });
+ }
+
/**
* Get content of rcmail.env entry
*/
protected function getEnv($key)
{
$this->browse(function (Browser $browser) use ($key, &$result) {
$result = $browser->script("return rcmail.env['$key']");
$result = $result[0];
});
return $result;
}
- /**
- * 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();
}
});
}
/**
* Change state of the Elastic's pretty checkbox
*/
protected function setCheckboxState($selector, $state)
{
// Because you can't operate on the original checkbox directly
$this->browse(function (Browser $browser) use ($selector, $state) {
$browser->ensurejQueryIsAvailable();
if ($state) {
$run = "if (!element.prev().is(':checked')) element.click()";
}
else {
$run = "if (element.prev().is(':checked')) element.click()";
}
$browser->script(
"var element = jQuery('$selector')[0] || jQuery('input[name=$selector]')[0];"
."element = jQuery(element).next('.custom-control-label'); $run;"
);
});
}
/**
* Wait for UI (notice/confirmation/loading/error/warning) message
* and assert it's text
*/
protected function waitForMessage($type, $text)
{
$selector = '#messagestack > div.' . $type;
$this->browse(function ($browser) use ($selector, $text) {
$browser->waitFor($selector)->assertSeeIn($selector, $text);
});
}
+ /**
+ * Returns content of a downloaded file
+ */
+ protected function readDownloadedFile($filename)
+ {
+ $filename = TESTS_DIR . "downloads/$filename";
+ // Give the browser a chance to finish download
+ if (!file_exists($filename)) {
+ sleep(2);
+ }
+
+ $this->assertFileExists($filename);
+
+ return file_get_contents($filename);
+ }
+
+ /**
+ * Removes downloaded file
+ */
+ protected function removeDownloadedFile($filename)
+ {
+ @unlink(TESTS_DIR . "downloads/$filename");
+ }
+
/**
* Starts PHP server.
*/
protected static function startWebServer()
{
$path = realpath(__DIR__ . '/../../public_html');
$cmd = ['php', '-S', 'localhost:8000'];
$env = [];
static::$phpProcess = new Process($cmd, null, $env);
static::$phpProcess->setWorkingDirectory($path);
static::$phpProcess->start();
static::afterClass(function () {
static::$phpProcess->stop();
});
}
}
diff --git a/tests/Browser/phpunit.xml b/tests/Browser/phpunit.xml
index 77a041b95..ada5031c6 100644
--- a/tests/Browser/phpunit.xml
+++ b/tests/Browser/phpunit.xml
@@ -1,29 +1,30 @@
<phpunit backupGlobals="false"
bootstrap="bootstrap.php"
colors="true">
<testsuites>
<testsuite name="Logon">
<file>Login.php</file>
<file>Logout.php</file>
</testsuite>
<testsuite name="Contacts">
<file>Contacts/Contacts.php</file>
<file>Contacts/Import.php</file>
+ <file>Contacts/Export.php</file>
</testsuite>
<testsuite name="Settings">
<file>Settings/Settings.php</file>
<file>Settings/Preferences.php</file>
<file>Settings/Preferences/General.php</file>
<file>Settings/Folders.php</file>
<file>Settings/Identities.php</file>
<file>Settings/Responses.php</file>
<file>Settings/About.php</file>
</testsuite>
<testsuite name="Mail">
<file>Mail/Mail.php</file>
<file>Mail/Compose.php</file>
<file>Mail/Getunread.php</file>
<file>Mail/List.php</file>
</testsuite>
</testsuites>
</phpunit>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Mar 19, 8:57 AM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
457056
Default Alt Text
(22 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment