Tests: Create Browser and Components for better code structure
parent
1edd7a4b3f
commit
f72054e761
@ -0,0 +1,248 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use Facebook\WebDriver\WebDriverKeys;
|
||||||
|
use PHPUnit\Framework\Assert;
|
||||||
|
use Tests\Browser\Components;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Laravel Dusk Browser extensions
|
||||||
|
*/
|
||||||
|
class Browser extends \Laravel\Dusk\Browser
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Assert specified rcmail.env value
|
||||||
|
*/
|
||||||
|
public function assertEnvEquals($key, $expected)
|
||||||
|
{
|
||||||
|
$this->assertEquals($expected, $this->getEnv($key));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert specified checkbox state
|
||||||
|
*/
|
||||||
|
public function assertCheckboxState($selector, $state)
|
||||||
|
{
|
||||||
|
if ($state) {
|
||||||
|
$this->assertChecked($selector);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->assertNotChecked($selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert Task menu state
|
||||||
|
*/
|
||||||
|
public function assertTaskMenu($selected)
|
||||||
|
{
|
||||||
|
$this->with(new Components\Taskmenu(), function ($browser) use ($selected) {
|
||||||
|
$browser->assertMenuState($selected);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert toolbar menu state
|
||||||
|
*/
|
||||||
|
public function assertToolbarMenu($active, $disabled)
|
||||||
|
{
|
||||||
|
$this->with(new Components\Toolbarmenu(), function ($browser) use ($active, $disabled) {
|
||||||
|
$browser->assertMenuState($active, $disabled);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close toolbar menu (on phones)
|
||||||
|
*/
|
||||||
|
public function closeToolbarMenu()
|
||||||
|
{
|
||||||
|
$this->with(new Components\Toolbarmenu(), function ($browser) {
|
||||||
|
$browser->closeMenu();
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select taskmenu item
|
||||||
|
*/
|
||||||
|
public function clickTaskMenuItem($name)
|
||||||
|
{
|
||||||
|
$this->with(new Components\Taskmenu(), function ($browser) use ($name) {
|
||||||
|
$browser->clickMenuItem($name);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select toolbar menu item
|
||||||
|
*/
|
||||||
|
public function clickToolbarMenuItem($name, $dropdown_action = null)
|
||||||
|
{
|
||||||
|
$this->with(new Components\Toolbarmenu(), function ($browser) use ($name, $dropdown_action) {
|
||||||
|
$browser->clickMenuItem($name, $dropdown_action);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shortcut to click an element while holding CTRL key
|
||||||
|
*/
|
||||||
|
public function ctrlClick($selector)
|
||||||
|
{
|
||||||
|
$this->driver->getKeyboard()->pressKey(WebDriverKeys::LEFT_CONTROL);
|
||||||
|
$this->element($selector)->click();
|
||||||
|
$this->driver->getKeyboard()->releaseKey(WebDriverKeys::LEFT_CONTROL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log in the test user
|
||||||
|
*/
|
||||||
|
public function doLogin()
|
||||||
|
{
|
||||||
|
$this->type('_user', TESTS_USER);
|
||||||
|
$this->type('_pass', TESTS_PASS);
|
||||||
|
$this->click('button[type="submit"]');
|
||||||
|
|
||||||
|
// wait after successful login
|
||||||
|
$this->waitUntil('!rcmail.busy');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visit specified task/action with logon if needed
|
||||||
|
*/
|
||||||
|
public function go($task = 'mail', $action = null, $login = true)
|
||||||
|
{
|
||||||
|
$this->visit("/?_task=$task&_action=$action");
|
||||||
|
|
||||||
|
// check if we have a valid session
|
||||||
|
if ($login) {
|
||||||
|
$app = new Components\App();
|
||||||
|
if ($app->getEnv($this, 'task') == 'login') {
|
||||||
|
$this->doLogin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if in Phone mode
|
||||||
|
*/
|
||||||
|
public static function isPhone()
|
||||||
|
{
|
||||||
|
return getenv('TESTS_MODE') == 'phone';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if in Tablet mode
|
||||||
|
*/
|
||||||
|
public static function isTablet()
|
||||||
|
{
|
||||||
|
return getenv('TESTS_MODE') == 'tablet';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if in Desktop mode
|
||||||
|
*/
|
||||||
|
public static function isDesktop()
|
||||||
|
{
|
||||||
|
return !self::isPhone() && !self::isTablet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change state of the Elastic's pretty checkbox
|
||||||
|
*/
|
||||||
|
public function setCheckboxState($selector, $state)
|
||||||
|
{
|
||||||
|
// Because you can't operate on the original checkbox directly
|
||||||
|
$this->ensurejQueryIsAvailable();
|
||||||
|
|
||||||
|
if ($state) {
|
||||||
|
$run = "if (!element.prev().is(':checked')) element.click()";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$run = "if (element.prev().is(':checked')) element.click()";
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->script(
|
||||||
|
"var element = jQuery('$selector')[0] || jQuery('input[name=$selector]')[0];"
|
||||||
|
."element = jQuery(element).next('.custom-control-label'); $run;"
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns content of a downloaded file
|
||||||
|
*/
|
||||||
|
public function readDownloadedFile($filename)
|
||||||
|
{
|
||||||
|
$filename = TESTS_DIR . "downloads/$filename";
|
||||||
|
|
||||||
|
// Give the browser a chance to finish download
|
||||||
|
if (!file_exists($filename)) {
|
||||||
|
sleep(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert::assertFileExists($filename);
|
||||||
|
|
||||||
|
return file_get_contents($filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes downloaded file
|
||||||
|
*/
|
||||||
|
public function removeDownloadedFile($filename)
|
||||||
|
{
|
||||||
|
@unlink(TESTS_DIR . "downloads/$filename");
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for UI (notice/confirmation/loading/error/warning) message
|
||||||
|
* and assert it's text
|
||||||
|
*/
|
||||||
|
public function waitForMessage($type, $text)
|
||||||
|
{
|
||||||
|
$selector = '#messagestack > div.' . $type;
|
||||||
|
|
||||||
|
$this->waitFor($selector)->assertSeeIn($selector, $text);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute code within body context.
|
||||||
|
* Useful to execute code that selects elements outside of a component context
|
||||||
|
*/
|
||||||
|
public function withinBody($callback)
|
||||||
|
{
|
||||||
|
if ($this->resolver->prefix != 'body') {
|
||||||
|
$orig_prefix = $this->resolver->prefix;
|
||||||
|
$this->resolver->prefix = 'body';
|
||||||
|
}
|
||||||
|
|
||||||
|
call_user_func($callback, $this);
|
||||||
|
|
||||||
|
if (isset($orig_prefix)) {
|
||||||
|
$this->resolver->prefix = $orig_prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Components;
|
||||||
|
|
||||||
|
use Laravel\Dusk\Component as BaseComponent;
|
||||||
|
use PHPUnit\Framework\Assert;
|
||||||
|
use Tests\Browser\Browser;
|
||||||
|
|
||||||
|
class App extends BaseComponent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
{
|
||||||
|
$result = $browser->script("return typeof(window.rcmail)");
|
||||||
|
|
||||||
|
Assert::assertEquals('object', $result[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the element shortcuts for the component.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function elements()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert value of rcmail.env entry
|
||||||
|
*/
|
||||||
|
public function assertEnv($browser, string $key, $expected)
|
||||||
|
{
|
||||||
|
Assert::assertEquals($expected, $this->getEnv($browser, $key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert existence of defined gui_objects
|
||||||
|
*/
|
||||||
|
public function assertObjects($browser, array $names)
|
||||||
|
{
|
||||||
|
$objects = $this->getObjects($browser);
|
||||||
|
|
||||||
|
foreach ($names as $object_name) {
|
||||||
|
Assert::assertContains($object_name, $objects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return rcmail.env entry
|
||||||
|
*/
|
||||||
|
public function getEnv($browser, $key)
|
||||||
|
{
|
||||||
|
$result = $browser->script("return rcmail.env['$key']");
|
||||||
|
$result = $result[0];
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return names of defined gui_objects
|
||||||
|
*/
|
||||||
|
public function getObjects($browser)
|
||||||
|
{
|
||||||
|
$objects = $browser->script("var i, r = []; for (i in rcmail.gui_objects) r.push(i); return r");
|
||||||
|
$objects = $objects[0];
|
||||||
|
|
||||||
|
return (array) $objects;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Components;
|
||||||
|
|
||||||
|
use Tests\Browser\Browser;
|
||||||
|
use Laravel\Dusk\Component as BaseComponent;
|
||||||
|
|
||||||
|
class Taskmenu extends BaseComponent
|
||||||
|
{
|
||||||
|
protected $options = ['compose', 'mail', 'contacts', 'settings', 'about', 'logout'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the root selector for the component.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function selector()
|
||||||
|
{
|
||||||
|
return '#taskmenu';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the browser page contains the component.
|
||||||
|
*
|
||||||
|
* @param Browser $browser
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function assert($browser)
|
||||||
|
{
|
||||||
|
if ($browser->isPhone()) {
|
||||||
|
$browser->assertPresent($this->selector());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$browser->assertVisible($this->selector());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the element shortcuts for the component.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function elements()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert Taskmenu state
|
||||||
|
*/
|
||||||
|
public function assertMenuState(Browser $browser, $selected)
|
||||||
|
{
|
||||||
|
// On phone the menu is invisible, open it
|
||||||
|
if ($browser->isPhone()) {
|
||||||
|
$browser->withinBody(function ($browser) {
|
||||||
|
$browser->click('.task-menu-button');
|
||||||
|
$browser->waitFor($this->selector());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->options as $option) {
|
||||||
|
$browser->assertVisible("a.{$option}:not(.disabled)" . ($selected == $option ? ".selected" : ":not(.selected)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide the menu back
|
||||||
|
if ($browser->isPhone()) {
|
||||||
|
$browser->withinBody(function ($browser) {
|
||||||
|
$browser->click('.popover a.button.cancel');
|
||||||
|
$browser->waitUntilMissing($this->selector());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select Taskmenu item
|
||||||
|
*/
|
||||||
|
public function clickMenuItem(Browser $browser, $name)
|
||||||
|
{
|
||||||
|
if ($browser->isPhone()) {
|
||||||
|
$browser->withinBody(function ($browser) {
|
||||||
|
$browser->click('.task-menu-button');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$browser->click("a.{$name}");
|
||||||
|
|
||||||
|
if ($browser->isPhone()) {
|
||||||
|
$browser->withinBody(function ($browser) {
|
||||||
|
$browser->waitUntilMissing($this->selector());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,126 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Components;
|
||||||
|
|
||||||
|
use Tests\Browser\Browser;
|
||||||
|
use Laravel\Dusk\Component as BaseComponent;
|
||||||
|
|
||||||
|
class Toolbarmenu extends BaseComponent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the root selector for the component.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function selector()
|
||||||
|
{
|
||||||
|
return '#toolbar-menu';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the browser page contains the component.
|
||||||
|
*
|
||||||
|
* @param Browser $browser
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function assert($browser)
|
||||||
|
{
|
||||||
|
if ($browser->isPhone()) {
|
||||||
|
$browser->assertPresent($this->selector());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$browser->assertVisible($this->selector());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the element shortcuts for the component.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function elements()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert toolbar menu state
|
||||||
|
*/
|
||||||
|
public function assertMenuState($browser, $active, $disabled)
|
||||||
|
{
|
||||||
|
// On phone the menu is invisible, open it
|
||||||
|
if ($browser->isPhone()) {
|
||||||
|
$browser->withinBody(function ($browser) {
|
||||||
|
$browser->click('.toolbar-menu-button');
|
||||||
|
$browser->waitFor($this->selector());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($active as $option) {
|
||||||
|
// Print action is disabled on phones
|
||||||
|
if ($option == 'print' && $browser->isPhone()) {
|
||||||
|
$browser->assertMissing("a.print");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$browser->assertVisible("a.{$option}:not(.disabled)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($disabled as $option) {
|
||||||
|
if ($option == 'print' && $browser->isPhone()) {
|
||||||
|
$browser->assertMissing("a.print");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$browser->assertVisible("a.{$option}.disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->closeMenu($browser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close toolbar menu (on phones)
|
||||||
|
*/
|
||||||
|
public function closeMenu($browser)
|
||||||
|
{
|
||||||
|
// hide the menu back
|
||||||
|
if ($browser->isPhone()) {
|
||||||
|
$browser->withinBody(function ($browser) {
|
||||||
|
$browser->script("window.UI.menu_hide('toolbar-menu')");
|
||||||
|
$browser->waitUntilMissing($this->selector());
|
||||||
|
// FIXME: For some reason sometimes .popover-overlay does not close,
|
||||||
|
// we have to remove it manually
|
||||||
|
$browser->script(
|
||||||
|
"Array.from(document.getElementsByClassName('popover-overlay')).forEach(function(elem) { elem.parentNode.removeChild(elem); })"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select toolbar menu item
|
||||||
|
*/
|
||||||
|
public function clickMenuItem($browser, $name, $dropdown_action = null)
|
||||||
|
{
|
||||||
|
if ($browser->isPhone()) {
|
||||||
|
$browser->withinBody(function ($browser) {
|
||||||
|
$browser->click('.toolbar-menu-button');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$selector = "a.{$name}" . ($dropdown_action ? " + a.dropdown" : '');
|
||||||
|
|
||||||
|
$browser->click($selector);
|
||||||
|
|
||||||
|
if ($dropdown_action) {
|
||||||
|
$popup_id = $browser->attribute($selector, 'data-popup');
|
||||||
|
$browser->withinBody(function ($browser) use ($popup_id, $dropdown_action) {
|
||||||
|
$browser->click("#{$popup_id} li a.{$dropdown_action}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->closeMenu($browser);
|
||||||
|
}
|
||||||
|
}
|
@ -1,460 +0,0 @@
|
|||||||
<?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 = TESTS_DIR . 'screenshots';
|
|
||||||
Browser::$storeConsoleLogAt = TESTS_DIR . 'console';
|
|
||||||
|
|
||||||
// This folder must exist in case Browser will try to write logs to it
|
|
||||||
if (!is_dir(Browser::$storeConsoleLogAt)) {
|
|
||||||
mkdir(Browser::$storeConsoleLogAt, 0777, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge screenshots from the last test run
|
|
||||||
$pattern = sprintf('failure-%s_%s-*',
|
|
||||||
str_replace("\\", '_', get_class($this)),
|
|
||||||
$this->getName(false)
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$files = Finder::create()->files()->in(Browser::$storeScreenshotsAt)->name($pattern);
|
|
||||||
foreach ($files as $file) {
|
|
||||||
@unlink($file->getRealPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
|
|
||||||
// ignore missing screenshots directory
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge console logs from the last test run
|
|
||||||
$pattern = sprintf('%s_%s-*',
|
|
||||||
str_replace("\\", '_', get_class($this)),
|
|
||||||
$this->getName(false)
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
$files = Finder::create()->files()->in(Browser::$storeConsoleLogAt)->name($pattern);
|
|
||||||
foreach ($files as $file) {
|
|
||||||
@unlink($file->getRealPath());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
|
|
||||||
// ignore missing screenshots directory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if in Phone mode
|
|
||||||
*/
|
|
||||||
public static function isPhone()
|
|
||||||
{
|
|
||||||
return getenv('TESTS_MODE') == 'phone';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if in Tablet mode
|
|
||||||
*/
|
|
||||||
public static function isTablet()
|
|
||||||
{
|
|
||||||
return getenv('TESTS_MODE') == 'tablet';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if in Desktop mode
|
|
||||||
*/
|
|
||||||
public static function isDesktop()
|
|
||||||
{
|
|
||||||
return !self::isPhone() && !self::isTablet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert specified rcmail.env value
|
|
||||||
*/
|
|
||||||
protected function assertEnvEquals($key, $expected)
|
|
||||||
{
|
|
||||||
$this->assertEquals($expected, $this->getEnv($key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert specified checkbox state
|
|
||||||
*/
|
|
||||||
protected function assertCheckboxState($selector, $state)
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) use ($selector, $state) {
|
|
||||||
if ($state) {
|
|
||||||
$browser->assertChecked($selector);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$browser->assertNotChecked($selector);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert Task menu state
|
|
||||||
*/
|
|
||||||
protected function assertTaskMenu($selected)
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) use ($selected) {
|
|
||||||
// On phone the menu is invisible, open it
|
|
||||||
if ($this->isPhone()) {
|
|
||||||
$browser->click('.task-menu-button');
|
|
||||||
$browser->waitFor('#taskmenu');
|
|
||||||
}
|
|
||||||
|
|
||||||
$browser->with('#taskmenu', function(Browser $browser) use ($selected) {
|
|
||||||
$options = ['compose', 'mail', 'contacts', 'settings', 'about', 'logout'];
|
|
||||||
foreach ($options as $option) {
|
|
||||||
$browser->assertVisible("a.{$option}:not(.disabled)" . ($selected == $option ? ".selected" : ":not(.selected)"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// hide the menu back
|
|
||||||
if ($this->isPhone()) {
|
|
||||||
$browser->click('.popover a.button.cancel');
|
|
||||||
$browser->waitUntilMissing('#taskmenu');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assert toolbar menu state
|
|
||||||
*/
|
|
||||||
protected function assertToolbarMenu($active, $disabled)
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) use ($active, $disabled) {
|
|
||||||
// On phone the menu is invisible, open it
|
|
||||||
if ($this->isPhone()) {
|
|
||||||
$browser->click('.toolbar-menu-button');
|
|
||||||
$browser->waitFor('#toolbar-menu');
|
|
||||||
}
|
|
||||||
|
|
||||||
$browser->with('#toolbar-menu', function(Browser $browser) use ($active, $disabled) {
|
|
||||||
foreach ($active as $option) {
|
|
||||||
// Print action is disabled on phones
|
|
||||||
if ($option == 'print' && $this->isPhone()) {
|
|
||||||
$browser->assertMissing("a.print");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$browser->assertVisible("a.{$option}:not(.disabled)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($disabled as $option) {
|
|
||||||
if ($option == 'print' && $this->isPhone()) {
|
|
||||||
$browser->assertMissing("a.print");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$browser->assertVisible("a.{$option}.disabled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->closeToolbarMenu();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close toolbar menu (on phones)
|
|
||||||
*/
|
|
||||||
protected function closeToolbarMenu()
|
|
||||||
{
|
|
||||||
// hide the menu back
|
|
||||||
if ($this->isPhone()) {
|
|
||||||
$this->browse(function (Browser $browser) {
|
|
||||||
$browser->script("window.UI.menu_hide('toolbar-menu')");
|
|
||||||
$browser->waitUntilMissing('#toolbar-menu');
|
|
||||||
// FIXME: For some reason sometimes .popover-overlay does not close,
|
|
||||||
// we have to remove it manually
|
|
||||||
$browser->script(
|
|
||||||
"Array.from(document.getElementsByClassName('popover-overlay')).forEach(function(elem) { elem.parentNode.removeChild(elem); })"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select taskmenu item
|
|
||||||
*/
|
|
||||||
protected function clickTaskMenuItem($name)
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) use ($name) {
|
|
||||||
if ($this->isPhone()) {
|
|
||||||
$browser->click('.task-menu-button');
|
|
||||||
}
|
|
||||||
|
|
||||||
$browser->click("#taskmenu a.{$name}");
|
|
||||||
|
|
||||||
if ($this->isPhone()) {
|
|
||||||
$browser->waitUntilMissing('#taskmenu');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Select toolbar menu item
|
|
||||||
*/
|
|
||||||
protected function clickToolbarMenuItem($name, $dropdown_action = null)
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) use ($name, $dropdown_action) {
|
|
||||||
if ($this->isPhone()) {
|
|
||||||
$browser->click('.toolbar-menu-button');
|
|
||||||
}
|
|
||||||
|
|
||||||
$selector = "#toolbar-menu a.{$name}" . ($dropdown_action ? " + a.dropdown" : '');
|
|
||||||
|
|
||||||
$browser->click($selector);
|
|
||||||
|
|
||||||
if ($dropdown_action) {
|
|
||||||
$popup_id = $browser->attribute($selector, 'data-popup');
|
|
||||||
$browser->click("#{$popup_id} li a.{$dropdown_action}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isPhone()) {
|
|
||||||
$this->closeToolbarMenu();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function ctrlClick($selector)
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) use ($selector) {
|
|
||||||
$browser->driver->getKeyboard()->pressKey(\Facebook\WebDriver\WebDriverKeys::LEFT_CONTROL);
|
|
||||||
$browser->element('#contacts-table tbody tr:first-child')->click();
|
|
||||||
$browser->driver->getKeyboard()->releaseKey(\Facebook\WebDriver\WebDriverKeys::LEFT_CONTROL);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get content of rcmail.env entry
|
|
||||||
*/
|
|
||||||
protected function getEnv($key)
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) use ($key, &$result) {
|
|
||||||
$result = $browser->script("return rcmail.env['$key']");
|
|
||||||
$result = $result[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return names of defined gui_objects
|
|
||||||
*/
|
|
||||||
protected function getObjects()
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) use (&$objects) {
|
|
||||||
$objects = $browser->script("var i, r = []; for (i in rcmail.gui_objects) r.push(i); return r");
|
|
||||||
$objects = $objects[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
return (array) $objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log in the test user
|
|
||||||
*/
|
|
||||||
protected function doLogin()
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) {
|
|
||||||
$browser->type('_user', TESTS_USER);
|
|
||||||
$browser->type('_pass', TESTS_PASS);
|
|
||||||
$browser->click('button[type="submit"]');
|
|
||||||
|
|
||||||
// wait after successful login
|
|
||||||
//$browser->waitForReload();
|
|
||||||
$browser->waitUntil('!rcmail.busy');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Visit specified task/action with logon if needed
|
|
||||||
*/
|
|
||||||
protected function go($task = 'mail', $action = null, $login = true)
|
|
||||||
{
|
|
||||||
$this->browse(function (Browser $browser) use ($task, $action, $login) {
|
|
||||||
$browser->visit("/?_task=$task&_action=$action");
|
|
||||||
|
|
||||||
// check if we have a valid session
|
|
||||||
if ($login && $this->getEnv('task') == 'login') {
|
|
||||||
$this->doLogin();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change state of the Elastic's pretty checkbox
|
|
||||||
*/
|
|
||||||
protected function setCheckboxState($selector, $state)
|
|
||||||
{
|
|
||||||
// Because you can't operate on the original checkbox directly
|
|
||||||
$this->browse(function (Browser $browser) use ($selector, $state) {
|
|
||||||
$browser->ensurejQueryIsAvailable();
|
|
||||||
|
|
||||||
if ($state) {
|
|
||||||
$run = "if (!element.prev().is(':checked')) element.click()";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$run = "if (element.prev().is(':checked')) element.click()";
|
|
||||||
}
|
|
||||||
|
|
||||||
$browser->script(
|
|
||||||
"var element = jQuery('$selector')[0] || jQuery('input[name=$selector]')[0];"
|
|
||||||
."element = jQuery(element).next('.custom-control-label'); $run;"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for UI (notice/confirmation/loading/error/warning) message
|
|
||||||
* and assert it's text
|
|
||||||
*/
|
|
||||||
protected function waitForMessage($type, $text)
|
|
||||||
{
|
|
||||||
$selector = '#messagestack > div.' . $type;
|
|
||||||
|
|
||||||
$this->browse(function ($browser) use ($selector, $text) {
|
|
||||||
$browser->waitFor($selector)->assertSeeIn($selector, $text);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns content of a downloaded file
|
|
||||||
*/
|
|
||||||
protected function readDownloadedFile($filename)
|
|
||||||
{
|
|
||||||
$filename = TESTS_DIR . "downloads/$filename";
|
|
||||||
// Give the browser a chance to finish download
|
|
||||||
if (!file_exists($filename)) {
|
|
||||||
sleep(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->assertFileExists($filename);
|
|
||||||
|
|
||||||
return file_get_contents($filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes downloaded file
|
|
||||||
*/
|
|
||||||
protected function removeDownloadedFile($filename)
|
|
||||||
{
|
|
||||||
@unlink(TESTS_DIR . "downloads/$filename");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts PHP server.
|
|
||||||
*/
|
|
||||||
protected static function startWebServer()
|
|
||||||
{
|
|
||||||
$path = realpath(__DIR__ . '/../../public_html');
|
|
||||||
$cmd = ['php', '-S', 'localhost:8000'];
|
|
||||||
$env = [];
|
|
||||||
|
|
||||||
static::$phpProcess = new Process($cmd, null, $env);
|
|
||||||
static::$phpProcess->setWorkingDirectory($path);
|
|
||||||
static::$phpProcess->start();
|
|
||||||
|
|
||||||
static::afterClass(function () {
|
|
||||||
static::$phpProcess->stop();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,169 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase as PHPUnitTestCase;
|
||||||
|
use Facebook\WebDriver\Chrome\ChromeOptions;
|
||||||
|
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||||
|
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
||||||
|
use Laravel\Dusk\Chrome\SupportsChrome;
|
||||||
|
use Laravel\Dusk\Concerns\ProvidesBrowser;
|
||||||
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Symfony\Component\Process\Process;
|
||||||
|
|
||||||
|
abstract class TestCase extends PHPUnitTestCase
|
||||||
|
{
|
||||||
|
use ProvidesBrowser,
|
||||||
|
SupportsChrome;
|
||||||
|
|
||||||
|
protected $app;
|
||||||
|
protected static $phpProcess;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace Dusk's Browser with our (extended) Browser
|
||||||
|
*/
|
||||||
|
protected function newBrowser($driver)
|
||||||
|
{
|
||||||
|
return new Browser($driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = TESTS_DIR . 'screenshots';
|
||||||
|
Browser::$storeConsoleLogAt = TESTS_DIR . 'console';
|
||||||
|
|
||||||
|
// This folder must exist in case Browser will try to write logs to it
|
||||||
|
if (!is_dir(Browser::$storeConsoleLogAt)) {
|
||||||
|
mkdir(Browser::$storeConsoleLogAt, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge screenshots from the last test run
|
||||||
|
$pattern = sprintf('failure-%s_%s-*',
|
||||||
|
str_replace("\\", '_', get_class($this)),
|
||||||
|
$this->getName(false)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$files = Finder::create()->files()->in(Browser::$storeScreenshotsAt)->name($pattern);
|
||||||
|
foreach ($files as $file) {
|
||||||
|
@unlink($file->getRealPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
|
||||||
|
// ignore missing screenshots directory
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purge console logs from the last test run
|
||||||
|
$pattern = sprintf('%s_%s-*',
|
||||||
|
str_replace("\\", '_', get_class($this)),
|
||||||
|
$this->getName(false)
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$files = Finder::create()->files()->in(Browser::$storeConsoleLogAt)->name($pattern);
|
||||||
|
foreach ($files as $file) {
|
||||||
|
@unlink($file->getRealPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (\Symfony\Component\Finder\Exception\DirectoryNotFoundException $e) {
|
||||||
|
// ignore missing screenshots directory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts PHP server.
|
||||||
|
*/
|
||||||
|
protected static function startWebServer()
|
||||||
|
{
|
||||||
|
$path = realpath(__DIR__ . '/../../public_html');
|
||||||
|
$cmd = ['php', '-S', 'localhost:8000'];
|
||||||
|
$env = [];
|
||||||
|
|
||||||
|
static::$phpProcess = new Process($cmd, null, $env);
|
||||||
|
static::$phpProcess->setWorkingDirectory($path);
|
||||||
|
static::$phpProcess->start();
|
||||||
|
|
||||||
|
static::afterClass(function () {
|
||||||
|
static::$phpProcess->stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue