dependencies-upgrade
This commit is contained in:
240
vendor/laravel/dusk/CHANGELOG.md
vendored
240
vendor/laravel/dusk/CHANGELOG.md
vendored
@@ -1,240 +0,0 @@
|
||||
# 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)
|
||||
|
||||
### Added
|
||||
- Added `illuminate/console` as dependency ([#232](https://github.com/laravel/dusk/pull/232))
|
||||
- Added security measurement against registering Dusk on production ([#229](https://github.com/laravel/dusk/pull/229))
|
||||
- Added `PHP_BINARY` constant to the list of PHP's executable binaries ([#240](https://github.com/laravel/dusk/pull/240))
|
||||
|
||||
### Changed
|
||||
- Changed `propagateScaffoldingToBrowser()` to `setUp()` for compatibility with PHPUnit ~6.0 ([#227](https://github.com/laravel/dusk/pull/227))
|
||||
- Changed `selected()` comparison to always cast the value to string ([#239](https://github.com/laravel/dusk/pull/239))
|
||||
|
||||
### Fixed
|
||||
- No longer throws exception when Tty is not available ([#226](https://github.com/laravel/dusk/pull/226))
|
||||
- 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
|
||||
- Added automated tests for HTML elements identified by strings with a colon ([#214](https://github.com/laravel/dusk/pull/214))
|
||||
|
||||
### Fixed
|
||||
- Support for colon on HTML id tag ([#214](https://github.com/laravel/dusk/pull/214))
|
||||
|
||||
|
||||
## v1.0.11 (2017-03-20)
|
||||
|
||||
### Added
|
||||
- Added `assertSelectHasOptions()`, `assertSelectMissingOptions()`, `assertSelectHasOption()` and `and assertSelectMissingOption()` ([#195](https://github.com/laravel/dusk/pull/195))
|
||||
- Added purge console logs before starting tests ([#193](https://github.com/laravel/dusk/pull/193))
|
||||
- Added `assertPathIsNot()` ([#183](https://github.com/laravel/dusk/pull/183))
|
||||
- Added support for back button ([#187](https://github.com/laravel/dusk/pull/187))
|
||||
- Added `waitForLocation()` to allow waiting on `window.location` to be changed ([#176](https://github.com/laravel/dusk/pull/176))
|
||||
|
||||
### Changed
|
||||
- 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))
|
8
vendor/laravel/dusk/README.md
vendored
8
vendor/laravel/dusk/README.md
vendored
@@ -1,10 +1,10 @@
|
||||
<p align="center"><img src="https://laravel.com/assets/img/components/logo-dusk.svg"></p>
|
||||
<p align="center"><img src="/art/logo.svg" alt="Logo Laravel Dusk"></p>
|
||||
|
||||
<p align="center">
|
||||
<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>
|
||||
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://img.shields.io/packagist/dt/laravel/dusk" alt="Total Downloads"></a>
|
||||
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://img.shields.io/packagist/v/laravel/dusk" alt="Latest Stable Version"></a>
|
||||
<a href="https://packagist.org/packages/laravel/dusk"><img src="https://img.shields.io/packagist/l/laravel/dusk" alt="License"></a>
|
||||
</p>
|
||||
|
||||
## Introduction
|
||||
|
17
vendor/laravel/dusk/UPGRADE.md
vendored
17
vendor/laravel/dusk/UPGRADE.md
vendored
@@ -1,17 +0,0 @@
|
||||
# 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.
|
1
vendor/laravel/dusk/art/logo.svg
vendored
Normal file
1
vendor/laravel/dusk/art/logo.svg
vendored
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.4 KiB |
23
vendor/laravel/dusk/composer.json
vendored
23
vendor/laravel/dusk/composer.json
vendored
@@ -10,21 +10,22 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1.0",
|
||||
"php": "^7.2|^8.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"
|
||||
"php-webdriver/webdriver": "^1.9.0",
|
||||
"nesbot/carbon": "^2.0",
|
||||
"illuminate/console": "^6.0|^7.0|^8.0|^9.0",
|
||||
"illuminate/support": "^6.0|^7.0|^8.0|^9.0",
|
||||
"symfony/console": "^4.3|^5.0|^6.0",
|
||||
"symfony/finder": "^4.3|^5.0|^6.0",
|
||||
"symfony/process": "^4.3|^5.0|^6.0",
|
||||
"vlucas/phpdotenv": "^3.0|^4.0|^5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"phpunit/phpunit": "^7.5|^8.0"
|
||||
"phpunit/phpunit": "^7.5.15|^8.4|^9.0",
|
||||
"orchestra/testbench": "^4.16|^5.17.1|^6.12.1|^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
|
||||
@@ -41,7 +42,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.0-dev"
|
||||
"dev-master": "6.x-dev"
|
||||
},
|
||||
"laravel": {
|
||||
"providers": [
|
||||
|
140
vendor/laravel/dusk/src/Browser.php
vendored
140
vendor/laravel/dusk/src/Browser.php
vendored
@@ -39,6 +39,38 @@ class Browser
|
||||
*/
|
||||
public static $storeScreenshotsAt;
|
||||
|
||||
/**
|
||||
* The common screen sizes to use for responsive screenshots.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $responsiveScreenSizes = [
|
||||
'xs' => [
|
||||
'width' => 360,
|
||||
'height' => 640,
|
||||
],
|
||||
'sm' => [
|
||||
'width' => 640,
|
||||
'height' => 360,
|
||||
],
|
||||
'md' => [
|
||||
'width' => 768,
|
||||
'height' => 1024,
|
||||
],
|
||||
'lg' => [
|
||||
'width' => 1024,
|
||||
'height' => 768,
|
||||
],
|
||||
'xl' => [
|
||||
'width' => 1280,
|
||||
'height' => 1024,
|
||||
],
|
||||
'2xl' => [
|
||||
'width' => 1536,
|
||||
'height' => 864,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The directory that will contain any console logs.
|
||||
*
|
||||
@@ -116,7 +148,7 @@ class Browser
|
||||
* Create a browser instance.
|
||||
*
|
||||
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
|
||||
* @param \Laravel\Dusk\ElementResolver $resolver
|
||||
* @param \Laravel\Dusk\ElementResolver|null $resolver
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($driver, $resolver = null)
|
||||
@@ -174,6 +206,18 @@ class Browser
|
||||
return $this->visit(route($route, $parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse to the "about:blank" page.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function blank()
|
||||
{
|
||||
$this->driver->navigate()->to('about:blank');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current page object.
|
||||
*
|
||||
@@ -233,6 +277,18 @@ class Browser
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to the next page.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function forward()
|
||||
{
|
||||
$this->driver->navigate()->forward();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximize the browser window.
|
||||
*
|
||||
@@ -272,7 +328,7 @@ class Browser
|
||||
|
||||
$html = $this->driver->findElement(WebDriverBy::tagName('html'));
|
||||
|
||||
if (! empty($html) && ($html->getSize()->getWidth() <= 0 || $html->getSize()->getHeight() <= 0)) {
|
||||
if (! empty($html) && $html->getSize()->getWidth() > 0 && $html->getSize()->getHeight() > 0) {
|
||||
$this->resize($html->getSize()->getWidth(), $html->getSize()->getHeight());
|
||||
}
|
||||
|
||||
@@ -319,6 +375,21 @@ class Browser
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll element into view at the given selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function scrollIntoView($selector)
|
||||
{
|
||||
$selector = addslashes($this->resolver->format($selector));
|
||||
|
||||
$this->driver->executeScript("document.querySelector(\"$selector\").scrollIntoView();");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll screen to element at the given selector.
|
||||
*
|
||||
@@ -357,6 +428,26 @@ class Browser
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a series of screenshots at different browser sizes to emulate different devices.
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function responsiveScreenshots($name)
|
||||
{
|
||||
if (substr($name, -1) !== '/') {
|
||||
$name .= '-';
|
||||
}
|
||||
|
||||
foreach (static::$responsiveScreenSizes as $device => $size) {
|
||||
$this->resize($size['width'], $size['height'])
|
||||
->screenshot("$name$device");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the console output with the given name.
|
||||
*
|
||||
@@ -418,7 +509,7 @@ class Browser
|
||||
/**
|
||||
* Execute a Closure with a scoped browser instance.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string|\Laravel\Dusk\Component $selector
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
@@ -430,7 +521,7 @@ class Browser
|
||||
/**
|
||||
* Execute a Closure with a scoped browser instance.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string|\Laravel\Dusk\Component $selector
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
@@ -453,6 +544,47 @@ class Browser
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Closure outside of the current browser scope.
|
||||
*
|
||||
* @param string|\Laravel\Dusk\Component $selector
|
||||
* @param \Closure $callback
|
||||
* @return $this
|
||||
*/
|
||||
public function elsewhere($selector, Closure $callback)
|
||||
{
|
||||
$browser = new static(
|
||||
$this->driver, new ElementResolver($this->driver, 'body '.$selector)
|
||||
);
|
||||
|
||||
if ($this->page) {
|
||||
$browser->onWithoutAssert($this->page);
|
||||
}
|
||||
|
||||
if ($selector instanceof Component) {
|
||||
$browser->onComponent($selector, $this->resolver);
|
||||
}
|
||||
|
||||
call_user_func($callback, $browser);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a Closure outside of the current browser scope when the selector is available.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param \Closure $callback
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function elsewhereWhenAvailable($selector, Closure $callback, $seconds = null)
|
||||
{
|
||||
return $this->elsewhere('', function ($browser) use ($selector, $callback, $seconds) {
|
||||
$browser->whenAvailable($selector, $callback, $seconds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current component state.
|
||||
*
|
||||
|
30
vendor/laravel/dusk/src/Chrome/ChromeProcess.php
vendored
30
vendor/laravel/dusk/src/Chrome/ChromeProcess.php
vendored
@@ -18,7 +18,7 @@ class ChromeProcess
|
||||
/**
|
||||
* Create a new ChromeProcess instance.
|
||||
*
|
||||
* @param string $driver
|
||||
* @param string|null $driver
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
@@ -44,13 +44,15 @@ class ChromeProcess
|
||||
return $this->process($arguments);
|
||||
}
|
||||
|
||||
if ($this->onWindows()) {
|
||||
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-win.exe');
|
||||
} elseif ($this->onMac()) {
|
||||
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-mac');
|
||||
} else {
|
||||
$this->driver = realpath(__DIR__.'/../../bin/chromedriver-linux');
|
||||
}
|
||||
$filenames = [
|
||||
'linux' => 'chromedriver-linux',
|
||||
'mac' => 'chromedriver-mac',
|
||||
'mac-intel' => 'chromedriver-mac-intel',
|
||||
'mac-arm' => 'chromedriver-mac-arm',
|
||||
'win' => 'chromedriver-win.exe',
|
||||
];
|
||||
|
||||
$this->driver = realpath(__DIR__.'/../../bin').DIRECTORY_SEPARATOR.$filenames[$this->operatingSystemId()];
|
||||
|
||||
return $this->process($arguments);
|
||||
}
|
||||
@@ -64,7 +66,7 @@ class ChromeProcess
|
||||
protected function process(array $arguments = [])
|
||||
{
|
||||
return new Process(
|
||||
array_merge([realpath($this->driver)], $arguments), null, $this->chromeEnvironment()
|
||||
array_merge([$this->driver], $arguments), null, $this->chromeEnvironment()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -101,4 +103,14 @@ class ChromeProcess
|
||||
{
|
||||
return OperatingSystem::onMac();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine OS ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function operatingSystemId()
|
||||
{
|
||||
return OperatingSystem::id();
|
||||
}
|
||||
}
|
||||
|
@@ -21,25 +21,25 @@ trait InteractsWithAuthentication
|
||||
* Log into the application using a given user ID or email.
|
||||
*
|
||||
* @param object|string $userId
|
||||
* @param string $guard
|
||||
* @param string|null $guard
|
||||
* @return $this
|
||||
*/
|
||||
public function loginAs($userId, $guard = null)
|
||||
{
|
||||
$userId = method_exists($userId, 'getKey') ? $userId->getKey() : $userId;
|
||||
$userId = is_object($userId) && method_exists($userId, 'getKey') ? $userId->getKey() : $userId;
|
||||
|
||||
return $this->visit(rtrim('/_dusk/login/'.$userId.'/'.$guard, '/'));
|
||||
return $this->visit(rtrim(route('dusk.login', ['userId' => $userId, 'guard' => $guard], $this->shouldUseAbsoluteRouteForAuthentication())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out of the application.
|
||||
*
|
||||
* @param string $guard
|
||||
* @param string|null $guard
|
||||
* @return $this
|
||||
*/
|
||||
public function logout($guard = null)
|
||||
{
|
||||
return $this->visit(rtrim('/_dusk/logout/'.$guard, '/'));
|
||||
return $this->visit(rtrim(route('dusk.logout', ['guard' => $guard], $this->shouldUseAbsoluteRouteForAuthentication()), '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,7 @@ trait InteractsWithAuthentication
|
||||
*/
|
||||
protected function currentUserInfo($guard = null)
|
||||
{
|
||||
$response = $this->visit("/_dusk/user/{$guard}");
|
||||
$response = $this->visit(route('dusk.user', ['guard' => $guard], $this->shouldUseAbsoluteRouteForAuthentication()));
|
||||
|
||||
return json_decode(strip_tags($response->driver->getPageSource()), true);
|
||||
}
|
||||
@@ -63,9 +63,11 @@ trait InteractsWithAuthentication
|
||||
*/
|
||||
public function assertAuthenticated($guard = null)
|
||||
{
|
||||
$currentUrl = $this->driver->getCurrentURL();
|
||||
|
||||
PHPUnit::assertNotEmpty($this->currentUserInfo($guard), 'The user is not authenticated.');
|
||||
|
||||
return $this;
|
||||
return $this->visit($currentUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,11 +78,13 @@ trait InteractsWithAuthentication
|
||||
*/
|
||||
public function assertGuest($guard = null)
|
||||
{
|
||||
$currentUrl = $this->driver->getCurrentURL();
|
||||
|
||||
PHPUnit::assertEmpty(
|
||||
$this->currentUserInfo($guard), 'The user is unexpectedly authenticated.'
|
||||
);
|
||||
|
||||
return $this;
|
||||
return $this->visit($currentUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,6 +96,8 @@ trait InteractsWithAuthentication
|
||||
*/
|
||||
public function assertAuthenticatedAs($user, $guard = null)
|
||||
{
|
||||
$currentUrl = $this->driver->getCurrentURL();
|
||||
|
||||
$expected = [
|
||||
'id' => $user->getAuthIdentifier(),
|
||||
'className' => get_class($user),
|
||||
@@ -102,6 +108,16 @@ trait InteractsWithAuthentication
|
||||
'The currently authenticated user is not who was expected.'
|
||||
);
|
||||
|
||||
return $this;
|
||||
return $this->visit($currentUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if route() should use an absolute path.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldUseAbsoluteRouteForAuthentication()
|
||||
{
|
||||
return config('dusk.domain') !== null;
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,9 @@
|
||||
namespace Laravel\Dusk\Concerns;
|
||||
|
||||
use DateTimeInterface;
|
||||
use Facebook\WebDriver\Exception\NoSuchCookieException;
|
||||
use Illuminate\Cookie\CookieValuePrefix;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
trait InteractsWithCookies
|
||||
{
|
||||
@@ -13,7 +16,7 @@ trait InteractsWithCookies
|
||||
* @param string|null $value
|
||||
* @param int|DateTimeInterface|null $expiry
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @return $this|string
|
||||
*/
|
||||
public function cookie($name, $value = null, $expiry = null, array $options = [])
|
||||
{
|
||||
@@ -21,19 +24,29 @@ trait InteractsWithCookies
|
||||
return $this->addCookie($name, $value, $expiry, $options);
|
||||
}
|
||||
|
||||
if ($cookie = $this->driver->manage()->getCookieNamed($name)) {
|
||||
return decrypt(rawurldecode($cookie['value']), $unserialize = false);
|
||||
try {
|
||||
$cookie = $this->driver->manage()->getCookieNamed($name);
|
||||
} catch (NoSuchCookieException $e) {
|
||||
$cookie = null;
|
||||
}
|
||||
|
||||
if ($cookie) {
|
||||
$decryptedValue = decrypt(rawurldecode($cookie['value']), $unserialize = false);
|
||||
|
||||
$hasValuePrefix = strpos($decryptedValue, CookieValuePrefix::create($name, Crypt::getKey())) === 0;
|
||||
|
||||
return $hasValuePrefix ? CookieValuePrefix::remove($decryptedValue) : $decryptedValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or set a plain cookie's value.
|
||||
* Get or set an unencrypted cookie's value.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $value
|
||||
* @param int|DateTimeInterface|null $expiry
|
||||
* @param array $options
|
||||
* @return string
|
||||
* @return $this|string
|
||||
*/
|
||||
public function plainCookie($name, $value = null, $expiry = null, array $options = [])
|
||||
{
|
||||
@@ -41,7 +54,13 @@ trait InteractsWithCookies
|
||||
return $this->addCookie($name, $value, $expiry, $options, false);
|
||||
}
|
||||
|
||||
if ($cookie = $this->driver->manage()->getCookieNamed($name)) {
|
||||
try {
|
||||
$cookie = $this->driver->manage()->getCookieNamed($name);
|
||||
} catch (NoSuchCookieException $e) {
|
||||
$cookie = null;
|
||||
}
|
||||
|
||||
if ($cookie) {
|
||||
return rawurldecode($cookie['value']);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +78,9 @@ trait InteractsWithCookies
|
||||
public function addCookie($name, $value, $expiry = null, array $options = [], $encrypt = true)
|
||||
{
|
||||
if ($encrypt) {
|
||||
$value = encrypt($value, $serialize = false);
|
||||
$prefix = CookieValuePrefix::create($name, Crypt::getKey());
|
||||
|
||||
$value = encrypt($prefix.$value, $serialize = false);
|
||||
}
|
||||
|
||||
if ($expiry instanceof DateTimeInterface) {
|
||||
|
@@ -6,6 +6,8 @@ use Facebook\WebDriver\Interactions\WebDriverActions;
|
||||
use Facebook\WebDriver\Remote\LocalFileDetector;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
use Facebook\WebDriver\WebDriverKeys;
|
||||
use Facebook\WebDriver\WebDriverSelect;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
trait InteractsWithElements
|
||||
@@ -43,9 +45,11 @@ trait InteractsWithElements
|
||||
{
|
||||
$this->ensurejQueryIsAvailable();
|
||||
|
||||
$selector = addslashes(trim($this->resolver->format("{$element}:contains({$link}):visible")));
|
||||
$selector = addslashes(trim($this->resolver->format("{$element}")));
|
||||
|
||||
$this->driver->executeScript("jQuery.find(\"{$selector}\")[0].click();");
|
||||
$link = str_replace("'", "\\\\'", $link);
|
||||
|
||||
$this->driver->executeScript("jQuery.find(`{$selector}:contains('{$link}'):visible`)[0].click();");
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -99,7 +103,7 @@ trait InteractsWithElements
|
||||
* Send the given keys to the element matching the given selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param dynamic $keys
|
||||
* @param mixed $keys
|
||||
* @return $this
|
||||
*/
|
||||
public function keys($selector, ...$keys)
|
||||
@@ -183,7 +187,7 @@ trait InteractsWithElements
|
||||
*/
|
||||
public function appendSlowly($field, $value, $pause = 100)
|
||||
{
|
||||
foreach (str_split($value) as $char) {
|
||||
foreach (preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY) as $char) {
|
||||
$this->append($field, $char)->pause($pause);
|
||||
}
|
||||
|
||||
@@ -207,7 +211,7 @@ trait InteractsWithElements
|
||||
* Select the given value or random value of a drop-down field.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|array|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function select($field, $value = null)
|
||||
@@ -216,18 +220,34 @@ trait InteractsWithElements
|
||||
|
||||
$options = $element->findElements(WebDriverBy::cssSelector('option:not([disabled])'));
|
||||
|
||||
if (is_null($value)) {
|
||||
$select = $element->getTagName() === 'select' ? new WebDriverSelect($element) : null;
|
||||
|
||||
$isMultiple = false;
|
||||
|
||||
if (! is_null($select)) {
|
||||
if ($isMultiple = $select->isMultiple()) {
|
||||
$select->deselectAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (func_num_args() === 1) {
|
||||
$options[array_rand($options)]->click();
|
||||
} else {
|
||||
if (is_bool($value)) {
|
||||
$value = $value ? '1' : '0';
|
||||
}
|
||||
$value = collect(Arr::wrap($value))->transform(function ($value) {
|
||||
if (is_bool($value)) {
|
||||
return $value ? '1' : '0';
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
})->all();
|
||||
|
||||
foreach ($options as $option) {
|
||||
if ((string) $option->getAttribute('value') === (string) $value) {
|
||||
if (in_array((string) $option->getAttribute('value'), $value)) {
|
||||
$option->click();
|
||||
|
||||
break;
|
||||
if (! $isMultiple) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +273,7 @@ trait InteractsWithElements
|
||||
* Check the given checkbox.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function check($field, $value = null)
|
||||
@@ -271,7 +291,7 @@ trait InteractsWithElements
|
||||
* Uncheck the given checkbox.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function uncheck($field, $value = null)
|
||||
|
@@ -7,7 +7,7 @@ trait InteractsWithJavascript
|
||||
/**
|
||||
* Execute JavaScript within the browser.
|
||||
*
|
||||
* @param string|array $scripts
|
||||
* @param string|array $scripts
|
||||
* @return array
|
||||
*/
|
||||
public function script($scripts)
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Laravel\Dusk\Concerns;
|
||||
|
||||
use Facebook\WebDriver\Exception\ElementClickInterceptedException;
|
||||
use Facebook\WebDriver\Exception\NoSuchElementException;
|
||||
use Facebook\WebDriver\Interactions\WebDriverActions;
|
||||
use Facebook\WebDriver\WebDriverBy;
|
||||
|
||||
@@ -48,17 +50,41 @@ trait InteractsWithMouse
|
||||
{
|
||||
if (is_null($selector)) {
|
||||
(new WebDriverActions($this->driver))->click()->perform();
|
||||
} else {
|
||||
$this->resolver->findOrFail($selector)->click();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
foreach ($this->resolver->all($selector) as $element) {
|
||||
try {
|
||||
$element->click();
|
||||
|
||||
return $this;
|
||||
} catch (ElementClickInterceptedException $e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
throw $e ?? new NoSuchElementException("Unable to locate element with selector [{$selector}].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the topmost element at the given pair of coordinates.
|
||||
*
|
||||
* @param int $x
|
||||
* @param int $y
|
||||
* @return $this
|
||||
*/
|
||||
public function clickAtPoint($x, $y)
|
||||
{
|
||||
$this->driver->executeScript("document.elementFromPoint({$x}, {$y}).click()");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Click the element at the given XPath expression.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $expression
|
||||
* @return $this
|
||||
*/
|
||||
public function clickAtXPath($expression)
|
||||
|
390
vendor/laravel/dusk/src/Concerns/MakesAssertions.php
vendored
390
vendor/laravel/dusk/src/Concerns/MakesAssertions.php
vendored
@@ -10,7 +10,14 @@ use PHPUnit\Framework\Assert as PHPUnit;
|
||||
trait MakesAssertions
|
||||
{
|
||||
/**
|
||||
* Assert that the page title is the given value.
|
||||
* Indicates the browser has made an assertion about the source code of the page.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $madeSourceAssertion = false;
|
||||
|
||||
/**
|
||||
* Assert that the page title matches the given text.
|
||||
*
|
||||
* @param string $title
|
||||
* @return $this
|
||||
@@ -26,7 +33,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the page title contains the given value.
|
||||
* Assert that the page title contains the given text.
|
||||
*
|
||||
* @param string $title
|
||||
* @return $this
|
||||
@@ -35,16 +42,16 @@ trait MakesAssertions
|
||||
{
|
||||
PHPUnit::assertTrue(
|
||||
Str::contains($this->driver->getTitle(), $title),
|
||||
"Did not see expected value [{$title}] within title [{$this->driver->getTitle()}]."
|
||||
"Did not see expected text [{$title}] within title [{$this->driver->getTitle()}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given cookie is present.
|
||||
* Assert that the given encrypted cookie is present.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $name
|
||||
* @param bool $decrypt
|
||||
* @return $this
|
||||
*/
|
||||
@@ -61,7 +68,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given plain cookie is present.
|
||||
* Assert that the given unencrypted cookie is present.
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
@@ -72,9 +79,9 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given cookie is not present.
|
||||
* Assert that the given encrypted cookie is not present.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $name
|
||||
* @param bool $decrypt
|
||||
* @return $this
|
||||
*/
|
||||
@@ -91,7 +98,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given plain cookie is not present.
|
||||
* Assert that the given unencrypted cookie is not present.
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
@@ -114,7 +121,8 @@ trait MakesAssertions
|
||||
$actual = $decrypt ? $this->cookie($name) : $this->plainCookie($name);
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$value, $actual,
|
||||
$value,
|
||||
$actual,
|
||||
"Cookie [{$name}] had value [{$actual}], but expected [{$value}]."
|
||||
);
|
||||
|
||||
@@ -122,7 +130,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a cookie has a given value.
|
||||
* Assert that an unencrypted cookie has a given value.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
@@ -134,7 +142,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text appears on the page.
|
||||
* Assert that the given text is present on the page.
|
||||
*
|
||||
* @param string $text
|
||||
* @return $this
|
||||
@@ -145,7 +153,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text does not appear on the page.
|
||||
* Assert that the given text is not present on the page.
|
||||
*
|
||||
* @param string $text
|
||||
* @return $this
|
||||
@@ -156,7 +164,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text appears within the given selector.
|
||||
* Assert that the given text is present within the selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $text
|
||||
@@ -177,7 +185,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given text does not appear within the given selector.
|
||||
* Assert that the given text is not present within the selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $text
|
||||
@@ -197,6 +205,66 @@ trait MakesAssertions
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that any text is present within the selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertSeeAnythingIn($selector)
|
||||
{
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
$element = $this->resolver->findOrFail($selector);
|
||||
|
||||
PHPUnit::assertTrue(
|
||||
$element->getText() !== '',
|
||||
"Saw unexpected text [''] within element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that no text is present within the selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertSeeNothingIn($selector)
|
||||
{
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
$element = $this->resolver->findOrFail($selector);
|
||||
|
||||
PHPUnit::assertTrue(
|
||||
$element->getText() === '',
|
||||
"Did not see expected text [''] within element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given JavaScript expression evaluates to the given value.
|
||||
*
|
||||
* @param string $expression
|
||||
* @param mixed $expected
|
||||
* @return $this
|
||||
*/
|
||||
public function assertScript($expression, $expected = true)
|
||||
{
|
||||
$expression = Str::start($expression, 'return ');
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$expected,
|
||||
$this->driver->executeScript($expression),
|
||||
"JavaScript expression [{$expression}] mismatched."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given source code is present on the page.
|
||||
*
|
||||
@@ -205,9 +273,11 @@ trait MakesAssertions
|
||||
*/
|
||||
public function assertSourceHas($code)
|
||||
{
|
||||
$this->madeSourceAssertion = true;
|
||||
|
||||
PHPUnit::assertTrue(
|
||||
Str::contains($this->driver->getPageSource(), $code),
|
||||
"Did not find expected source code [{$code}]"
|
||||
"Did not find expected source code [{$code}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
@@ -221,16 +291,18 @@ trait MakesAssertions
|
||||
*/
|
||||
public function assertSourceMissing($code)
|
||||
{
|
||||
$this->madeSourceAssertion = true;
|
||||
|
||||
PHPUnit::assertFalse(
|
||||
Str::contains($this->driver->getPageSource(), $code),
|
||||
"Found unexpected source code [{$code}]"
|
||||
"Found unexpected source code [{$code}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given link is visible.
|
||||
* Assert that the given link is present on the page.
|
||||
*
|
||||
* @param string $link
|
||||
* @return $this
|
||||
@@ -252,7 +324,7 @@ trait MakesAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given link is not visible.
|
||||
* Assert that the given link is not present on the page.
|
||||
*
|
||||
* @param string $link
|
||||
* @return $this
|
||||
@@ -283,10 +355,12 @@ trait MakesAssertions
|
||||
{
|
||||
$this->ensurejQueryIsAvailable();
|
||||
|
||||
$selector = addslashes(trim($this->resolver->format("a:contains('{$link}')")));
|
||||
$selector = addslashes(trim($this->resolver->format('a')));
|
||||
|
||||
$link = str_replace("'", "\\\\'", $link);
|
||||
|
||||
$script = <<<JS
|
||||
var link = jQuery.find("{$selector}");
|
||||
var link = jQuery.find(`{$selector}:contains('{$link}')`);
|
||||
return link.length > 0 && jQuery(link).is(':visible');
|
||||
JS;
|
||||
|
||||
@@ -294,7 +368,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given input or text area contains the given value.
|
||||
* Assert that the given input field has the given value.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -303,7 +377,8 @@ JS;
|
||||
public function assertInputValue($field, $value)
|
||||
{
|
||||
PHPUnit::assertEquals(
|
||||
$value, $this->inputValue($field),
|
||||
$value,
|
||||
$this->inputValue($field),
|
||||
"Expected value [{$value}] for the [{$field}] input does not equal the actual value [{$this->inputValue($field)}]."
|
||||
);
|
||||
|
||||
@@ -311,7 +386,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given input or text area does not contain the given value.
|
||||
* Assert that the given input field does not have the given value.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -320,7 +395,8 @@ JS;
|
||||
public function assertInputValueIsNot($field, $value)
|
||||
{
|
||||
PHPUnit::assertNotEquals(
|
||||
$value, $this->inputValue($field),
|
||||
$value,
|
||||
$this->inputValue($field),
|
||||
"Value [{$value}] for the [{$field}] input should not equal the actual value."
|
||||
);
|
||||
|
||||
@@ -343,10 +419,40 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given checkbox field is checked.
|
||||
* Assert that the given input field is present.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertInputPresent($field)
|
||||
{
|
||||
$this->assertPresent(
|
||||
"input[name='{$field}'], textarea[name='{$field}'], select[name='{$field}']"
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given input field is not visible.
|
||||
*
|
||||
* @param string $field
|
||||
* @return $this
|
||||
*/
|
||||
public function assertInputMissing($field)
|
||||
{
|
||||
$this->assertMissing(
|
||||
"input[name='{$field}'], textarea[name='{$field}'], select[name='{$field}']"
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given checkbox is checked.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertChecked($field, $value = null)
|
||||
@@ -362,10 +468,10 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given checkbox field is not checked.
|
||||
* Assert that the given checkbox is not checked.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertNotChecked($field, $value = null)
|
||||
@@ -380,6 +486,26 @@ JS;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given checkbox is in an indeterminate state.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertIndeterminate($field, $value = null)
|
||||
{
|
||||
$this->assertNotChecked($field, $value);
|
||||
|
||||
PHPUnit::assertSame(
|
||||
'true',
|
||||
$this->resolver->findOrFail($field)->getAttribute('indeterminate'),
|
||||
"Checkbox [{$field}] was not in indeterminate state."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given radio field is selected.
|
||||
*
|
||||
@@ -403,7 +529,7 @@ JS;
|
||||
* Assert that the given radio field is not selected.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertRadioNotSelected($field, $value = null)
|
||||
@@ -419,7 +545,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given select field has the given value selected.
|
||||
* Assert that the given dropdown has the given value selected.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -436,7 +562,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given select field does not have the given value selected.
|
||||
* Assert that the given dropdown does not have the given value selected.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -468,7 +594,8 @@ JS;
|
||||
})->all();
|
||||
|
||||
PHPUnit::assertCount(
|
||||
count($values), $options,
|
||||
count($values),
|
||||
$options,
|
||||
'Expected options ['.implode(',', $values)."] for selection field [{$field}] to be available."
|
||||
);
|
||||
|
||||
@@ -485,7 +612,8 @@ JS;
|
||||
public function assertSelectMissingOptions($field, array $values)
|
||||
{
|
||||
PHPUnit::assertCount(
|
||||
0, $this->resolver->resolveSelectOptions($field, $values),
|
||||
0,
|
||||
$this->resolver->resolveSelectOptions($field, $values),
|
||||
'Unexpected options ['.implode(',', $values)."] for selection field [{$field}]."
|
||||
);
|
||||
|
||||
@@ -505,7 +633,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the given value is not available to be selected on the given field.
|
||||
* Assert that the given value is not available to be selected.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
@@ -533,7 +661,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element at the given selector has the given value.
|
||||
* Assert that the element matching the given selector has the given value.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $value
|
||||
@@ -541,15 +669,75 @@ JS;
|
||||
*/
|
||||
public function assertValue($selector, $value)
|
||||
{
|
||||
$actual = $this->resolver->findOrFail($selector)->getAttribute('value');
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
PHPUnit::assertEquals($value, $actual);
|
||||
$this->ensureElementSupportsValueAttribute(
|
||||
$element = $this->resolver->findOrFail($selector),
|
||||
$fullSelector
|
||||
);
|
||||
|
||||
$actual = $element->getAttribute('value');
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$value,
|
||||
$actual,
|
||||
"Did not see expected value [{$value}] within element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element at the given selector has the given attribute value.
|
||||
* Assert that the element matching the given selector does not have the given value.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertValueIsNot($selector, $value)
|
||||
{
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
$this->ensureElementSupportsValueAttribute(
|
||||
$element = $this->resolver->findOrFail($selector),
|
||||
$fullSelector
|
||||
);
|
||||
|
||||
$actual = $element->getAttribute('value');
|
||||
|
||||
PHPUnit::assertNotEquals(
|
||||
$value,
|
||||
$actual,
|
||||
"Saw unexpected value [{$value}] within element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the given element supports the 'value' attribute.
|
||||
*
|
||||
* @param mixed $element
|
||||
* @param string $fullSelector
|
||||
* @return void
|
||||
*/
|
||||
public function ensureElementSupportsValueAttribute($element, $fullSelector)
|
||||
{
|
||||
PHPUnit::assertTrue(in_array($element->getTagName(), [
|
||||
'textarea',
|
||||
'select',
|
||||
'button',
|
||||
'input',
|
||||
'li',
|
||||
'meter',
|
||||
'option',
|
||||
'param',
|
||||
'progress',
|
||||
]), "This assertion cannot be used with the element [{$fullSelector}].");
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element matching the given selector has the given value in the provided attribute.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $attribute
|
||||
@@ -568,7 +756,8 @@ JS;
|
||||
);
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$value, $actual,
|
||||
$value,
|
||||
$actual,
|
||||
"Expected '$attribute' attribute [{$value}] does not equal actual value [$actual]."
|
||||
);
|
||||
|
||||
@@ -576,20 +765,35 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element at the given selector has the given data attribute value.
|
||||
* Assert that the element matching the given selector contains the given value in the provided attribute.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $attribute
|
||||
* @param string $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertDataAttribute($selector, $attribute, $value)
|
||||
public function assertAttributeContains($selector, $attribute, $value)
|
||||
{
|
||||
return $this->assertAttribute($selector, 'data-'.$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::assertStringContainsString(
|
||||
$value,
|
||||
$actual,
|
||||
"Attribute '$attribute' does not contain [{$value}]. Full attribute value was [$actual]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element at the given selector has the given aria attribute value.
|
||||
* Assert that the element matching the given selector has the given value in the provided aria attribute.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param string $attribute
|
||||
@@ -602,7 +806,20 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element with the given selector is visible.
|
||||
* Assert that the element matching the given selector has the given value in the provided data attribute.
|
||||
*
|
||||
* @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 matching the given selector is visible.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
@@ -620,7 +837,7 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element with the given selector is present in the DOM.
|
||||
* Assert that the element matching the given selector is present.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
@@ -638,7 +855,25 @@ JS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element with the given selector is not on the page.
|
||||
* Assert that the element matching the given selector is not present in the source.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertNotPresent($selector)
|
||||
{
|
||||
$fullSelector = $this->resolver->format($selector);
|
||||
|
||||
PHPUnit::assertTrue(
|
||||
is_null($this->resolver->find($selector)),
|
||||
"Element [{$fullSelector}] is present."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the element matching the given selector is not visible.
|
||||
*
|
||||
* @param string $selector
|
||||
* @return $this
|
||||
@@ -653,13 +888,16 @@ JS;
|
||||
$missing = true;
|
||||
}
|
||||
|
||||
PHPUnit::assertTrue($missing, "Saw unexpected element [{$fullSelector}].");
|
||||
PHPUnit::assertTrue(
|
||||
$missing,
|
||||
"Saw unexpected element [{$fullSelector}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a JavaScript dialog with given message has been opened.
|
||||
* Assert that a JavaScript dialog with the given message has been opened.
|
||||
*
|
||||
* @param string $message
|
||||
* @return $this
|
||||
@@ -669,7 +907,8 @@ JS;
|
||||
$actualMessage = $this->driver->switchTo()->alert()->getText();
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$message, $actualMessage,
|
||||
$message,
|
||||
$actualMessage,
|
||||
"Expected dialog message [{$message}] does not equal actual message [{$actualMessage}]."
|
||||
);
|
||||
|
||||
@@ -788,36 +1027,46 @@ JS;
|
||||
* Assert that the Vue component's attribute at the given key has the given value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param mixed $value
|
||||
* @param string|null $componentSelector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertVue($key, $value, $componentSelector = null)
|
||||
{
|
||||
PHPUnit::assertEquals($value, $this->vueAttribute($componentSelector, $key));
|
||||
$formattedValue = json_encode($value);
|
||||
|
||||
PHPUnit::assertEquals(
|
||||
$value,
|
||||
$this->vueAttribute($componentSelector, $key),
|
||||
"Did not see expected value [{$formattedValue}] at the key [{$key}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the Vue component's attribute at the given key
|
||||
* does not have the given value.
|
||||
* Assert that a given Vue component data property does not match the given value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param mixed $value
|
||||
* @param string|null $componentSelector
|
||||
* @return $this
|
||||
*/
|
||||
public function assertVueIsNot($key, $value, $componentSelector = null)
|
||||
{
|
||||
PHPUnit::assertNotEquals($value, $this->vueAttribute($componentSelector, $key));
|
||||
$formattedValue = json_encode($value);
|
||||
|
||||
PHPUnit::assertNotEquals(
|
||||
$value,
|
||||
$this->vueAttribute($componentSelector, $key),
|
||||
"Saw unexpected value [{$formattedValue}] at the key [{$key}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the Vue component's attribute at the given key
|
||||
* is an array that contains the given value.
|
||||
* Assert that a given Vue component data propertys is an array and contains the given value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
@@ -828,15 +1077,18 @@ JS;
|
||||
{
|
||||
$attribute = $this->vueAttribute($componentSelector, $key);
|
||||
|
||||
PHPUnit::assertIsArray($attribute, "The attribute for key [$key] is not an array.");
|
||||
PHPUnit::assertIsArray(
|
||||
$attribute,
|
||||
"The attribute for key [{$key}] is not an array."
|
||||
);
|
||||
|
||||
PHPUnit::assertContains($value, $attribute);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the Vue component's attribute at the given key
|
||||
* is an array that does not contain the given value.
|
||||
* Assert that a given Vue component data property is an array and does not contain the given value.
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
@@ -847,7 +1099,11 @@ JS;
|
||||
{
|
||||
$attribute = $this->vueAttribute($componentSelector, $key);
|
||||
|
||||
PHPUnit::assertIsArray($attribute, "The attribute for key [$key] is not an array.");
|
||||
PHPUnit::assertIsArray(
|
||||
$attribute,
|
||||
"The attribute for key [{$key}] is not an array."
|
||||
);
|
||||
|
||||
PHPUnit::assertNotContains($value, $attribute);
|
||||
|
||||
return $this;
|
||||
@@ -865,7 +1121,15 @@ JS;
|
||||
$fullSelector = $this->resolver->format($componentSelector);
|
||||
|
||||
return $this->driver->executeScript(
|
||||
"return document.querySelector('".$fullSelector."').__vue__.".$key
|
||||
"var el = document.querySelector('".$fullSelector."');".
|
||||
"if (typeof el.__vue__ !== 'undefined')".
|
||||
' return el.__vue__.'.$key.';'.
|
||||
'try {'.
|
||||
' var attr = el.__vueParentComponent.ctx.'.$key.';'.
|
||||
" if (typeof attr !== 'undefined')".
|
||||
' return attr;'.
|
||||
'} catch (e) {}'.
|
||||
'return el.__vueParentComponent.setupState.'.$key.';'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -4,11 +4,12 @@ namespace Laravel\Dusk\Concerns;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use PHPUnit\Framework\Assert as PHPUnit;
|
||||
use PHPUnit\Framework\Constraint\RegularExpression;
|
||||
|
||||
trait MakesUrlAssertions
|
||||
{
|
||||
/**
|
||||
* Assert that the current URL matches the given URL.
|
||||
* Assert that the current URL (without the query string) matches the given string.
|
||||
*
|
||||
* @param string $url
|
||||
* @return $this
|
||||
@@ -27,8 +28,8 @@ trait MakesUrlAssertions
|
||||
Arr::get($segments, 'path', '')
|
||||
);
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.$pattern.'$/u', $currentUrl,
|
||||
PHPUnit::assertThat(
|
||||
$currentUrl, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual URL [{$this->driver->getCurrentURL()}] does not equal expected URL [{$url}]."
|
||||
);
|
||||
|
||||
@@ -36,7 +37,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current scheme matches the given scheme.
|
||||
* Assert that the current URL scheme matches the given scheme.
|
||||
*
|
||||
* @param string $scheme
|
||||
* @return $this
|
||||
@@ -47,8 +48,8 @@ trait MakesUrlAssertions
|
||||
|
||||
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_SCHEME) ?? '';
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.$pattern.'$/u', $actual,
|
||||
PHPUnit::assertThat(
|
||||
$actual, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual scheme [{$actual}] does not equal expected scheme [{$pattern}]."
|
||||
);
|
||||
|
||||
@@ -56,7 +57,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current scheme does not match the given scheme.
|
||||
* Assert that the current URL scheme does not match the given scheme.
|
||||
*
|
||||
* @param string $scheme
|
||||
* @return $this
|
||||
@@ -74,7 +75,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current host matches the given host.
|
||||
* Assert that the current URL host matches the given host.
|
||||
*
|
||||
* @param string $host
|
||||
* @return $this
|
||||
@@ -85,8 +86,8 @@ trait MakesUrlAssertions
|
||||
|
||||
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_HOST) ?? '';
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.$pattern.'$/u', $actual,
|
||||
PHPUnit::assertThat(
|
||||
$actual, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual host [{$actual}] does not equal expected host [{$pattern}]."
|
||||
);
|
||||
|
||||
@@ -94,7 +95,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current host does not match the given host.
|
||||
* Assert that the current URL host does not match the given host.
|
||||
*
|
||||
* @param string $host
|
||||
* @return $this
|
||||
@@ -112,7 +113,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current port matches the given port.
|
||||
* Assert that the current URL port matches the given port.
|
||||
*
|
||||
* @param string $port
|
||||
* @return $this
|
||||
@@ -121,10 +122,10 @@ trait MakesUrlAssertions
|
||||
{
|
||||
$pattern = str_replace('\*', '.*', preg_quote($port, '/'));
|
||||
|
||||
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_PORT) ?? '';
|
||||
$actual = (string) parse_url($this->driver->getCurrentURL(), PHP_URL_PORT) ?? '';
|
||||
|
||||
PHPUnit::assertRegExp(
|
||||
'/^'.$pattern.'$/u', $actual,
|
||||
PHPUnit::assertThat(
|
||||
$actual, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual port [{$actual}] does not equal expected port [{$pattern}]."
|
||||
);
|
||||
|
||||
@@ -132,7 +133,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current host does not match the given host.
|
||||
* Assert that the current URL port does not match the given port.
|
||||
*
|
||||
* @param string $port
|
||||
* @return $this
|
||||
@@ -150,27 +151,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Assert that the current URL path begins with the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return $this
|
||||
@@ -188,7 +169,27 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current URL path does not match the given path.
|
||||
* Assert that the current path matches the given path.
|
||||
*
|
||||
* @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::assertThat(
|
||||
$actualPath, new RegularExpression('/^'.$pattern.'$/u'),
|
||||
"Actual path [{$actualPath}] does not equal expected path [{$path}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the current path does not match the given path.
|
||||
*
|
||||
* @param string $path
|
||||
* @return $this
|
||||
@@ -206,63 +207,7 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Assert that the current URL matches the given named route's URL.
|
||||
*
|
||||
* @param string $route
|
||||
* @param array $parameters
|
||||
@@ -274,10 +219,10 @@ trait MakesUrlAssertions
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a query string parameter is present and has a given value.
|
||||
* Assert that the given query string parameter is present and has a given value.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return $this
|
||||
*/
|
||||
public function assertQueryStringHas($name, $value = null)
|
||||
@@ -326,6 +271,62 @@ trait MakesUrlAssertions
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the URL's current hash fragment matches the given fragment.
|
||||
*
|
||||
* @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::assertThat(
|
||||
$actualFragment, new RegularExpression('/^'.str_replace('\*', '.*', $pattern).'$/u'),
|
||||
"Actual fragment [{$actualFragment}] does not equal expected fragment [{$fragment}]."
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that the URL's current hash fragment begins with the 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 URL's current hash 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 given query string parameter is present.
|
||||
*
|
||||
|
@@ -29,6 +29,7 @@ trait ProvidesBrowser
|
||||
* Tear down the Dusk test case class.
|
||||
*
|
||||
* @afterClass
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function tearDownDuskClass()
|
||||
@@ -68,10 +69,12 @@ trait ProvidesBrowser
|
||||
$callback(...$browsers->all());
|
||||
} catch (Exception $e) {
|
||||
$this->captureFailuresFor($browsers);
|
||||
$this->storeSourceLogsFor($browsers);
|
||||
|
||||
throw $e;
|
||||
} catch (Throwable $e) {
|
||||
$this->captureFailuresFor($browsers);
|
||||
$this->storeSourceLogsFor($browsers);
|
||||
|
||||
throw $e;
|
||||
} finally {
|
||||
@@ -162,6 +165,22 @@ trait ProvidesBrowser
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the source code for the given browsers (if necessary).
|
||||
*
|
||||
* @param \Illuminate\Support\Collection $browsers
|
||||
* @return void
|
||||
*/
|
||||
protected function storeSourceLogsFor($browsers)
|
||||
{
|
||||
$browsers->each(function ($browser, $key) {
|
||||
if (property_exists($browser, 'madeSourceAssertion') &&
|
||||
$browser->madeSourceAssertion) {
|
||||
$browser->storeSource($this->getCallerName().'-'.$key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all of the browsers except the primary (first) one.
|
||||
*
|
||||
|
@@ -6,6 +6,7 @@ use Carbon\Carbon;
|
||||
use Closure;
|
||||
use Exception;
|
||||
use Facebook\WebDriver\Exception\NoSuchElementException;
|
||||
use Facebook\WebDriver\Exception\ScriptTimeoutException;
|
||||
use Facebook\WebDriver\Exception\TimeOutException;
|
||||
use Facebook\WebDriver\WebDriverExpectedCondition;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -18,7 +19,7 @@ trait WaitsForElements
|
||||
*
|
||||
* @param string $selector
|
||||
* @param \Closure $callback
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -29,10 +30,10 @@ trait WaitsForElements
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given selector to be visible.
|
||||
* Wait for the given selector to become visible.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -50,7 +51,7 @@ trait WaitsForElements
|
||||
* Wait for the given selector to be removed.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -74,7 +75,7 @@ trait WaitsForElements
|
||||
* Wait for the given text to be removed.
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -91,10 +92,10 @@ trait WaitsForElements
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given text to be visible.
|
||||
* Wait for the given text to become visible.
|
||||
*
|
||||
* @param array|string $text
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -111,10 +112,29 @@ trait WaitsForElements
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given link to be visible.
|
||||
* Wait for the given text to become visible inside the given selector.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param array|string $text
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
*/
|
||||
public function waitForTextIn($selector, $text, $seconds = null)
|
||||
{
|
||||
$message = 'Waited %s seconds for text "'.$text.'" in selector '.$selector;
|
||||
|
||||
return $this->waitUsing($seconds, 100, function () use ($selector, $text) {
|
||||
return $this->assertSeeIn($selector, $text);
|
||||
}, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given link to become visible.
|
||||
*
|
||||
* @param string $link
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -128,11 +148,23 @@ trait WaitsForElements
|
||||
}, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for an input field to become visible.
|
||||
*
|
||||
* @param string $field
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitForInput($field, $seconds = null)
|
||||
{
|
||||
return $this->waitFor("input[name='{$field}'], textarea[name='{$field}'], select[name='{$field}']", $seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given location.
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -141,7 +173,9 @@ trait WaitsForElements
|
||||
{
|
||||
$message = $this->formatTimeOutMessage('Waited %s seconds for location', $path);
|
||||
|
||||
return $this->waitUntil("window.location.pathname == '{$path}'", $seconds, $message);
|
||||
return Str::startsWith($path, ['http://', 'https://'])
|
||||
? $this->waitUntil('`${location.protocol}//${location.host}${location.pathname}` == \''.$path.'\'', $seconds, $message)
|
||||
: $this->waitUntil("window.location.pathname == '{$path}'", $seconds, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +183,7 @@ trait WaitsForElements
|
||||
*
|
||||
* @param string $route
|
||||
* @param array $parameters
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -159,12 +193,48 @@ trait WaitsForElements
|
||||
return $this->waitForLocation(route($route, $parameters, false), $seconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until an element is enabled.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitUntilEnabled($selector, $seconds = null)
|
||||
{
|
||||
$message = $this->formatTimeOutMessage('Waited %s seconds for element to be enabled', $selector);
|
||||
|
||||
$this->waitUsing($seconds, 100, function () use ($selector) {
|
||||
return $this->resolver->findOrFail($selector)->isEnabled();
|
||||
}, $message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until an element is disabled.
|
||||
*
|
||||
* @param string $selector
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitUntilDisabled($selector, $seconds = null)
|
||||
{
|
||||
$message = $this->formatTimeOutMessage('Waited %s seconds for element to be disabled', $selector);
|
||||
|
||||
$this->waitUsing($seconds, 100, function () use ($selector) {
|
||||
return ! $this->resolver->findOrFail($selector)->isEnabled();
|
||||
}, $message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until the given script returns true.
|
||||
*
|
||||
* @param string $script
|
||||
* @param int $seconds
|
||||
* @param string $message
|
||||
* @param int|null $seconds
|
||||
* @param string|null $message
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -190,6 +260,7 @@ trait WaitsForElements
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param string|null $componentSelector
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitUntilVue($key, $value, $componentSelector = null, $seconds = null)
|
||||
@@ -207,6 +278,7 @@ trait WaitsForElements
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param string|null $componentSelector
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitUntilVueIsNot($key, $value, $componentSelector = null, $seconds = null)
|
||||
@@ -221,7 +293,7 @@ trait WaitsForElements
|
||||
/**
|
||||
* Wait for a JavaScript dialog to open.
|
||||
*
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function waitForDialog($seconds = null)
|
||||
@@ -238,8 +310,8 @@ trait WaitsForElements
|
||||
/**
|
||||
* Wait for the current page to reload.
|
||||
*
|
||||
* @param \Closure $callback
|
||||
* @param int $seconds
|
||||
* @param \Closure|null $callback
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
@@ -259,10 +331,55 @@ trait WaitsForElements
|
||||
}, 'Waited %s seconds for page reload.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Click an element and wait for the page to reload.
|
||||
*
|
||||
* @param string|null $selector
|
||||
* @return $this
|
||||
*/
|
||||
public function clickAndWaitForReload($selector = null)
|
||||
{
|
||||
return $this->waitForReload(function ($browser) use ($selector) {
|
||||
$browser->click($selector);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given event type to occur on a target.
|
||||
*
|
||||
* @param string $type
|
||||
* @param string|null $target
|
||||
* @param int|null $seconds
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Facebook\WebDriver\Exception\TimeOutException
|
||||
*/
|
||||
public function waitForEvent($type, $target = null, $seconds = null)
|
||||
{
|
||||
$seconds = is_null($seconds) ? static::$waitSeconds : $seconds;
|
||||
|
||||
if ($target !== 'document' && $target !== 'window') {
|
||||
$target = $this->resolver->findOrFail($target ?? '');
|
||||
}
|
||||
|
||||
$this->driver->manage()->timeouts()->setScriptTimeout($seconds);
|
||||
|
||||
try {
|
||||
$this->driver->executeAsyncScript(
|
||||
'eval(arguments[0]).addEventListener(arguments[1], () => arguments[2](), { once: true });',
|
||||
[$target, $type]
|
||||
);
|
||||
} catch (ScriptTimeoutException $e) {
|
||||
throw new TimeoutException("Waited {$seconds} seconds for event [{$type}].");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the given callback to be true.
|
||||
*
|
||||
* @param int $seconds
|
||||
* @param int|null $seconds
|
||||
* @param int $interval
|
||||
* @param \Closure $callback
|
||||
* @param string|null $message
|
||||
|
@@ -4,6 +4,7 @@ namespace Laravel\Dusk\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Laravel\Dusk\OperatingSystem;
|
||||
use Symfony\Component\Process\Process;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
@@ -18,6 +19,7 @@ class ChromeDriverCommand extends Command
|
||||
*/
|
||||
protected $signature = 'dusk:chrome-driver {version?}
|
||||
{--all : Install a ChromeDriver binary for every OS}
|
||||
{--detect : Detect the installed Chrome / Chromium version}
|
||||
{--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}';
|
||||
|
||||
@@ -57,6 +59,8 @@ class ChromeDriverCommand extends Command
|
||||
protected $slugs = [
|
||||
'linux' => 'linux64',
|
||||
'mac' => 'mac64',
|
||||
'mac-intel' => 'mac64',
|
||||
'mac-arm' => 'mac_arm64',
|
||||
'win' => 'win32',
|
||||
];
|
||||
|
||||
@@ -102,6 +106,32 @@ class ChromeDriverCommand extends Command
|
||||
*/
|
||||
protected $directory = __DIR__.'/../../bin/';
|
||||
|
||||
/**
|
||||
* The default commands to detect the installed Chrome / Chromium version.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $chromeVersionCommands = [
|
||||
'linux' => [
|
||||
'/usr/bin/google-chrome --version',
|
||||
'/usr/bin/chromium-browser --version',
|
||||
'/usr/bin/chromium --version',
|
||||
'/usr/bin/google-chrome-stable --version',
|
||||
],
|
||||
'mac' => [
|
||||
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
|
||||
],
|
||||
'mac-intel' => [
|
||||
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
|
||||
],
|
||||
'mac-arm' => [
|
||||
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
|
||||
],
|
||||
'win' => [
|
||||
'reg query "HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" /v version',
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
@@ -139,6 +169,10 @@ class ChromeDriverCommand extends Command
|
||||
{
|
||||
$version = $this->argument('version');
|
||||
|
||||
if ($this->option('detect')) {
|
||||
$version = $this->detectChromeVersion(OperatingSystem::id());
|
||||
}
|
||||
|
||||
if (! $version) {
|
||||
return $this->latestVersion();
|
||||
}
|
||||
@@ -165,7 +199,49 @@ class ChromeDriverCommand extends Command
|
||||
*/
|
||||
protected function latestVersion()
|
||||
{
|
||||
return trim(file_get_contents($this->latestVersionUrl));
|
||||
$streamOptions = [];
|
||||
|
||||
if ($this->option('ssl-no-verify')) {
|
||||
$streamOptions = [
|
||||
'ssl' => [
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
if ($this->option('proxy')) {
|
||||
$streamOptions['http'] = ['proxy' => $this->option('proxy'), 'request_fulluri' => true];
|
||||
}
|
||||
|
||||
return trim(file_get_contents($this->latestVersionUrl, false, stream_context_create($streamOptions)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect the installed Chrome / Chromium major version.
|
||||
*
|
||||
* @param string $os
|
||||
* @return int|bool
|
||||
*/
|
||||
protected function detectChromeVersion($os)
|
||||
{
|
||||
foreach ($this->chromeVersionCommands[$os] as $command) {
|
||||
$process = Process::fromShellCommandline($command);
|
||||
|
||||
$process->run();
|
||||
|
||||
preg_match('/(\d+)(\.\d+){3}/', $process->getOutput(), $matches);
|
||||
|
||||
if (! isset($matches[1])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
$this->error('Chrome version could not be detected.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
87
vendor/laravel/dusk/src/Console/DuskCommand.php
vendored
87
vendor/laravel/dusk/src/Console/DuskCommand.php
vendored
@@ -17,7 +17,10 @@ class DuskCommand extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'dusk {--without-tty : Disable output to TTY}';
|
||||
protected $signature = 'dusk
|
||||
{--browse : Open a browser instead of using headless mode}
|
||||
{--without-tty : Disable output to TTY}
|
||||
{--pest : Run the tests using Pest}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -56,12 +59,18 @@ class DuskCommand extends Command
|
||||
|
||||
$this->purgeConsoleLogs();
|
||||
|
||||
$options = array_slice($_SERVER['argv'], $this->option('without-tty') ? 3 : 2);
|
||||
$this->purgeSourceLogs();
|
||||
|
||||
$options = collect($_SERVER['argv'])
|
||||
->slice(2)
|
||||
->diff(['--browse', '--without-tty'])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return $this->withDuskEnvironment(function () use ($options) {
|
||||
$process = (new Process(array_merge(
|
||||
$this->binary(), $this->phpunitArguments($options)
|
||||
)))->setTimeout(null);
|
||||
), null, $this->env()))->setTimeout(null);
|
||||
|
||||
try {
|
||||
$process->setTty(! $this->option('without-tty'));
|
||||
@@ -88,11 +97,17 @@ class DuskCommand extends Command
|
||||
*/
|
||||
protected function binary()
|
||||
{
|
||||
if ('phpdbg' === PHP_SAPI) {
|
||||
return [PHP_BINARY, '-qrr', 'vendor/phpunit/phpunit/phpunit'];
|
||||
$binaryPath = 'vendor/phpunit/phpunit/phpunit';
|
||||
|
||||
if ($this->option('pest')) {
|
||||
$binaryPath = 'vendor/pestphp/pest/bin/pest';
|
||||
}
|
||||
|
||||
return [PHP_BINARY, 'vendor/phpunit/phpunit/phpunit'];
|
||||
if ('phpdbg' === PHP_SAPI) {
|
||||
return [PHP_BINARY, '-qrr', $binaryPath];
|
||||
}
|
||||
|
||||
return [PHP_BINARY, $binaryPath];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +119,7 @@ class DuskCommand extends Command
|
||||
protected function phpunitArguments($options)
|
||||
{
|
||||
$options = array_values(array_filter($options, function ($option) {
|
||||
return ! Str::startsWith($option, '--env=');
|
||||
return ! Str::startsWith($option, ['--env=', '--pest']);
|
||||
}));
|
||||
|
||||
if (! file_exists($file = base_path('phpunit.dusk.xml'))) {
|
||||
@@ -114,6 +129,18 @@ class DuskCommand extends Command
|
||||
return array_merge(['-c', $file], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PHP binary environment variables.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function env()
|
||||
{
|
||||
if ($this->option('browse') && ! isset($_ENV['CI']) && ! isset($_SERVER['CI'])) {
|
||||
return ['DUSK_HEADLESS_DISABLED' => true];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the failure screenshots.
|
||||
*
|
||||
@@ -121,19 +148,9 @@ class DuskCommand extends Command
|
||||
*/
|
||||
protected function purgeScreenshots()
|
||||
{
|
||||
$path = base_path('tests/Browser/screenshots');
|
||||
|
||||
if (! is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = Finder::create()->files()
|
||||
->in($path)
|
||||
->name('failure-*');
|
||||
|
||||
foreach ($files as $file) {
|
||||
@unlink($file->getRealPath());
|
||||
}
|
||||
$this->purgeDebuggingFiles(
|
||||
base_path('tests/Browser/screenshots'), 'failure-*'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,15 +160,39 @@ class DuskCommand extends Command
|
||||
*/
|
||||
protected function purgeConsoleLogs()
|
||||
{
|
||||
$path = base_path('tests/Browser/console');
|
||||
$this->purgeDebuggingFiles(
|
||||
base_path('tests/Browser/console'), '*.log'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the source logs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeSourceLogs()
|
||||
{
|
||||
$this->purgeDebuggingFiles(
|
||||
base_path('tests/Browser/source'), '*.txt'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge debugging files based on path and patterns.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $patterns
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeDebuggingFiles($path, $patterns)
|
||||
{
|
||||
if (! is_dir($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$files = Finder::create()->files()
|
||||
->in($path)
|
||||
->name('*.log');
|
||||
->in($path)
|
||||
->name($patterns);
|
||||
|
||||
foreach ($files as $file) {
|
||||
@unlink($file->getRealPath());
|
||||
|
@@ -9,7 +9,10 @@ class DuskFailsCommand extends DuskCommand
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'dusk:fails {--without-tty : Disable output to TTY}';
|
||||
protected $signature = 'dusk:fails
|
||||
{--browse : Open a browser instead of using headless mode}
|
||||
{--without-tty : Disable output to TTY}
|
||||
{--pest : Run the tests using Pest}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
|
@@ -45,6 +45,10 @@ class InstallCommand extends Command
|
||||
$this->createConsoleDirectory();
|
||||
}
|
||||
|
||||
if (! is_dir(base_path('tests/Browser/source'))) {
|
||||
$this->createSourceDirectory();
|
||||
}
|
||||
|
||||
$stubs = [
|
||||
'ExampleTest.stub' => base_path('tests/Browser/ExampleTest.php'),
|
||||
'HomePage.stub' => base_path('tests/Browser/Pages/HomePage.php'),
|
||||
@@ -100,6 +104,20 @@ class InstallCommand extends Command
|
||||
|
||||
file_put_contents(base_path('tests/Browser/console/.gitignore'), '*
|
||||
!.gitignore
|
||||
');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the source directory.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function createSourceDirectory()
|
||||
{
|
||||
mkdir(base_path('tests/Browser/source'), 0755, true);
|
||||
|
||||
file_put_contents(base_path('tests/Browser/source/.gitignore'), '*
|
||||
!.gitignore
|
||||
');
|
||||
}
|
||||
}
|
||||
|
114
vendor/laravel/dusk/src/Console/PurgeCommand.php
vendored
Normal file
114
vendor/laravel/dusk/src/Console/PurgeCommand.php
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Dusk\Console;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
class PurgeCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'dusk:purge';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Purge dusk test debugging files';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->ignoreValidationErrors();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->purgeScreenshots();
|
||||
$this->purgeConsoleLogs();
|
||||
$this->purgeSourceLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the failure screenshots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeScreenshots()
|
||||
{
|
||||
$this->purgeDebuggingFiles(
|
||||
'tests/Browser/screenshots', 'failure-*'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the console logs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeConsoleLogs()
|
||||
{
|
||||
$this->purgeDebuggingFiles(
|
||||
'tests/Browser/console', '*.log'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the source logs.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeSourceLogs()
|
||||
{
|
||||
$this->purgeDebuggingFiles(
|
||||
'tests/Browser/source', '*.txt'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge debugging files based on path and patterns.
|
||||
*
|
||||
* @param string $relativePath
|
||||
* @param string $patterns
|
||||
* @return void
|
||||
*/
|
||||
protected function purgeDebuggingFiles($relativePath, $patterns)
|
||||
{
|
||||
$path = base_path($relativePath);
|
||||
|
||||
if (! is_dir($path)) {
|
||||
$this->warn(
|
||||
"Unable to purge missing directory [{$relativePath}].", OutputInterface::VERBOSITY_DEBUG
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$files = Finder::create()->files()
|
||||
->in($path)
|
||||
->name($patterns);
|
||||
|
||||
foreach ($files as $file) {
|
||||
@unlink($file->getRealPath());
|
||||
}
|
||||
|
||||
$this->info("Purged \"{$patterns}\" from [{$relativePath}].");
|
||||
}
|
||||
}
|
@@ -3,6 +3,7 @@
|
||||
namespace DummyNamespace;
|
||||
|
||||
use Laravel\Dusk\Browser;
|
||||
use Laravel\Dusk\Page;
|
||||
|
||||
class DummyClass extends Page
|
||||
{
|
||||
|
41
vendor/laravel/dusk/src/DuskServiceProvider.php
vendored
41
vendor/laravel/dusk/src/DuskServiceProvider.php
vendored
@@ -15,32 +15,28 @@ class DuskServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
if (! $this->app->environment('production')) {
|
||||
Route::get('/_dusk/login/{userId}/{guard?}', [
|
||||
'middleware' => 'web',
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@login',
|
||||
]);
|
||||
Route::group(array_filter([
|
||||
'prefix' => config('dusk.path', '_dusk'),
|
||||
'domain' => config('dusk.domain', null),
|
||||
'middleware' => config('dusk.middleware', 'web'),
|
||||
]), function () {
|
||||
Route::get('/login/{userId}/{guard?}', [
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@login',
|
||||
'as' => 'dusk.login',
|
||||
]);
|
||||
|
||||
Route::get('/_dusk/logout/{guard?}', [
|
||||
'middleware' => 'web',
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@logout',
|
||||
]);
|
||||
Route::get('/logout/{guard?}', [
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@logout',
|
||||
'as' => 'dusk.logout',
|
||||
]);
|
||||
|
||||
Route::get('/_dusk/user/{guard?}', [
|
||||
'middleware' => 'web',
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@user',
|
||||
]);
|
||||
Route::get('/user/{guard?}', [
|
||||
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@user',
|
||||
'as' => 'dusk.user',
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register any package services.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
if ($this->app->runningInConsole()) {
|
||||
$this->commands([
|
||||
Console\InstallCommand::class,
|
||||
@@ -48,6 +44,7 @@ class DuskServiceProvider extends ServiceProvider
|
||||
Console\DuskFailsCommand::class,
|
||||
Console\MakeCommand::class,
|
||||
Console\PageCommand::class,
|
||||
Console\PurgeCommand::class,
|
||||
Console\ComponentCommand::class,
|
||||
Console\ChromeDriverCommand::class,
|
||||
]);
|
||||
|
18
vendor/laravel/dusk/src/ElementResolver.php
vendored
18
vendor/laravel/dusk/src/ElementResolver.php
vendored
@@ -87,7 +87,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "input[name='{$field}']", "textarea[name='{$field}']",
|
||||
"input[name='{$field}']", "textarea[name='{$field}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "select[name='{$field}']",
|
||||
"select[name='{$field}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ class ElementResolver
|
||||
* Resolve the element for a given radio "field" / value.
|
||||
*
|
||||
* @param string $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
||||
*
|
||||
* @throws \Exception
|
||||
@@ -156,7 +156,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "input[type=radio][name='{$field}'][value='{$value}']",
|
||||
"input[type=radio][name='{$field}'][value='{$value}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ class ElementResolver
|
||||
* Resolve the element for a given checkbox "field".
|
||||
*
|
||||
* @param string|null $field
|
||||
* @param string $value
|
||||
* @param string|null $value
|
||||
* @return \Facebook\WebDriver\Remote\RemoteWebElement
|
||||
*
|
||||
* @throws \Exception
|
||||
@@ -186,7 +186,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, $selector,
|
||||
$selector, $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "input[type=file][name='{$field}']",
|
||||
"input[type=file][name='{$field}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -224,8 +224,8 @@ class ElementResolver
|
||||
}
|
||||
|
||||
return $this->firstOrFail([
|
||||
$field, "input[name='{$field}']", "textarea[name='{$field}']",
|
||||
"select[name='{$field}']", "button[name='{$field}']",
|
||||
"input[name='{$field}']", "textarea[name='{$field}']",
|
||||
"select[name='{$field}']", "button[name='{$field}']", $field,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@
|
||||
namespace Laravel\Dusk\Http\Controllers;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class UserController
|
||||
@@ -31,7 +32,7 @@ class UserController
|
||||
* Login using the given user ID / email.
|
||||
*
|
||||
* @param string $userId
|
||||
* @param string $guard
|
||||
* @param string|null $guard
|
||||
* @return void
|
||||
*/
|
||||
public function login($userId, $guard = null)
|
||||
@@ -50,12 +51,16 @@ class UserController
|
||||
/**
|
||||
* Log the user out of the application.
|
||||
*
|
||||
* @param string $guard
|
||||
* @param string|null $guard
|
||||
* @return void
|
||||
*/
|
||||
public function logout($guard = null)
|
||||
{
|
||||
Auth::guard($guard ?: config('auth.defaults.guard'))->logout();
|
||||
$guard = $guard ?: config('auth.defaults.guard');
|
||||
|
||||
Auth::guard($guard)->logout();
|
||||
|
||||
Session::forget('password_hash_'.$guard);
|
||||
}
|
||||
|
||||
/**
|
||||
|
25
vendor/laravel/dusk/src/OperatingSystem.php
vendored
25
vendor/laravel/dusk/src/OperatingSystem.php
vendored
@@ -13,7 +13,13 @@ class OperatingSystem
|
||||
*/
|
||||
public static function id()
|
||||
{
|
||||
return static::onWindows() ? 'win' : (static::onMac() ? 'mac' : 'linux');
|
||||
if (static::onWindows()) {
|
||||
return 'win';
|
||||
} elseif (static::onMac()) {
|
||||
return static::macArchitectureId();
|
||||
}
|
||||
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,4 +41,21 @@ class OperatingSystem
|
||||
{
|
||||
return PHP_OS === 'Darwin';
|
||||
}
|
||||
|
||||
/**
|
||||
* Mac platform architecture.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function macArchitectureId()
|
||||
{
|
||||
switch (php_uname('m')) {
|
||||
case 'arm64':
|
||||
return 'mac-arm';
|
||||
case 'x86_64':
|
||||
return 'mac-intel';
|
||||
default:
|
||||
return 'mac';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
12
vendor/laravel/dusk/src/TestCase.php
vendored
12
vendor/laravel/dusk/src/TestCase.php
vendored
@@ -54,7 +54,7 @@ abstract class TestCase extends FoundationTestCase
|
||||
*/
|
||||
protected function baseUrl()
|
||||
{
|
||||
return config('app.url');
|
||||
return rtrim(config('app.url'), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,4 +68,14 @@ abstract class TestCase extends FoundationTestCase
|
||||
{
|
||||
throw new Exception('User resolver has not been set.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the tests are running within Laravel Sail.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function runningInSail()
|
||||
{
|
||||
return isset($_ENV['LARAVEL_SAIL']) && $_ENV['LARAVEL_SAIL'] == '1';
|
||||
}
|
||||
}
|
||||
|
42
vendor/laravel/dusk/stubs/DuskTestCase.stub
vendored
42
vendor/laravel/dusk/stubs/DuskTestCase.stub
vendored
@@ -19,7 +19,9 @@ abstract class DuskTestCase extends BaseTestCase
|
||||
*/
|
||||
public static function prepare()
|
||||
{
|
||||
static::startChromeDriver();
|
||||
if (! static::runningInSail()) {
|
||||
static::startChromeDriver();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,16 +31,42 @@ abstract class DuskTestCase extends BaseTestCase
|
||||
*/
|
||||
protected function driver()
|
||||
{
|
||||
$options = (new ChromeOptions)->addArguments([
|
||||
'--disable-gpu',
|
||||
'--headless',
|
||||
'--window-size=1920,1080',
|
||||
]);
|
||||
$options = (new ChromeOptions)->addArguments(collect([
|
||||
$this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1920,1080',
|
||||
])->unless($this->hasHeadlessDisabled(), function ($items) {
|
||||
return $items->merge([
|
||||
'--disable-gpu',
|
||||
'--headless',
|
||||
]);
|
||||
})->all());
|
||||
|
||||
return RemoteWebDriver::create(
|
||||
'http://localhost:9515', DesiredCapabilities::chrome()->setCapability(
|
||||
$_ENV['DUSK_DRIVER_URL'] ?? 'http://localhost:9515',
|
||||
DesiredCapabilities::chrome()->setCapability(
|
||||
ChromeOptions::CAPABILITY, $options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the Dusk command has disabled headless mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasHeadlessDisabled()
|
||||
{
|
||||
return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
|
||||
isset($_ENV['DUSK_HEADLESS_DISABLED']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the browser window should start maximized.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldStartMaximized()
|
||||
{
|
||||
return isset($_SERVER['DUSK_START_MAXIMIZED']) ||
|
||||
isset($_ENV['DUSK_START_MAXIMIZED']);
|
||||
}
|
||||
}
|
||||
|
5
vendor/laravel/dusk/stubs/phpunit.xml
vendored
5
vendor/laravel/dusk/stubs/phpunit.xml
vendored
@@ -13,9 +13,4 @@
|
||||
<directory suffix="Test.php">./tests/Browser</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist processUncoveredFilesFromWhitelist="true">
|
||||
<directory suffix=".php">./app</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
||||
|
Reference in New Issue
Block a user