updated-packages

This commit is contained in:
RafficMohammed
2023-01-08 00:13:22 +05:30
parent 3ff7df7487
commit da241bacb6
12659 changed files with 563377 additions and 510538 deletions

View File

@@ -1,4 +1,203 @@
# Release Notes for 1.0.x
# Release Notes
## [Unreleased](https://github.com/laravel/dusk/compare/v5.11.0...5.0)
## [v5.11.0 (2020-03-24)](https://github.com/laravel/dusk/compare/v5.10.0...v5.11.0)
### Added
- Add assert attribute methods ([#751](https://github.com/laravel/dusk/pull/751))
### Fixed
- Fix Call to undefined method ([#750](https://github.com/laravel/dusk/pull/750))
- Avoid throwing exception on production ([#755](https://github.com/laravel/dusk/pull/755))
## [v5.10.0 (2020-03-17)](https://github.com/laravel/dusk/compare/v5.9.2...v5.10.0)
### Added
- Adds `typeSlowly` & `appendSlowly` ([#748](https://github.com/laravel/dusk/pull/748))
## [v5.9.2 (2020-02-18)](https://github.com/laravel/dusk/compare/v5.9.1...v5.9.2)
### Fixed
- Bugfix quoting for `InteractsWithElements::value` ([#735](https://github.com/laravel/dusk/pull/735))
- Remove php-webdriver constraints ([#737](https://github.com/laravel/dusk/pull/737))
## [v5.9.1 (2020-02-12)](https://github.com/laravel/dusk/compare/v5.9.0...v5.9.1)
### Fixed
- Adds the missing import for `InteractsWithMouse@clickAtXPath` ([#728](https://github.com/laravel/dusk/pull/728))
- Size sanity check at fitContent ([#730](https://github.com/laravel/dusk/pull/730))
- Lock php-webdriver constraints for now ([#733](https://github.com/laravel/dusk/pull/733))
## [v5.9.0 (2020-01-28)](https://github.com/laravel/dusk/compare/v5.8.2...v5.9.0)
### Added
- Add `clickAtXPath` ([#723](https://github.com/laravel/dusk/pull/723), [effe73d](https://github.com/laravel/dusk/commit/effe73d6eb61b4bd77f88814bcd679e4fceb6f25))
- Add `ProvidesBrowser::getCallerName()` ([#725](https://github.com/laravel/dusk/pull/725))
### Fixed
- Fit content to `<html>` instead of `<body>` ([#726](https://github.com/laravel/dusk/pull/726))
## [v5.8.2 (2020-01-21)](https://github.com/laravel/dusk/compare/v5.8.1...v5.8.2)
### Changed
- Rename php-webdriver package ([#720](https://github.com/laravel/dusk/pull/720))
- Update jQuery file ([#721](https://github.com/laravel/dusk/pull/721))
### Fixed
- Update `fitContent()` ([#717](https://github.com/laravel/dusk/pull/717))
## [v5.8.1 (2020-01-07)](https://github.com/laravel/dusk/compare/v5.8.0...v5.8.1)
### Fixed
- Cast boolean values to appropriate string ([#713](https://github.com/laravel/dusk/pull/713))
## [v5.8.0 (2019-12-30)](https://github.com/laravel/dusk/compare/v5.7.0...v5.8.0)
### Added
- Add "waitUntilMissingText" ([#706](https://github.com/laravel/dusk/pull/706))
- Add ability to store source from browser ([#707](https://github.com/laravel/dusk/pull/707), [9c90e2a](https://github.com/laravel/dusk/commit/9c90e2a716030c9a36e6306c3f67d606a254bbb7), [1d5bc20](https://github.com/laravel/dusk/commit/1d5bc203b67ffc5a17eb1b89f3e22547e3ea174b))
## [v5.7.0 (2019-12-17)](https://github.com/laravel/dusk/compare/v5.6.3...v5.7.0)
### Added
- Automatically fit content on failures ([#704](https://github.com/laravel/dusk/pull/704))
## [v5.6.3 (2019-12-03)](https://github.com/laravel/dusk/compare/v5.6.2...v5.6.3)
### Added
- Support phpdotenv v4 ([#699](https://github.com/laravel/dusk/pull/699))
### Fixed
- scrollTo: add support for selectors with quotes ([#697](https://github.com/laravel/dusk/pull/697))
## [v5.6.2 (2019-11-26)](https://github.com/laravel/dusk/compare/v5.6.1...v5.6.2)
### Changed
- Allow for Symfony 5 ([#696](https://github.com/laravel/dusk/pull/696))
## [v5.6.1 (2019-11-12)](https://github.com/laravel/dusk/compare/v5.6.0...v5.6.1)
### Fixed
- Ensure jQuery for scrollTo ([#686](https://github.com/laravel/dusk/pull/686))
- Added missing return statement in withDuskEnvironment ([#691](https://github.com/laravel/dusk/pull/691))
- Prevent using pcntl when not installed ([#692](https://github.com/laravel/dusk/pull/692))
## [v5.6.0 (2019-10-29)](https://github.com/laravel/dusk/compare/v5.5.2...v5.6.0)
### Added
- Add scrollTo method ([#684](https://github.com/laravel/dusk/pull/684))
### Fixed
- Add graceful handler for `SIGINT` for .env restoration ([#682](https://github.com/laravel/dusk/pull/682), [f843b8a](https://github.com/laravel/dusk/commit/f843b8a51ae96933cefcc74dec515377d3135611))
## [v5.5.2 (2019-09-24)](https://github.com/laravel/dusk/compare/v5.5.1...v5.5.2)
### Fixed
- Improve detection of latest stable ChromeDriver release ([#677](https://github.com/laravel/dusk/pull/677))
## [v5.5.1 (2019-09-12)](https://github.com/laravel/dusk/compare/v5.5.0...v5.5.1)
### Fixed
- Update regular expression base on website changes ([#674](https://github.com/laravel/dusk/pull/674))
## [v5.5.0 (2019-08-06)](https://github.com/laravel/dusk/compare/v5.4.0...v5.5.0)
### Added
- Allow saving screenshots in a subdirectory ([#662](https://github.com/laravel/dusk/pull/662))
## [v5.4.0 (2019-07-30)](https://github.com/laravel/dusk/compare/v5.3.0...v5.4.0)
### Added
- Add assertion checks if a button is disabled or enabled ([#661](https://github.com/laravel/dusk/pull/661))
### Fixed
- Update constraints for Laravel 6.0 ([e4b4d63](https://github.com/laravel/dusk/commit/e4b4d63c179bb1f228db22852bd776db750d1ec6))
## [v5.3.0 (2019-07-11)](https://github.com/laravel/dusk/compare/v5.2.0...v5.3.0)
### Added
- Add proxy support to the `dusk:install` command ([#659](https://github.com/laravel/dusk/pull/659))
## [v5.2.0 (2019-06-25)](https://github.com/laravel/dusk/compare/v5.1.1...v5.2.0)
### Added
- Add option to fullsize the browser ([#655](https://github.com/laravel/dusk/pull/655))
## [v5.1.1 (2019-06-14)](https://github.com/laravel/dusk/compare/v5.1.0...v5.1.1)
### Fixed
- Add latest version of Facebook Webdriver ([#654](https://github.com/laravel/dusk/pull/654))
## [v5.1.0 (2019-05-02)](https://github.com/laravel/dusk/compare/v5.0.3...v5.1.0)
### Added
- Implement ChromeDriverCommand ([#643](https://github.com/laravel/dusk/pull/643), [60339a5](https://github.com/laravel/dusk/commit/60339a521a1b05e55af7c90b3151557100a0db4d), [#644](https://github.com/laravel/dusk/pull/644))
## [v5.0.3 (2019-04-02)](https://github.com/laravel/dusk/compare/v5.0.2...v5.0.3)
### Fixed
- Fix `assertVueContains` and `assertVueDoesNotContain` ([#638](https://github.com/laravel/dusk/pull/638))
## [v5.0.2 (2019-03-12)](https://github.com/laravel/dusk/compare/v5.0.1...v5.0.2)
### Fixed
- Fix cookies with falsey values ([#617](https://github.com/laravel/dusk/pull/617))
- Fix `with()` and page assertions ([#625](https://github.com/laravel/dusk/pull/625))
- Avoid deprecation messages on PHPUnit 8 ([#620](https://github.com/laravel/dusk/pull/620))
## [v5.0.1 (2019-02-27)](https://github.com/laravel/dusk/compare/v5.0.0...v5.0.1)
### Added
- Added support for `phpunit.dusk.xml.dist` ([#610](https://github.com/laravel/dusk/pull/610))
- Use custom DISPLAY variable when set on Linux ([#613](https://github.com/laravel/dusk/pull/613), [2480495](https://github.com/laravel/dusk/commit/24804953c5b521415a717afbf82ff4b938c10535))
### Fixed
- Added missing dependencies ([98eccfd](https://github.com/laravel/dusk/commit/98eccfd56e9b2b23b093b801f62c766aaf67589f))
- Fix installation of Dotenv on Laravel 5.8 ([1f67bf2](https://github.com/laravel/dusk/commit/1f67bf204fab65a212975683b5391c2f55dd3bcf))
## [v5.0.0 (2019-02-12)](https://github.com/laravel/dusk/compare/v4.0.5...v5.0.0)
### Added
- Support PHPUnit 8 ([788e79c](https://github.com/laravel/dusk/commit/788e79c4713a5706eeafaf7270986a71a4ed43be))
- Support Carbon 2 ([85cfc50](https://github.com/laravel/dusk/commit/85cfc50e126a3835428577052cc0660d1c3b639e))
- Support Laravel 5.8 ([5b2e9aa](https://github.com/laravel/dusk/commit/5b2e9aa9e4a124f4c4878f65fb644101de1644e7))
### Changed
- Update minimum Laravel version ([fcb2226](https://github.com/laravel/dusk/commit/fcb2226a524f47b51b9b49316d87bc51738733d7))
- Update Symfony dependencies to latest version ([788e79c](https://github.com/laravel/dusk/commit/788e79c4713a5706eeafaf7270986a71a4ed43be))
- Prefer stable dependencies ([fdb2fd4](https://github.com/laravel/dusk/commit/fdb2fd4b2a2e787b08cf44649c4eef84837324ca))
## [v4.0.0 (2018-08-11)](https://github.com/laravel/dusk/compare/v3.0.10...v4.0.0)
Dusk 4.0.0 disables cookie serialization and is intended for use with Laravel 5.6.30 or later. If you are using a previous version of Laravel, please continue using Dusk 3.0.0.
## v1.0.13 (2017-04-20)
@@ -16,6 +215,7 @@
- Use `getAttribute('value')` instead of `getText()` for `textarea` elements ([#237](https://github.com/laravel/dusk/pull/237))
- Fixed bug when giving strings with apostrophe to `clickLink()` ([#228](https://github.com/laravel/dusk/pull/228))
## v1.0.12 (2017-04-07)
### Added
@@ -38,6 +238,3 @@
- Updated ChromeDriver to v2.28 so that it works with Chrome 57 ([#199](https://github.com/laravel/dusk/pull/199))
- Comparison to `option` inside `select` will no longer be strict ([#178](https://github.com/laravel/dusk/pull/178))
- Type-hint Browser for auto-complete support ([#174](https://github.com/laravel/dusk/pull/174))
## v1.0.10 (2017-03-03)

View File

@@ -1,7 +1,7 @@
<p align="center"><img src="https://laravel.com/assets/img/components/logo-dusk.svg"></p>
<p align="center">
<a href="https://travis-ci.org/laravel/dusk"><img src="https://travis-ci.org/laravel/dusk.svg" alt="Build Status"></a>
<a href="https://github.com/laravel/dusk/actions"><img src="https://github.com/laravel/dusk/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://poser.pugx.org/laravel/dusk/d/total.svg" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://poser.pugx.org/laravel/dusk/v/stable.svg" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://poser.pugx.org/laravel/dusk/license.svg" alt="License"></a>
@@ -13,8 +13,20 @@ Laravel Dusk provides an expressive, easy-to-use browser automation and testing
## Official Documentation
Documentation for Dusk can be found on the [Laravel website](https://laravel.com/docs/master/dusk).
Documentation for Dusk can be found on the [Laravel website](https://laravel.com/docs/dusk).
## Contributing
Thank you for considering contributing to Dusk! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
Please review [our security policy](https://github.com/laravel/dusk/security/policy) on how to report security vulnerabilities.
## License
Laravel Dusk is open-sourced software licensed under the [MIT license](http://opensource.org/licenses/MIT)
Laravel Dusk is open-sourced software licensed under the [MIT license](LICENSE.md).

17
vendor/laravel/dusk/UPGRADE.md vendored Normal file
View File

@@ -0,0 +1,17 @@
# Upgrade Guide
## Upgrading To 5.0 From 4.0
### PHPUnit 8
Dusk now provides optional support for PHPUnit 8, which requires PHP >= 7.2 Please read through the entire list of changes in [the PHPUnit 8 release announcement](https://phpunit.de/announcements/phpunit-8.html). Using PHPUnit 8 will require Laravel 5.8, which will be released at the end of February 2019.
You may also continue using PHPUnit 7, which requires a minimum of PHP 7.1.
### Minimum Laravel version
Laravel 5.7 is now the minimum supported version of the framework and you should upgrade to continue using Dusk.
### `setUp` and `tearDown` changes
The `setUp` and `tearDown` methods now require the `void` return type. If you were overwriting these methods you should add it to the method signatures.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -11,25 +11,37 @@
],
"require": {
"php": ">=7.1.0",
"facebook/webdriver": "~1.3",
"nesbot/carbon": "~1.20",
"illuminate/console": "~5.6",
"illuminate/support": "~5.6",
"symfony/console": "~4.0",
"symfony/process": "~4.0"
"ext-json": "*",
"ext-zip": "*",
"php-webdriver/webdriver": "^1.8.1",
"nesbot/carbon": "^1.20|^2.0",
"illuminate/console": "~5.7.0|~5.8.0|^6.0|^7.0",
"illuminate/support": "~5.7.0|~5.8.0|^6.0|^7.0",
"symfony/console": "^4.0|^5.0",
"symfony/finder": "^4.0|^5.0",
"symfony/process": "^4.0|^5.0",
"vlucas/phpdotenv": "^2.2|^3.0|^4.0"
},
"require-dev": {
"mockery/mockery": "~1.0",
"phpunit/phpunit": "~7.0"
"mockery/mockery": "^1.0",
"phpunit/phpunit": "^7.5|^8.0"
},
"suggest": {
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
},
"autoload": {
"psr-4": {
"Laravel\\Dusk\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Laravel\\Dusk\\Tests\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
"dev-master": "5.0-dev"
},
"laravel": {
"providers": [
@@ -37,5 +49,9 @@
]
}
},
"minimum-stability": "dev"
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
failOnRisky="true"
failOnWarning="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
verbose="true"
>
<testsuites>
<testsuite name="Laravel Dusk Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -2,12 +2,14 @@
namespace Laravel\Dusk;
use Closure;
use BadMethodCallException;
use Closure;
use Facebook\WebDriver\Remote\WebDriverBrowserType;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverDimension;
use Facebook\WebDriver\WebDriverPoint;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use Facebook\WebDriver\WebDriverDimension;
use Facebook\WebDriver\Remote\WebDriverBrowserType;
class Browser
{
@@ -17,6 +19,7 @@ class Browser
Concerns\InteractsWithJavascript,
Concerns\InteractsWithMouse,
Concerns\MakesAssertions,
Concerns\MakesUrlAssertions,
Concerns\WaitsForElements,
Macroable {
__call as macroCall;
@@ -43,6 +46,13 @@ class Browser
*/
public static $storeConsoleLogAt;
/**
* The directory where source code snapshots will be stored.
*
* @var string
*/
public static $storeSourceAt;
/**
* The browsers that support retrieving logs.
*
@@ -77,7 +87,7 @@ class Browser
/**
* The element resolver instance.
*
* @var ElementResolver
* @var \Laravel\Dusk\ElementResolver
*/
public $resolver;
@@ -95,11 +105,18 @@ class Browser
*/
public $component;
/**
* Indicates that the browser should be resized to fit the entire "body" before screenshotting failures.
*
* @var bool
*/
public $fitOnFailure = true;
/**
* Create a browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @param ElementResolver $resolver
* @param \Laravel\Dusk\ElementResolver $resolver
* @return void
*/
public function __construct($driver, $resolver = null)
@@ -164,6 +181,21 @@ class Browser
* @return $this
*/
public function on($page)
{
$this->onWithoutAssert($page);
$page->assert($this);
return $this;
}
/**
* Set the current page object without executing the assertions.
*
* @param mixed $page
* @return $this
*/
public function onWithoutAssert($page)
{
$this->page = $page;
@@ -174,8 +206,6 @@ class Browser
$page::siteElements(), $page->elements()
));
$page->assert($this);
return $this;
}
@@ -231,6 +261,81 @@ class Browser
return $this;
}
/**
* Make the browser window as large as the content.
*
* @return $this
*/
public function fitContent()
{
$this->driver->switchTo()->defaultContent();
$html = $this->driver->findElement(WebDriverBy::tagName('html'));
if (! empty($html) && ($html->getSize()->getWidth() <= 0 || $html->getSize()->getHeight() <= 0)) {
$this->resize($html->getSize()->getWidth(), $html->getSize()->getHeight());
}
return $this;
}
/**
* Disable fit on failures.
*
* @return $this
*/
public function disableFitOnFailure()
{
$this->fitOnFailure = false;
return $this;
}
/**
* Enable fit on failures.
*
* @return $this
*/
public function enableFitOnFailure()
{
$this->fitOnFailure = true;
return $this;
}
/**
* Move the browser window.
*
* @param int $x
* @param int $y
* @return $this
*/
public function move($x, $y)
{
$this->driver->manage()->window()->setPosition(
new WebDriverPoint($x, $y)
);
return $this;
}
/**
* Scroll screen to element at the given selector.
*
* @param string $selector
* @return $this
*/
public function scrollTo($selector)
{
$this->ensurejQueryIsAvailable();
$selector = addslashes($this->resolver->format($selector));
$this->driver->executeScript("jQuery(\"html, body\").animate({scrollTop: jQuery(\"$selector\").offset().top}, 0);");
return $this;
}
/**
* Take a screenshot and store it with the given name.
*
@@ -239,9 +344,15 @@ class Browser
*/
public function screenshot($name)
{
$this->driver->takeScreenshot(
sprintf('%s/%s.png', rtrim(static::$storeScreenshotsAt, '/'), $name)
);
$filePath = sprintf('%s/%s.png', rtrim(static::$storeScreenshotsAt, '/'), $name);
$directoryPath = dirname($filePath);
if (! is_dir($directoryPath)) {
mkdir($directoryPath, 0777, true);
}
$this->driver->takeScreenshot($filePath);
return $this;
}
@@ -257,10 +368,9 @@ class Browser
if (in_array($this->driver->getCapabilities()->getBrowserName(), static::$supportsRemoteLogs)) {
$console = $this->driver->manage()->getLog('browser');
if (!empty($console)) {
if (! empty($console)) {
file_put_contents(
sprintf('%s/%s.log', rtrim(static::$storeConsoleLogAt, '/'), $name)
, json_encode($console, JSON_PRETTY_PRINT)
sprintf('%s/%s.log', rtrim(static::$storeConsoleLogAt, '/'), $name), json_encode($console, JSON_PRETTY_PRINT)
);
}
}
@@ -269,9 +379,29 @@ class Browser
}
/**
* Switch to a specified frame in the browser.
* Store a snapshot of the page's current source code with the given name.
*
* @param string $name
* @return $this
*/
public function storeSource($name)
{
$source = $this->driver->getPageSource();
if (! empty($source)) {
file_put_contents(
sprintf('%s/%s.txt', rtrim(static::$storeSourceAt, '/'), $name), $source
);
}
return $this;
}
/**
* Switch to a specified frame in the browser and execute the given callback.
*
* @param string $selector
* @param \Closure $callback
* @return $this
*/
public function withinFrame($selector, Closure $callback)
@@ -311,7 +441,7 @@ class Browser
);
if ($this->page) {
$browser->on($this->page);
$browser->onWithoutAssert($this->page);
}
if ($selector instanceof Component) {
@@ -355,7 +485,7 @@ class Browser
*/
public function ensurejQueryIsAvailable()
{
if ($this->driver->executeScript("return window.jQuery == null")) {
if ($this->driver->executeScript('return window.jQuery == null')) {
$this->driver->executeScript(file_get_contents(__DIR__.'/../bin/jquery.js'));
}
}
@@ -439,6 +569,8 @@ class Browser
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{

View File

@@ -2,8 +2,8 @@
namespace Laravel\Dusk\Chrome;
use Laravel\Dusk\OperatingSystem;
use RuntimeException;
use Illuminate\Support\Str;
use Symfony\Component\Process\Process;
class ChromeProcess
@@ -11,7 +11,7 @@ class ChromeProcess
/**
* The path to the Chromedriver.
*
* @var String
* @var string
*/
protected $driver;
@@ -20,6 +20,8 @@ class ChromeProcess
*
* @param string $driver
* @return void
*
* @throws \RuntimeException
*/
public function __construct($driver = null)
{
@@ -33,37 +35,37 @@ class ChromeProcess
/**
* Build the process to run Chromedriver.
*
* @param array $arguments
* @return \Symfony\Component\Process\Process
*/
public function toProcess()
public function toProcess(array $arguments = [])
{
if ($this->driver) {
return $this->process();
return $this->process($arguments);
}
if ($this->onWindows()) {
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-win.exe');
return $this->process();
} elseif ($this->onMac()) {
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-mac');
} else {
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-linux');
}
$this->driver = $this->onMac()
? realpath(__DIR__.'/../../bin/chromedriver-mac')
: realpath(__DIR__.'/../../bin/chromedriver-linux');
return $this->process();
return $this->process($arguments);
}
/**
* Build the Chromedriver with Symfony Process.
*
* @param array $arguments
* @return \Symfony\Component\Process\Process
*/
protected function process()
protected function process(array $arguments = [])
{
return (new Process(
[realpath($this->driver)], null, $this->chromeEnvironment()
));
return new Process(
array_merge([realpath($this->driver)], $arguments), null, $this->chromeEnvironment()
);
}
/**
@@ -77,7 +79,7 @@ class ChromeProcess
return [];
}
return ['DISPLAY' => ':0'];
return ['DISPLAY' => $_ENV['DISPLAY'] ?? ':0'];
}
/**
@@ -87,7 +89,7 @@ class ChromeProcess
*/
protected function onWindows()
{
return PHP_OS === 'WINNT' || Str::contains(php_uname(), 'Microsoft');
return OperatingSystem::onWindows();
}
/**
@@ -97,6 +99,6 @@ class ChromeProcess
*/
protected function onMac()
{
return PHP_OS === 'Darwin';
return OperatingSystem::onMac();
}
}

View File

@@ -21,13 +21,14 @@ trait SupportsChrome
/**
* Start the Chromedriver process.
*
* @throws \RuntimeException if the driver file path doesn't exist.
*
* @param array $arguments
* @return void
*
* @throws \RuntimeException
*/
public static function startChromeDriver()
public static function startChromeDriver(array $arguments = [])
{
static::$chromeProcess = static::buildChromeProcess();
static::$chromeProcess = static::buildChromeProcess($arguments);
static::$chromeProcess->start();
@@ -51,13 +52,14 @@ trait SupportsChrome
/**
* Build the process to run the Chromedriver.
*
*
* @param array $arguments
* @return \Symfony\Component\Process\Process
* @throws \RuntimeException if the driver file path doesn't exist.
*
* @throws \RuntimeException
*/
protected static function buildChromeProcess()
protected static function buildChromeProcess(array $arguments = [])
{
return (new ChromeProcess(static::$chromeDriver))->toProcess();
return (new ChromeProcess(static::$chromeDriver))->toProcess($arguments);
}
/**

View File

@@ -21,7 +21,7 @@ trait InteractsWithAuthentication
* Log into the application using a given user ID or email.
*
* @param object|string $userId
* @param string $guard
* @param string $guard
* @return $this
*/
public function loginAs($userId, $guard = null)

View File

@@ -17,12 +17,12 @@ trait InteractsWithCookies
*/
public function cookie($name, $value = null, $expiry = null, array $options = [])
{
if ($value) {
if (! is_null($value)) {
return $this->addCookie($name, $value, $expiry, $options);
}
if ($cookie = $this->driver->manage()->getCookieNamed($name)) {
return decrypt(rawurldecode($cookie['value']));
return decrypt(rawurldecode($cookie['value']), $unserialize = false);
}
}
@@ -37,7 +37,7 @@ trait InteractsWithCookies
*/
public function plainCookie($name, $value = null, $expiry = null, array $options = [])
{
if ($value) {
if (! is_null($value)) {
return $this->addCookie($name, $value, $expiry, $options, false);
}
@@ -59,7 +59,7 @@ trait InteractsWithCookies
public function addCookie($name, $value, $expiry = null, array $options = [], $encrypt = true)
{
if ($encrypt) {
$value = encrypt($value);
$value = encrypt($value, $serialize = false);
}
if ($expiry instanceof DateTimeInterface) {

View File

@@ -2,11 +2,11 @@
namespace Laravel\Dusk\Concerns;
use Illuminate\Support\Str;
use Facebook\WebDriver\Interactions\WebDriverActions;
use Facebook\WebDriver\Remote\LocalFileDetector;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverKeys;
use Facebook\WebDriver\Remote\LocalFileDetector;
use Facebook\WebDriver\Interactions\WebDriverActions;
use Illuminate\Support\Str;
trait InteractsWithElements
{
@@ -39,11 +39,11 @@ trait InteractsWithElements
* @param string $element
* @return $this
*/
public function clickLink($link, $element = "a")
public function clickLink($link, $element = 'a')
{
$this->ensurejQueryIsAvailable();
$selector = addslashes(trim($this->resolver->format("{$element}:contains({$link})")));
$selector = addslashes(trim($this->resolver->format("{$element}:contains({$link}):visible")));
$this->driver->executeScript("jQuery.find(\"{$selector}\")[0].click();");
@@ -66,7 +66,7 @@ trait InteractsWithElements
$selector = $this->resolver->format($selector);
$this->driver->executeScript(
"document.querySelector('{$selector}').value = '{$value}';"
'document.querySelector('.json_encode($selector).').value = '.json_encode($value).';'
);
return $this;
@@ -144,6 +144,21 @@ trait InteractsWithElements
return $this;
}
/**
* Type the given value in the given field slowly.
*
* @param string $field
* @param string $value
* @param int $pause
* @return $this
*/
public function typeSlowly($field, $value, $pause = 100)
{
$this->clear($field)->appendSlowly($field, $value, $pause);
return $this;
}
/**
* Type the given value in the given field without clearing it.
*
@@ -158,6 +173,23 @@ trait InteractsWithElements
return $this;
}
/**
* Type the given value in the given field slowly without clearing it.
*
* @param string $field
* @param string $value
* @param int $pause
* @return $this
*/
public function appendSlowly($field, $value, $pause = 100)
{
foreach (str_split($value) as $char) {
$this->append($field, $char)->pause($pause);
}
return $this;
}
/**
* Clear the given field.
*
@@ -182,11 +214,15 @@ trait InteractsWithElements
{
$element = $this->resolver->resolveForSelection($field);
$options = $element->findElements(WebDriverBy::tagName('option'));
$options = $element->findElements(WebDriverBy::cssSelector('option:not([disabled])'));
if (is_null($value)) {
$options[array_rand($options)]->click();
} else {
if (is_bool($value)) {
$value = $value ? '1' : '0';
}
foreach ($options as $option) {
if ((string) $option->getAttribute('value') === (string) $value) {
$option->click();

View File

@@ -3,6 +3,7 @@
namespace Laravel\Dusk\Concerns;
use Facebook\WebDriver\Interactions\WebDriverActions;
use Facebook\WebDriver\WebDriverBy;
trait InteractsWithMouse
{
@@ -54,6 +55,21 @@ trait InteractsWithMouse
return $this;
}
/**
* Click the element at the given XPath expression.
*
* @param string $selector
* @return $this
*/
public function clickAtXPath($expression)
{
$this->driver
->findElement(WebDriverBy::xpath($expression))
->click();
return $this;
}
/**
* Perform a mouse click and hold the mouse button down.
*

View File

@@ -2,10 +2,10 @@
namespace Laravel\Dusk\Concerns;
use Facebook\WebDriver\Exception\NoSuchElementException;
use Facebook\WebDriver\Remote\RemoteWebElement;
use Illuminate\Support\Str;
use PHPUnit\Framework\Assert as PHPUnit;
use Facebook\WebDriver\Remote\RemoteWebElement;
use Facebook\WebDriver\Exception\NoSuchElementException;
trait MakesAssertions
{
@@ -41,233 +41,6 @@ trait MakesAssertions
return $this;
}
/**
* Assert that the current URL matches the given URL.
*
* @param string $url
* @return $this
*/
public function assertUrlIs($url)
{
$pattern = str_replace('\*', '.*', preg_quote($url, '/'));
$segments = parse_url($this->driver->getCurrentURL());
$currentUrl = sprintf(
'%s://%s%s%s',
$segments['scheme'],
$segments['host'],
array_get($segments, 'port', '') ? ':'.$segments['port'] : '',
array_get($segments, 'path', '')
);
PHPUnit::assertRegExp(
'/^'.$pattern.'$/u', $currentUrl,
"Actual URL [{$this->driver->getCurrentURL()}] does not equal expected URL [{$url}]."
);
return $this;
}
/**
* Assert that the current URL path matches the given pattern.
*
* @param string $path
* @return $this
*/
public function assertPathIs($path)
{
$pattern = preg_quote($path, '/');
$pattern = str_replace('\*', '.*', $pattern);
$actualPath = parse_url($this->driver->getCurrentURL())['path'];
PHPUnit::assertRegExp(
'/^'.$pattern.'$/u', $actualPath,
"Actual path [{$actualPath}] does not equal expected path [{$path}]."
);
return $this;
}
/**
* Assert that the current URL path begins with given path.
*
* @param string $path
* @return $this
*/
public function assertPathBeginsWith($path)
{
$actualPath = parse_url($this->driver->getCurrentURL())['path'];
PHPUnit::assertStringStartsWith(
$path, $actualPath,
"Actual path [{$actualPath}] does not begin with expected path [{$path}]."
);
return $this;
}
/**
* Assert that the current URL path does not match the given path.
*
* @param string $path
* @return $this
*/
public function assertPathIsNot($path)
{
$actualPath = parse_url($this->driver->getCurrentURL())['path'];
PHPUnit::assertNotEquals(
$path, $actualPath,
"Path [{$path}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL fragment matches the given pattern.
*
* @param string $fragment
* @return $this
*/
public function assertFragmentIs($fragment)
{
$pattern = preg_quote($fragment, '/');
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
PHPUnit::assertRegExp(
'/^'.str_replace('\*', '.*', $pattern).'$/u', $actualFragment,
"Actual fragment [{$actualFragment}] does not equal expected fragment [{$fragment}]."
);
return $this;
}
/**
* Assert that the current URL fragment begins with given fragment.
*
* @param string $fragment
* @return $this
*/
public function assertFragmentBeginsWith($fragment)
{
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
PHPUnit::assertStringStartsWith(
$fragment, $actualFragment,
"Actual fragment [$actualFragment] does not begin with expected fragment [$fragment]."
);
return $this;
}
/**
* Assert that the current URL fragment does not match the given fragment.
*
* @param string $fragment
* @return $this
*/
public function assertFragmentIsNot($fragment)
{
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
PHPUnit::assertNotEquals(
$fragment, $actualFragment,
"Fragment [{$fragment}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL path matches the given route.
*
* @param string $route
* @param array $parameters
* @return $this
*/
public function assertRouteIs($route, $parameters = [])
{
return $this->assertPathIs(route($route, $parameters, false));
}
/**
* Assert that a query string parameter is present and has a given value.
*
* @param string $name
* @param string $value
* @return $this
*/
public function assertQueryStringHas($name, $value = null)
{
$output = $this->assertHasQueryStringParameter($name);
if (is_null($value)) {
return $this;
}
PHPUnit::assertEquals(
$value, $output[$name],
"Query string parameter [{$name}] had value [{$output[$name]}], but expected [{$value}]."
);
return $this;
}
/**
* Assert that the given query string parameter is missing.
*
* @param string $name
* @return $this
*/
public function assertQueryStringMissing($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
if (! array_key_exists('query', $parsedUrl)) {
PHPUnit::assertTrue(true);
return $this;
}
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayNotHasKey(
$name, $output,
"Found unexpected query string parameter [{$name}] in [".$this->driver->getCurrentURL()."]."
);
return $this;
}
/**
* Assert that the given query string parameter is present.
*
* @param string $name
* @return array
*/
protected function assertHasQueryStringParameter($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
PHPUnit::assertArrayHasKey(
'query', $parsedUrl,
"Did not see expected query string in [".$this->driver->getCurrentURL()."]."
);
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayHasKey(
$name, $output,
"Did not see expected query string parameter [{$name}] in [".$this->driver->getCurrentURL()."]."
);
return $output;
}
/**
* Assert that the given cookie is present.
*
@@ -432,8 +205,8 @@ trait MakesAssertions
*/
public function assertSourceHas($code)
{
PHPUnit::assertContains(
$code, $this->driver->getPageSource(),
PHPUnit::assertTrue(
Str::contains($this->driver->getPageSource(), $code),
"Did not find expected source code [{$code}]"
);
@@ -448,8 +221,8 @@ trait MakesAssertions
*/
public function assertSourceMissing($code)
{
PHPUnit::assertNotContains(
$code, $this->driver->getPageSource(),
PHPUnit::assertFalse(
Str::contains($this->driver->getPageSource(), $code),
"Found unexpected source code [{$code}]"
);
@@ -682,8 +455,8 @@ JS;
/**
* Assert that the given array of values are available to be selected.
*
* @param string $field
* @param array $values
* @param string $field
* @param array $values
* @return $this
*/
public function assertSelectHasOptions($field, array $values)
@@ -696,7 +469,7 @@ JS;
PHPUnit::assertCount(
count($values), $options,
"Expected options [".implode(',', $values)."] for selection field [{$field}] to be available."
'Expected options ['.implode(',', $values)."] for selection field [{$field}] to be available."
);
return $this;
@@ -705,15 +478,15 @@ JS;
/**
* Assert that the given array of values are not available to be selected.
*
* @param string $field
* @param array $values
* @param string $field
* @param array $values
* @return $this
*/
public function assertSelectMissingOptions($field, array $values)
{
PHPUnit::assertCount(
0, $this->resolver->resolveSelectOptions($field, $values),
"Unexpected options [".implode(',', $values)."] for selection field [{$field}]."
'Unexpected options ['.implode(',', $values)."] for selection field [{$field}]."
);
return $this;
@@ -722,8 +495,8 @@ JS;
/**
* Assert that the given value is available to be selected on the given field.
*
* @param string $field
* @param string $value
* @param string $field
* @param string $value
* @return $this
*/
public function assertSelectHasOption($field, $value)
@@ -734,8 +507,8 @@ JS;
/**
* Assert that the given value is not available to be selected on the given field.
*
* @param string $field
* @param string $value
* @param string $field
* @param string $value
* @return $this
*/
public function assertSelectMissingOption($field, $value)
@@ -752,9 +525,11 @@ JS;
*/
public function selected($field, $value)
{
$element = $this->resolver->resolveForSelection($field);
$options = $this->resolver->resolveSelectOptions($field, (array) $value);
return (string) $element->getAttribute('value') === (string) $value;
return collect($options)->contains(function (RemoteWebElement $option) {
return $option->isSelected();
});
}
/**
@@ -773,6 +548,59 @@ JS;
return $this;
}
/**
* Assert that the element at the given selector has the given attribute value.
*
* @param string $selector
* @param string $attribute
* @param string $value
* @return $this
*/
public function assertAttribute($selector, $attribute, $value)
{
$fullSelector = $this->resolver->format($selector);
$actual = $this->resolver->findOrFail($selector)->getAttribute($attribute);
PHPUnit::assertNotNull(
$actual,
"Did not see expected attribute [{$attribute}] within element [{$fullSelector}]."
);
PHPUnit::assertEquals(
$value, $actual,
"Expected '$attribute' attribute [{$value}] does not equal actual value [$actual]."
);
return $this;
}
/**
* Assert that the element at the given selector has the given data attribute value.
*
* @param string $selector
* @param string $attribute
* @param string $value
* @return $this
*/
public function assertDataAttribute($selector, $attribute, $value)
{
return $this->assertAttribute($selector, 'data-'.$attribute, $value);
}
/**
* Assert that the element at the given selector has the given aria attribute value.
*
* @param string $selector
* @param string $attribute
* @param string $value
* @return $this
*/
public function assertAriaAttribute($selector, $attribute, $value)
{
return $this->assertAttribute($selector, 'aria-'.$attribute, $value);
}
/**
* Assert that the element with the given selector is visible.
*
@@ -854,7 +682,8 @@ JS;
* @param string $field
* @return $this
*/
public function assertEnabled($field) {
public function assertEnabled($field)
{
$element = $this->resolver->resolveForField($field);
PHPUnit::assertTrue(
@@ -871,7 +700,8 @@ JS;
* @param string $field
* @return $this
*/
public function assertDisabled($field) {
public function assertDisabled($field)
{
$element = $this->resolver->resolveForField($field);
PHPUnit::assertFalse(
@@ -882,13 +712,50 @@ JS;
return $this;
}
/**
* Assert that the given button is enabled.
*
* @param string $button
* @return $this
*/
public function assertButtonEnabled($button)
{
$element = $this->resolver->resolveForButtonPress($button);
PHPUnit::assertTrue(
$element->isEnabled(),
"Expected button [{$button}] to be enabled, but it wasn't."
);
return $this;
}
/**
* Assert that the given button is disabled.
*
* @param string $button
* @return $this
*/
public function assertButtonDisabled($button)
{
$element = $this->resolver->resolveForButtonPress($button);
PHPUnit::assertFalse(
$element->isEnabled(),
"Expected button [{$button}] to be disabled, but it wasn't."
);
return $this;
}
/**
* Assert that the given field is focused.
*
* @param string $field
* @return $this
*/
public function assertFocused($field) {
public function assertFocused($field)
{
$element = $this->resolver->resolveForField($field);
PHPUnit::assertTrue(
@@ -905,7 +772,8 @@ JS;
* @param string $field
* @return $this
*/
public function assertNotFocused($field) {
public function assertNotFocused($field)
{
$element = $this->resolver->resolveForField($field);
PHPUnit::assertFalse(
@@ -958,7 +826,10 @@ JS;
*/
public function assertVueContains($key, $value, $componentSelector = null)
{
PHPUnit::assertContains($value, $this->vueAttribute($componentSelector, $key));
$attribute = $this->vueAttribute($componentSelector, $key);
PHPUnit::assertIsArray($attribute, "The attribute for key [$key] is not an array.");
PHPUnit::assertContains($value, $attribute);
return $this;
}
@@ -974,7 +845,10 @@ JS;
*/
public function assertVueDoesNotContain($key, $value, $componentSelector = null)
{
PHPUnit::assertNotContains($value, $this->vueAttribute($componentSelector, $key));
$attribute = $this->vueAttribute($componentSelector, $key);
PHPUnit::assertIsArray($attribute, "The attribute for key [$key] is not an array.");
PHPUnit::assertNotContains($value, $attribute);
return $this;
}
@@ -991,7 +865,7 @@ JS;
$fullSelector = $this->resolver->format($componentSelector);
return $this->driver->executeScript(
"return document.querySelector('" . $fullSelector . "').__vue__." . $key
"return document.querySelector('".$fullSelector."').__vue__.".$key
);
}
}

View File

@@ -0,0 +1,353 @@
<?php
namespace Laravel\Dusk\Concerns;
use Illuminate\Support\Arr;
use PHPUnit\Framework\Assert as PHPUnit;
trait MakesUrlAssertions
{
/**
* Assert that the current URL matches the given URL.
*
* @param string $url
* @return $this
*/
public function assertUrlIs($url)
{
$pattern = str_replace('\*', '.*', preg_quote($url, '/'));
$segments = parse_url($this->driver->getCurrentURL());
$currentUrl = sprintf(
'%s://%s%s%s',
$segments['scheme'],
$segments['host'],
Arr::get($segments, 'port', '') ? ':'.$segments['port'] : '',
Arr::get($segments, 'path', '')
);
PHPUnit::assertRegExp(
'/^'.$pattern.'$/u', $currentUrl,
"Actual URL [{$this->driver->getCurrentURL()}] does not equal expected URL [{$url}]."
);
return $this;
}
/**
* Assert that the current scheme matches the given scheme.
*
* @param string $scheme
* @return $this
*/
public function assertSchemeIs($scheme)
{
$pattern = str_replace('\*', '.*', preg_quote($scheme, '/'));
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_SCHEME) ?? '';
PHPUnit::assertRegExp(
'/^'.$pattern.'$/u', $actual,
"Actual scheme [{$actual}] does not equal expected scheme [{$pattern}]."
);
return $this;
}
/**
* Assert that the current scheme does not match the given scheme.
*
* @param string $scheme
* @return $this
*/
public function assertSchemeIsNot($scheme)
{
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_SCHEME) ?? '';
PHPUnit::assertNotEquals(
$scheme, $actual,
"Scheme [{$scheme}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current host matches the given host.
*
* @param string $host
* @return $this
*/
public function assertHostIs($host)
{
$pattern = str_replace('\*', '.*', preg_quote($host, '/'));
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_HOST) ?? '';
PHPUnit::assertRegExp(
'/^'.$pattern.'$/u', $actual,
"Actual host [{$actual}] does not equal expected host [{$pattern}]."
);
return $this;
}
/**
* Assert that the current host does not match the given host.
*
* @param string $host
* @return $this
*/
public function assertHostIsNot($host)
{
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_HOST) ?? '';
PHPUnit::assertNotEquals(
$host, $actual,
"Host [{$host}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current port matches the given port.
*
* @param string $port
* @return $this
*/
public function assertPortIs($port)
{
$pattern = str_replace('\*', '.*', preg_quote($port, '/'));
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_PORT) ?? '';
PHPUnit::assertRegExp(
'/^'.$pattern.'$/u', $actual,
"Actual port [{$actual}] does not equal expected port [{$pattern}]."
);
return $this;
}
/**
* Assert that the current host does not match the given host.
*
* @param string $port
* @return $this
*/
public function assertPortIsNot($port)
{
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_PORT) ?? '';
PHPUnit::assertNotEquals(
$port, $actual,
"Port [{$port}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL path matches the given pattern.
*
* @param string $path
* @return $this
*/
public function assertPathIs($path)
{
$pattern = str_replace('\*', '.*', preg_quote($path, '/'));
$actualPath = parse_url($this->driver->getCurrentURL(), PHP_URL_PATH) ?? '';
PHPUnit::assertRegExp(
'/^'.$pattern.'$/u', $actualPath,
"Actual path [{$actualPath}] does not equal expected path [{$path}]."
);
return $this;
}
/**
* Assert that the current URL path begins with given path.
*
* @param string $path
* @return $this
*/
public function assertPathBeginsWith($path)
{
$actualPath = parse_url($this->driver->getCurrentURL(), PHP_URL_PATH) ?? '';
PHPUnit::assertStringStartsWith(
$path, $actualPath,
"Actual path [{$actualPath}] does not begin with expected path [{$path}]."
);
return $this;
}
/**
* Assert that the current URL path does not match the given path.
*
* @param string $path
* @return $this
*/
public function assertPathIsNot($path)
{
$actualPath = parse_url($this->driver->getCurrentURL(), PHP_URL_PATH) ?? '';
PHPUnit::assertNotEquals(
$path, $actualPath,
"Path [{$path}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL fragment matches the given pattern.
*
* @param string $fragment
* @return $this
*/
public function assertFragmentIs($fragment)
{
$pattern = preg_quote($fragment, '/');
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
PHPUnit::assertRegExp(
'/^'.str_replace('\*', '.*', $pattern).'$/u', $actualFragment,
"Actual fragment [{$actualFragment}] does not equal expected fragment [{$fragment}]."
);
return $this;
}
/**
* Assert that the current URL fragment begins with given fragment.
*
* @param string $fragment
* @return $this
*/
public function assertFragmentBeginsWith($fragment)
{
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
PHPUnit::assertStringStartsWith(
$fragment, $actualFragment,
"Actual fragment [$actualFragment] does not begin with expected fragment [$fragment]."
);
return $this;
}
/**
* Assert that the current URL fragment does not match the given fragment.
*
* @param string $fragment
* @return $this
*/
public function assertFragmentIsNot($fragment)
{
$actualFragment = (string) parse_url($this->driver->executeScript('return window.location.href;'), PHP_URL_FRAGMENT);
PHPUnit::assertNotEquals(
$fragment, $actualFragment,
"Fragment [{$fragment}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL path matches the given route.
*
* @param string $route
* @param array $parameters
* @return $this
*/
public function assertRouteIs($route, $parameters = [])
{
return $this->assertPathIs(route($route, $parameters, false));
}
/**
* Assert that a query string parameter is present and has a given value.
*
* @param string $name
* @param string $value
* @return $this
*/
public function assertQueryStringHas($name, $value = null)
{
$output = $this->assertHasQueryStringParameter($name);
if (is_null($value)) {
return $this;
}
$parsedOutputName = is_array($output[$name]) ? implode(',', $output[$name]) : $output[$name];
$parsedValue = is_array($value) ? implode(',', $value) : $value;
PHPUnit::assertEquals(
$value, $output[$name],
"Query string parameter [{$name}] had value [{$parsedOutputName}], but expected [{$parsedValue}]."
);
return $this;
}
/**
* Assert that the given query string parameter is missing.
*
* @param string $name
* @return $this
*/
public function assertQueryStringMissing($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
if (! array_key_exists('query', $parsedUrl)) {
PHPUnit::assertTrue(true);
return $this;
}
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayNotHasKey(
$name, $output,
"Found unexpected query string parameter [{$name}] in [".$this->driver->getCurrentURL().'].'
);
return $this;
}
/**
* Assert that the given query string parameter is present.
*
* @param string $name
* @return array
*/
protected function assertHasQueryStringParameter($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
PHPUnit::assertArrayHasKey(
'query', $parsedUrl,
'Did not see expected query string in ['.$this->driver->getCurrentURL().'].'
);
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayHasKey(
$name, $output,
"Did not see expected query string parameter [{$name}] in [".$this->driver->getCurrentURL().'].'
);
return $output;
}
}

View File

@@ -4,10 +4,10 @@ namespace Laravel\Dusk\Concerns;
use Closure;
use Exception;
use Throwable;
use ReflectionFunction;
use Laravel\Dusk\Browser;
use Illuminate\Support\Collection;
use Laravel\Dusk\Browser;
use ReflectionFunction;
use Throwable;
trait ProvidesBrowser
{
@@ -56,6 +56,7 @@ trait ProvidesBrowser
*
* @param \Closure $callback
* @return \Laravel\Dusk\Browser|void
*
* @throws \Exception
* @throws \Throwable
*/
@@ -85,6 +86,7 @@ trait ProvidesBrowser
*
* @param \Closure $callback
* @return array
*
* @throws \ReflectionException
*/
protected function createBrowsersFor(Closure $callback)
@@ -118,6 +120,7 @@ trait ProvidesBrowser
*
* @param \Closure $callback
* @return int
*
* @throws \ReflectionException
*/
protected function browsersNeededFor(Closure $callback)
@@ -134,7 +137,11 @@ trait ProvidesBrowser
protected function captureFailuresFor($browsers)
{
$browsers->each(function ($browser, $key) {
$name = str_replace('\\', '_', get_class($this)).'_'.$this->getName(false);
if (property_exists($browser, 'fitOnFailure') && $browser->fitOnFailure) {
$browser->fitContent();
}
$name = $this->getCallerName();
$browser->screenshot('failure-'.$name.'-'.$key);
});
@@ -149,7 +156,7 @@ trait ProvidesBrowser
protected function storeConsoleLogsFor($browsers)
{
$browsers->each(function ($browser, $key) {
$name = str_replace('\\', '_', get_class($this)).'_'.$this->getName(false);
$name = $this->getCallerName();
$browser->storeConsoleLog($name.'-'.$key);
});
@@ -184,6 +191,7 @@ trait ProvidesBrowser
* Create the remote web driver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*
* @throws \Exception
*/
protected function createWebDriver()
@@ -193,6 +201,16 @@ trait ProvidesBrowser
}, 50);
}
/**
* Get the browser caller name.
*
* @return string
*/
protected function getCallerName()
{
return str_replace('\\', '_', get_class($this)).'_'.$this->getName(false);
}
/**
* Create the RemoteWebDriver instance.
*

View File

@@ -2,13 +2,14 @@
namespace Laravel\Dusk\Concerns;
use Carbon\Carbon;
use Closure;
use Exception;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Facebook\WebDriver\Exception\TimeOutException;
use Facebook\WebDriver\Exception\NoSuchElementException;
use Facebook\WebDriver\Exception\TimeOutException;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
trait WaitsForElements
{
@@ -16,9 +17,10 @@ trait WaitsForElements
* Execute the given callback in a scoped browser once the selector is available.
*
* @param string $selector
* @param Closure $callback
* @param \Closure $callback
* @param int $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function whenAvailable($selector, Closure $callback, $seconds = null)
@@ -32,13 +34,16 @@ trait WaitsForElements
* @param string $selector
* @param int $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitFor($selector, $seconds = null)
{
$message = $this->formatTimeOutMessage('Waited %s seconds for selector', $selector);
return $this->waitUsing($seconds, 100, function () use ($selector) {
return $this->resolver->findOrFail($selector)->isDisplayed();
}, "Waited %s seconds for selector [{$selector}].");
}, $message);
}
/**
@@ -47,10 +52,13 @@ trait WaitsForElements
* @param string $selector
* @param int $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitUntilMissing($selector, $seconds = null)
{
$message = $this->formatTimeOutMessage('Waited %s seconds for removal of selector', $selector);
return $this->waitUsing($seconds, 100, function () use ($selector) {
try {
$missing = ! $this->resolver->findOrFail($selector)->isDisplayed();
@@ -59,22 +67,47 @@ trait WaitsForElements
}
return $missing;
}, "Waited %s seconds for removal of selector [{$selector}].");
}, $message);
}
/**
* Wait for the given text to be removed.
*
* @param string $text
* @param int $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitUntilMissingText($text, $seconds = null)
{
$text = Arr::wrap($text);
$message = $this->formatTimeOutMessage('Waited %s seconds for removal of text', implode("', '", $text));
return $this->waitUsing($seconds, 100, function () use ($text) {
return ! Str::contains($this->resolver->findOrFail('')->getText(), $text);
}, $message);
}
/**
* Wait for the given text to be visible.
*
* @param string $text
* @param array|string $text
* @param int $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitForText($text, $seconds = null)
{
$text = Arr::wrap($text);
$message = $this->formatTimeOutMessage('Waited %s seconds for text', implode("', '", $text));
return $this->waitUsing($seconds, 100, function () use ($text) {
return Str::contains($this->resolver->findOrFail('')->getText(), $text);
}, "Waited %s seconds for text [{$text}].");
}, $message);
}
/**
@@ -83,13 +116,16 @@ trait WaitsForElements
* @param string $link
* @param int $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitForLink($link, $seconds = null)
{
$message = $this->formatTimeOutMessage('Waited %s seconds for link', $link);
return $this->waitUsing($seconds, 100, function () use ($link) {
return $this->seeLink($link);
}, "Waited %s seconds for link [{$link}].");
}, $message);
}
/**
@@ -98,11 +134,14 @@ trait WaitsForElements
* @param string $path
* @param int $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitForLocation($path, $seconds = null)
{
return $this->waitUntil("window.location.pathname == '{$path}'", $seconds, "Waited %s seconds for location [{$path}].");
$message = $this->formatTimeOutMessage('Waited %s seconds for location', $path);
return $this->waitUntil("window.location.pathname == '{$path}'", $seconds, $message);
}
/**
@@ -112,6 +151,7 @@ trait WaitsForElements
* @param array $parameters
* @param int $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitForRoute($route, $parameters = [], $seconds = null)
@@ -126,6 +166,7 @@ trait WaitsForElements
* @param int $seconds
* @param string $message
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitUntil($script, $seconds = null, $message = null)
@@ -143,6 +184,40 @@ trait WaitsForElements
}, $message);
}
/**
* Wait until the Vue component's attribute at the given key has the given value.
*
* @param string $key
* @param string $value
* @param string|null $componentSelector
* @return $this
*/
public function waitUntilVue($key, $value, $componentSelector = null, $seconds = null)
{
$this->waitUsing($seconds, 100, function () use ($key, $value, $componentSelector) {
return $value == $this->vueAttribute($componentSelector, $key);
});
return $this;
}
/**
* Wait until the Vue component's attribute at the given key does not have the given value.
*
* @param string $key
* @param string $value
* @param string|null $componentSelector
* @return $this
*/
public function waitUntilVueIsNot($key, $value, $componentSelector = null, $seconds = null)
{
$this->waitUsing($seconds, 100, function () use ($key, $value, $componentSelector) {
return $value != $this->vueAttribute($componentSelector, $key);
});
return $this;
}
/**
* Wait for a JavaScript dialog to open.
*
@@ -163,14 +238,15 @@ trait WaitsForElements
/**
* Wait for the current page to reload.
*
* @param Closure $callback
* @param \Closure $callback
* @param int $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitForReload($callback = null, $seconds = null)
{
$token = str_random();
$token = Str::random();
$this->driver->executeScript("window['{$token}'] = {};");
@@ -188,9 +264,10 @@ trait WaitsForElements
*
* @param int $seconds
* @param int $interval
* @param Closure $callback
* @param \Closure $callback
* @param string|null $message
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeOutException
*/
public function waitUsing($seconds, $interval, Closure $callback, $message = null)
@@ -222,4 +299,16 @@ trait WaitsForElements
return $this;
}
/**
* Prepare custom TimeOutException message for sprintf().
*
* @param string $message
* @param string $expected
* @return string
*/
protected function formatTimeOutMessage($message, $expected)
{
return $message.' ['.str_replace('%', '%%', $expected).'].';
}
}

View File

@@ -0,0 +1,251 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\Command;
use Laravel\Dusk\OperatingSystem;
use ZipArchive;
/**
* @copyright Originally created by Jonas Staudenmeir: https://github.com/staudenmeir/dusk-updater
*/
class ChromeDriverCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk:chrome-driver {version?}
{--all : Install a ChromeDriver binary for every OS}
{--proxy= : The proxy to download the binary through (example: "tcp://127.0.0.1:9000")}
{--ssl-no-verify : Bypass SSL certificate verification when installing through a proxy}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Install the ChromeDriver binary';
/**
* URL to the latest stable release version.
*
* @var string
*/
protected $latestVersionUrl = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE';
/**
* URL to the latest release version for a major Chrome version.
*
* @var string
*/
protected $versionUrl = 'https://chromedriver.storage.googleapis.com/LATEST_RELEASE_%d';
/**
* URL to the ChromeDriver download.
*
* @var string
*/
protected $downloadUrl = 'https://chromedriver.storage.googleapis.com/%s/chromedriver_%s.zip';
/**
* Download slugs for the available operating systems.
*
* @var array
*/
protected $slugs = [
'linux' => 'linux64',
'mac' => 'mac64',
'win' => 'win32',
];
/**
* The legacy versions for the ChromeDriver.
*
* @var array
*/
protected $legacyVersions = [
43 => '2.20',
44 => '2.20',
45 => '2.20',
46 => '2.21',
47 => '2.21',
48 => '2.21',
49 => '2.22',
50 => '2.22',
51 => '2.23',
52 => '2.24',
53 => '2.26',
54 => '2.27',
55 => '2.28',
56 => '2.29',
57 => '2.29',
58 => '2.31',
59 => '2.32',
60 => '2.33',
61 => '2.34',
62 => '2.35',
63 => '2.36',
64 => '2.37',
65 => '2.38',
66 => '2.40',
67 => '2.41',
68 => '2.42',
69 => '2.44',
];
/**
* Path to the bin directory.
*
* @var string
*/
protected $directory = __DIR__.'/../../bin/';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$version = $this->version();
$all = $this->option('all');
$currentOS = OperatingSystem::id();
foreach ($this->slugs as $os => $slug) {
if ($all || ($os === $currentOS)) {
$archive = $this->download($version, $slug);
$binary = $this->extract($archive);
$this->rename($binary, $os);
}
}
$message = 'ChromeDriver %s successfully installed for version %s.';
$this->info(sprintf($message, $all ? 'binaries' : 'binary', $version));
}
/**
* Get the desired ChromeDriver version.
*
* @return string
*/
protected function version()
{
$version = $this->argument('version');
if (! $version) {
return $this->latestVersion();
}
if (! ctype_digit($version)) {
return $version;
}
$version = (int) $version;
if ($version < 70) {
return $this->legacyVersions[$version];
}
return trim($this->getUrl(
sprintf($this->versionUrl, $version)
));
}
/**
* Get the latest stable ChromeDriver version.
*
* @return string
*/
protected function latestVersion()
{
return trim(file_get_contents($this->latestVersionUrl));
}
/**
* Download the ChromeDriver archive.
*
* @param string $version
* @param string $slug
* @return string
*/
protected function download($version, $slug)
{
$url = sprintf($this->downloadUrl, $version, $slug);
file_put_contents(
$archive = $this->directory.'chromedriver.zip',
$this->getUrl($url)
);
return $archive;
}
/**
* Extract the ChromeDriver binary from the archive and delete the archive.
*
* @param string $archive
* @return string
*/
protected function extract($archive)
{
$zip = new ZipArchive;
$zip->open($archive);
$zip->extractTo($this->directory);
$binary = $zip->getNameIndex(0);
$zip->close();
unlink($archive);
return $binary;
}
/**
* Rename the ChromeDriver binary and make it executable.
*
* @param string $binary
* @param string $os
* @return void
*/
protected function rename($binary, $os)
{
$newName = str_replace('chromedriver', 'chromedriver-'.$os, $binary);
rename($this->directory.$binary, $this->directory.$newName);
chmod($this->directory.$newName, 0755);
}
/**
* Get the contents of a URL using the 'proxy' and 'ssl-no-verify' command options.
*
* @param string $url
* @return string|bool
*/
protected function getUrl(string $url)
{
$contextOptions = [];
if ($this->option('proxy')) {
$contextOptions['http'] = ['proxy' => $this->option('proxy'), 'request_fulluri' => true];
}
if ($this->option('ssl-no-verify')) {
$contextOptions['ssl'] = ['verify_peer' => false];
}
$streamContext = stream_context_create($contextOptions);
return file_get_contents($url, false, $streamContext);
}
}

View File

@@ -3,6 +3,7 @@
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
class ComponentCommand extends GeneratorCommand
{
@@ -45,7 +46,7 @@ class ComponentCommand extends GeneratorCommand
*/
protected function getPath($name)
{
$name = str_replace_first($this->rootNamespace(), '', $name);
$name = Str::replaceFirst($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}

View File

@@ -3,11 +3,12 @@
namespace Laravel\Dusk\Console;
use Dotenv\Dotenv;
use Illuminate\Support\Str;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Process;
class DuskCommand extends Command
{
@@ -28,7 +29,7 @@ class DuskCommand extends Command
/**
* Indicates if the project has its own PHPUnit configuration.
*
* @var boolean
* @var bool
*/
protected $hasPhpUnitConfiguration = false;
@@ -68,9 +69,15 @@ class DuskCommand extends Command
$this->output->writeln('Warning: '.$e->getMessage());
}
return $process->run(function ($type, $line) {
$this->output->write($line);
});
try {
return $process->run(function ($type, $line) {
$this->output->write($line);
});
} catch (ProcessSignaledException $e) {
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
throw $e;
}
}
});
}
@@ -81,6 +88,10 @@ class DuskCommand extends Command
*/
protected function binary()
{
if ('phpdbg' === PHP_SAPI) {
return [PHP_BINARY, '-qrr', 'vendor/phpunit/phpunit/phpunit'];
}
return [PHP_BINARY, 'vendor/phpunit/phpunit/phpunit'];
}
@@ -96,18 +107,28 @@ class DuskCommand extends Command
return ! Str::startsWith($option, '--env=');
}));
return array_merge(['-c', base_path('phpunit.dusk.xml')], $options);
if (! file_exists($file = base_path('phpunit.dusk.xml'))) {
$file = base_path('phpunit.dusk.xml.dist');
}
return array_merge(['-c', $file], $options);
}
/**
* Purge the failure screenshots
* Purge the failure screenshots.
*
* @return void
*/
protected function purgeScreenshots()
{
$path = base_path('tests/Browser/screenshots');
if (! is_dir($path)) {
return;
}
$files = Finder::create()->files()
->in(base_path('tests/Browser/screenshots'))
->in($path)
->name('failure-*');
foreach ($files as $file) {
@@ -122,8 +143,14 @@ class DuskCommand extends Command
*/
protected function purgeConsoleLogs()
{
$path = base_path('tests/Browser/console');
if (! is_dir($path)) {
return;
}
$files = Finder::create()->files()
->in(base_path('tests/Browser/console'))
->in($path)
->name('*.log');
foreach ($files as $file) {
@@ -138,6 +165,22 @@ class DuskCommand extends Command
* @return mixed
*/
protected function withDuskEnvironment($callback)
{
$this->setupDuskEnvironment();
try {
return $callback();
} finally {
$this->teardownDuskEnviroment();
}
}
/**
* Setup the Dusk environment.
*
* @return void
*/
protected function setupDuskEnvironment()
{
if (file_exists(base_path($this->duskFile()))) {
if (file_get_contents(base_path('.env')) !== file_get_contents(base_path($this->duskFile()))) {
@@ -149,13 +192,7 @@ class DuskCommand extends Command
$this->writeConfiguration();
return tap($callback(), function () {
$this->removeConfiguration();
if (file_exists(base_path($this->duskFile())) && file_exists(base_path('.env.backup'))) {
$this->restoreEnvironment();
}
});
$this->setupSignalHandler();
}
/**
@@ -170,18 +207,6 @@ class DuskCommand extends Command
copy(base_path($this->duskFile()), base_path('.env'));
}
/**
* Restore the backed-up environment file.
*
* @return void
*/
protected function restoreEnvironment()
{
copy(base_path('.env.backup'), base_path('.env'));
unlink(base_path('.env.backup'));
}
/**
* Refresh the current environment variables.
*
@@ -189,7 +214,21 @@ class DuskCommand extends Command
*/
protected function refreshEnvironment()
{
(new Dotenv(base_path()))->overload();
// BC fix to support Dotenv ^2.2...
if (! method_exists(Dotenv::class, 'create')) {
(new Dotenv(base_path()))->overload();
return;
}
// BC fix to support Dotenv ^3.0...
if (! method_exists(Dotenv::class, 'createMutable')) {
Dotenv::create(base_path())->overload();
return;
}
Dotenv::createMutable(base_path())->load();
}
/**
@@ -199,10 +238,43 @@ class DuskCommand extends Command
*/
protected function writeConfiguration()
{
if (! file_exists($file = base_path('phpunit.dusk.xml'))) {
if (! file_exists($file = base_path('phpunit.dusk.xml')) &&
! file_exists(base_path('phpunit.dusk.xml.dist'))) {
copy(realpath(__DIR__.'/../../stubs/phpunit.xml'), $file);
} else {
$this->hasPhpUnitConfiguration = true;
return;
}
$this->hasPhpUnitConfiguration = true;
}
/**
* Setup the SIGINT signal handler for CTRL+C exits.
*
* @return void
*/
protected function setupSignalHandler()
{
if (extension_loaded('pcntl')) {
pcntl_async_signals(true);
pcntl_signal(SIGINT, function () {
$this->teardownDuskEnviroment();
});
}
}
/**
* Restore the original environment.
*
* @return void
*/
protected function teardownDuskEnviroment()
{
$this->removeConfiguration();
if (file_exists(base_path($this->duskFile())) && file_exists(base_path('.env.backup'))) {
$this->restoreEnvironment();
}
}
@@ -213,11 +285,23 @@ class DuskCommand extends Command
*/
protected function removeConfiguration()
{
if (! $this->hasPhpUnitConfiguration) {
unlink(base_path('phpunit.dusk.xml'));
if (! $this->hasPhpUnitConfiguration && file_exists($file = base_path('phpunit.dusk.xml'))) {
unlink($file);
}
}
/**
* Restore the backed-up environment file.
*
* @return void
*/
protected function restoreEnvironment()
{
copy(base_path('.env.backup'), base_path('.env'));
unlink(base_path('.env.backup'));
}
/**
* Get the name of the Dusk file for the environment.
*

View File

@@ -0,0 +1,33 @@
<?php
namespace Laravel\Dusk\Console;
class DuskFailsCommand extends DuskCommand
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk:fails {--without-tty : Disable output to TTY}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the failing Dusk tests from the last run and stop on failure';
/**
* Get the array of arguments for running PHPUnit.
*
* @param array $options
* @return array
*/
protected function phpunitArguments($options)
{
return array_unique(array_merge(parent::phpunitArguments($options), [
'--cache-result', '--order-by=defects', '--stop-on-failure',
]));
}
}

View File

@@ -11,7 +11,9 @@ class InstallCommand extends Command
*
* @var string
*/
protected $signature = 'dusk:install';
protected $signature = 'dusk:install
{--proxy= : The proxy to download the binary through (example: "tcp://127.0.0.1:9000")}
{--ssl-no-verify : Bypass SSL certificate verification when installing through a proxy}';
/**
* The console command description.
@@ -20,16 +22,6 @@ class InstallCommand extends Command
*/
protected $description = 'Install Dusk into the application';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
@@ -67,6 +59,20 @@ class InstallCommand extends Command
}
$this->info('Dusk scaffolding installed successfully.');
$this->comment('Downloading ChromeDriver binaries...');
$driverCommandArgs = ['--all' => true];
if ($this->option('proxy')) {
$driverCommandArgs['--proxy'] = $this->option('proxy');
}
if ($this->option('ssl-no-verify')) {
$driverCommandArgs['--ssl-no-verify'] = true;
}
$this->call('dusk:chrome-driver', $driverCommandArgs);
}
/**

View File

@@ -3,6 +3,7 @@
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
class MakeCommand extends GeneratorCommand
{
@@ -45,7 +46,7 @@ class MakeCommand extends GeneratorCommand
*/
protected function getPath($name)
{
$name = str_replace_first($this->rootNamespace(), '', $name);
$name = Str::replaceFirst($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}

View File

@@ -3,6 +3,7 @@
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
class PageCommand extends GeneratorCommand
{
@@ -45,7 +46,7 @@ class PageCommand extends GeneratorCommand
*/
protected function getPath($name)
{
$name = str_replace_first($this->rootNamespace(), '', $name);
$name = Str::replaceFirst($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}

View File

@@ -24,6 +24,8 @@ class Dusk
*
* @param array $options
* @return bool
*
* @throws \InvalidArgumentException
*/
protected static function duskEnvironment($options)
{
@@ -36,7 +38,7 @@ class Dusk
}
if (! is_array($options['environments'])) {
throw new InvalidArgumentException("Dusk environments must be listed as an array.");
throw new InvalidArgumentException('Dusk environments must be listed as an array.');
}
return app()->environment(...$options['environments']);

View File

@@ -2,7 +2,6 @@
namespace Laravel\Dusk;
use Exception;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
@@ -15,41 +14,42 @@ class DuskServiceProvider extends ServiceProvider
*/
public function boot()
{
Route::get('/_dusk/login/{userId}/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@login',
]);
if (! $this->app->environment('production')) {
Route::get('/_dusk/login/{userId}/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@login',
]);
Route::get('/_dusk/logout/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@logout',
]);
Route::get('/_dusk/logout/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@logout',
]);
Route::get('/_dusk/user/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@user',
]);
Route::get('/_dusk/user/{guard?}', [
'middleware' => 'web',
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@user',
]);
}
}
/**
* Register any package services.
*
* @return void
* @throws Exception
*
* @throws \Exception
*/
public function register()
{
if ($this->app->environment('production')) {
throw new Exception('It is unsafe to run Dusk in production.');
}
if ($this->app->runningInConsole()) {
$this->commands([
Console\InstallCommand::class,
Console\DuskCommand::class,
Console\DuskFailsCommand::class,
Console\MakeCommand::class,
Console\PageCommand::class,
Console\ComponentCommand::class,
Console\ChromeDriverCommand::class,
]);
}
}

View File

@@ -3,10 +3,10 @@
namespace Laravel\Dusk;
use Exception;
use Illuminate\Support\Str;
use InvalidArgumentException;
use Facebook\WebDriver\WebDriverBy;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
class ElementResolver
{
@@ -43,7 +43,7 @@ class ElementResolver
'findButtonBySelector',
'findButtonByName',
'findButtonByValue',
'findButtonByText'
'findButtonByText',
];
/**
@@ -77,6 +77,7 @@ class ElementResolver
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForTyping($field)
@@ -86,7 +87,7 @@ class ElementResolver
}
return $this->firstOrFail([
$field, "input[name='{$field}']", "textarea[name='{$field}']"
$field, "input[name='{$field}']", "textarea[name='{$field}']",
]);
}
@@ -95,6 +96,7 @@ class ElementResolver
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForSelection($field)
@@ -104,16 +106,17 @@ class ElementResolver
}
return $this->firstOrFail([
$field, "select[name='{$field}']"
$field, "select[name='{$field}']",
]);
}
/**
* Resolve all the options with the given value on the select field.
*
* @param string $field
* @param array $values
* @param string $field
* @param array $values
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
*
* @throws \Exception
*/
public function resolveSelectOptions($field, array $values)
@@ -136,7 +139,9 @@ class ElementResolver
* @param string $field
* @param string $value
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
* @throws \InvalidArgumentException
*/
public function resolveForRadioSelection($field, $value = null)
{
@@ -151,16 +156,17 @@ class ElementResolver
}
return $this->firstOrFail([
$field, "input[type=radio][name='{$field}'][value='{$value}']"
$field, "input[type=radio][name='{$field}'][value='{$value}']",
]);
}
/**
* Resolve the element for a given checkbox "field".
*
* @param string $field
* @param string|null $field
* @param string $value
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForChecking($field, $value = null)
@@ -169,14 +175,18 @@ class ElementResolver
return $element;
}
$selector = "input[type=checkbox][name='{$field}']";
$selector = 'input[type=checkbox]';
if (! is_null($field)) {
$selector .= "[name='{$field}']";
}
if (! is_null($value)) {
$selector .= "[value='{$value}']";
}
return $this->firstOrFail([
$field, $selector
$field, $selector,
]);
}
@@ -185,6 +195,7 @@ class ElementResolver
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForAttachment($field)
@@ -194,7 +205,7 @@ class ElementResolver
}
return $this->firstOrFail([
$field, "input[type=file][name='{$field}']"
$field, "input[type=file][name='{$field}']",
]);
}
@@ -203,6 +214,7 @@ class ElementResolver
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForField($field)
@@ -213,7 +225,7 @@ class ElementResolver
return $this->firstOrFail([
$field, "input[name='{$field}']", "textarea[name='{$field}']",
"select[name='{$field}']", "button[name='{$field}']"
"select[name='{$field}']", "button[name='{$field}']",
]);
}
@@ -222,6 +234,8 @@ class ElementResolver
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \InvalidArgumentException
*/
public function resolveForButtonPress($button)
{
@@ -272,7 +286,7 @@ class ElementResolver
*/
protected function findButtonByValue($button)
{
foreach ($this->all("input[type=submit]") as $element) {
foreach ($this->all('input[type=submit]') as $element) {
if ($element->getAttribute('value') === $button) {
return $element;
}
@@ -327,6 +341,7 @@ class ElementResolver
*
* @param array $selectors
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function firstOrFail($selectors)
@@ -394,7 +409,7 @@ class ElementResolver
array_keys($sortedElements), array_values($sortedElements), $originalSelector = $selector
);
if (starts_with($selector, '@') && $selector === $originalSelector) {
if (Str::startsWith($selector, '@') && $selector === $originalSelector) {
$selector = '[dusk="'.explode('@', $selector)[1].'"]';
}

View File

@@ -3,6 +3,7 @@
namespace Laravel\Dusk\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
class UserController
{
@@ -31,6 +32,7 @@ class UserController
*
* @param string $userId
* @param string $guard
* @return void
*/
public function login($userId, $guard = null)
{
@@ -38,7 +40,7 @@ class UserController
$provider = Auth::guard($guard)->getProvider();
$user = str_contains($userId, '@')
$user = Str::contains($userId, '@')
? $provider->retrieveByCredentials(['email' => $userId])
: $provider->retrieveById($userId);
@@ -49,6 +51,7 @@ class UserController
* Log the user out of the application.
*
* @param string $guard
* @return void
*/
public function logout($guard = null)
{

View File

@@ -0,0 +1,38 @@
<?php
namespace Laravel\Dusk;
use Illuminate\Support\Str;
class OperatingSystem
{
/**
* Returns the current OS identifier.
*
* @return string
*/
public static function id()
{
return static::onWindows() ? 'win' : (static::onMac() ? 'mac' : 'linux');
}
/**
* Determine if the operating system is Windows or Windows Subsystem for Linux.
*
* @return bool
*/
public static function onWindows()
{
return PHP_OS === 'WINNT' || Str::contains(php_uname(), 'Microsoft');
}
/**
* Determine if the operating system is macOS.
*
* @return bool
*/
public static function onMac()
{
return PHP_OS === 'Darwin';
}
}

View File

@@ -3,22 +3,22 @@
namespace Laravel\Dusk;
use Exception;
use Laravel\Dusk\Chrome\SupportsChrome;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Illuminate\Foundation\Testing\TestCase as FoundationTestCase;
use Laravel\Dusk\Chrome\SupportsChrome;
use Laravel\Dusk\Concerns\ProvidesBrowser;
abstract class TestCase extends FoundationTestCase
{
use Concerns\ProvidesBrowser,
SupportsChrome;
use ProvidesBrowser, SupportsChrome;
/**
* Register the base URL with Dusk.
*
* @return void
*/
protected function setUp()
protected function setUp(): void
{
parent::setUp();
@@ -28,6 +28,8 @@ abstract class TestCase extends FoundationTestCase
Browser::$storeConsoleLogAt = base_path('tests/Browser/console');
Browser::$storeSourceAt = base_path('tests/Browser/source');
Browser::$userResolver = function () {
return $this->user();
};
@@ -59,10 +61,11 @@ abstract class TestCase extends FoundationTestCase
* Return the default user to authenticate.
*
* @return \App\User|int|null
*
* @throws \Exception
*/
protected function user()
{
throw new Exception("User resolver has not been set.");
throw new Exception('User resolver has not been set.');
}
}

View File

@@ -2,10 +2,10 @@
namespace Tests;
use Laravel\Dusk\TestCase as BaseTestCase;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Laravel\Dusk\TestCase as BaseTestCase;
abstract class DuskTestCase extends BaseTestCase
{
@@ -31,7 +31,8 @@ abstract class DuskTestCase extends BaseTestCase
{
$options = (new ChromeOptions)->addArguments([
'--disable-gpu',
'--headless'
'--headless',
'--window-size=1920,1080',
]);
return RemoteWebDriver::create(

View File

@@ -2,9 +2,9 @@
namespace Tests\Browser;
use Tests\DuskTestCase;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{

View File

@@ -19,7 +19,7 @@ class HomePage extends Page
/**
* Assert that the browser is on the page.
*
* @param Browser $browser
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)