đã tạo được test case của createUser

This commit is contained in:
2026-04-07 11:22:54 +07:00
parent 9d0a872e91
commit 8a7ffe6212
235 changed files with 30524 additions and 9502 deletions
+1
View File
@@ -0,0 +1 @@
{"version":1,"defects":{"Tests\\Browser\\ExampleTest::test_basic_example":7,"Tests\\Browser\\CreateAccountTest::test_empty_form_shows_validation_errors":7,"Tests\\Browser\\CreateAccountTest::test_successful_registration_redirects_to_game":7,"Tests\\Browser\\CreateAccountTest::test_authenticated_user_sees_account_page":8,"Tests\\Browser\\CreateAccountTest::test_invalid_phone_shows_error":8,"Tests\\Browser\\AccountManagementTest::test_update_account_successfully":8,"Tests\\Browser\\AccountManagementTest::test_update_account_with_empty_name_shows_error":8,"Tests\\Browser\\AccountManagementTest::test_update_account_with_invalid_phone_shows_error":8,"Tests\\Browser\\AccountManagementTest::test_history_section_visible_when_authenticated":8,"Tests\\Browser\\AccountManagementTest::test_logout_works":8,"Tests\\Browser\\GamePageTest::test_root_redirects_to_game":7,"Tests\\Browser\\GamePageTest::test_game_rule_endpoint_returns_content":7},"times":{"Tests\\Browser\\ExampleTest::test_basic_example":3.169,"Tests\\Browser\\CreateAccountTest::test_login_page_shows_form":2.375,"Tests\\Browser\\CreateAccountTest::test_empty_form_shows_validation_errors":0.583,"Tests\\Browser\\CreateAccountTest::test_invalid_phone_shows_error":1.315,"Tests\\Browser\\CreateAccountTest::test_successful_registration_redirects_to_game":1.263,"Tests\\Browser\\CreateAccountTest::test_authenticated_user_sees_account_page":1.602,"Tests\\Browser\\AccountManagementTest::test_update_account_successfully":3.551,"Tests\\Browser\\AccountManagementTest::test_update_account_with_empty_name_shows_error":1.066,"Tests\\Browser\\AccountManagementTest::test_update_account_with_invalid_phone_shows_error":0.728,"Tests\\Browser\\AccountManagementTest::test_history_section_visible_when_authenticated":0.686,"Tests\\Browser\\AccountManagementTest::test_logout_works":0.684,"Tests\\Browser\\GamePageTest::test_game_page_loads_when_guest":2.332,"Tests\\Browser\\GamePageTest::test_root_redirects_to_game":1.283,"Tests\\Browser\\GamePageTest::test_game_page_shows_lucky_wheel":0.141,"Tests\\Browser\\GamePageTest::test_game_rule_endpoint_returns_content":0.375}}
+1
View File
@@ -17,6 +17,7 @@
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/dusk": "^8.5",
"laravel/pail": "^1.1",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
Generated
+9634 -9494
View File
File diff suppressed because it is too large Load Diff
+114
View File
@@ -0,0 +1,114 @@
<?php
namespace Tests\Browser;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class AccountManagementTest extends DuskTestCase
{
/**
* Helper: đăng ký tài khoản mới và đăng nhập.
*/
private function registerAndLogin(Browser $browser, string $phone): void
{
$browser->visit('/login')
->type('name', 'Dusk Test User')
->type('phone', $phone)
->type('address', 'Cần Thơ')
->radio('known_sis', '1')
->type('job', 'Nhân viên văn phòng')
->press('Gửi thông tin')
->assertPathIs('/game');
}
/**
* Cập nhật tài khoản thành công.
*/
public function test_update_account_successfully(): void
{
$phone = '09' . rand(10000000, 99999999);
$this->browse(function (Browser $browser) use ($phone) {
$this->registerAndLogin($browser, $phone);
$browser->visit('/login')
->assertSee('Cập nhật thông tin cá nhân')
->clear('name')
->type('name', 'Tên Mới Test')
->clear('address')
->type('address', 'Hà Nội')
->radio('known_sis', '0')
->clear('job')
->type('job', 'Giáo viên')
->press('Cập nhật')
->assertPathIs('/game')
->assertSee('Cập nhật tài khoản thành công');
});
}
/**
* Cập nhật tài khoản với tên rỗng phải hiện lỗi.
*/
public function test_update_account_with_empty_name_shows_error(): void
{
$phone = '09' . rand(10000000, 99999999);
$this->browse(function (Browser $browser) use ($phone) {
$this->registerAndLogin($browser, $phone);
$browser->visit('/login')
->clear('name')
->press('Cập nhật')
->assertSee('Nhập tên hiển thị');
});
}
/**
* Cập nhật tài khoản với số điện thoại sai phải hiện lỗi.
*/
public function test_update_account_with_invalid_phone_shows_error(): void
{
$phone = '09' . rand(10000000, 99999999);
$this->browse(function (Browser $browser) use ($phone) {
$this->registerAndLogin($browser, $phone);
$browser->visit('/login')
->clear('phone')
->type('phone', 'abcde')
->press('Cập nhật')
->assertSee('Số điện thoại không đúng định dạng');
});
}
/**
* Trang lịch sử hoạt động hiển thị khi đã đăng nhập.
*/
public function test_history_section_visible_when_authenticated(): void
{
$phone = '09' . rand(10000000, 99999999);
$this->browse(function (Browser $browser) use ($phone) {
$this->registerAndLogin($browser, $phone);
$browser->visit('/login')
->assertSee('LỊCH SỬ HOẠT ĐỘNG');
});
}
/**
* Đăng xuất thành công.
*/
public function test_logout_works(): void
{
$phone = '09' . rand(10000000, 99999999);
$this->browse(function (Browser $browser) use ($phone) {
$this->registerAndLogin($browser, $phone);
$browser->visit('/logout')
->assertGuest();
});
}
}
+103
View File
@@ -0,0 +1,103 @@
<?php
namespace Tests\Browser;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class CreateAccountTest extends DuskTestCase
{
/**
* Trang đăng ký / đăng nhập hiển thị form nhập thông tin.
*/
public function test_login_page_shows_form(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/login')
->assertSee('Nhập thông tin cá nhân')
->assertPresent('input[name="name"]')
->assertPresent('input[name="phone"]')
->assertPresent('input[name="address"]')
->assertPresent('input[name="job"]');
});
}
/**
* Submit form rỗng phải hiện lỗi validation.
*/
public function test_empty_form_shows_validation_errors(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/login')
->press('Gửi thông tin')
->assertPresent('.text-danger');
});
}
/**
* Số điện thoại sai định dạng phải hiện lỗi.
*/
public function test_invalid_phone_shows_error(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/login')
->type('name', 'Nguyễn Test')
->type('phone', '12345')
->type('address', 'Hà Nội')
->radio('known_sis', '0')
->type('job', 'Kỹ sư')
->press('Gửi thông tin')
->pause(400)
->assertSee('Số điện thoại không đúng định dạng');
});
}
/**
* Đăng ký thành công với số điện thoại mới và redirect về /game.
* Lưu ý: test này dùng số điện thoại ngẫu nhiên để tránh trùng DB.
*/
public function test_successful_registration_redirects_to_game(): void
{
$uniquePhone = '09' . rand(10000000, 99999999);
$this->browse(function (Browser $browser) use ($uniquePhone) {
$browser->visit('/login')
->type('name', 'Test Dusk User')
->type('phone', $uniquePhone)
->type('address', 'Cần Thơ')
->radio('known_sis', '1')
->type('job', 'Kỹ sư')
->press('Gửi thông tin')
->waitForText('thành công', 10)
->assertPathIs('/game');
});
}
/**
* Khi đã đăng nhập, GET /login hiển thị trang tài khoản (không có form đăng ký).
*/
public function test_authenticated_user_sees_account_page(): void
{
$uniquePhone = '09' . rand(10000000, 99999999);
$this->browse(function (Browser $browser) use ($uniquePhone) {
// Đảm bảo guest (xóa session từ test trước)
$browser->visit('/logout');
// Đăng ký và đăng nhập
$browser->visit('/login')
->type('name', 'Dusk Auth User')
->type('phone', $uniquePhone)
->type('address', 'TP.HCM')
->radio('known_sis', '0')
->type('job', 'Bác sĩ')
->press('Gửi thông tin')
->waitForText('thành công', 10)
->assertPathIs('/game');
// Truy cập lại /login sau khi đã đăng nhập: phone field phải được điền sẵn (chỉ xảy ra khi đã login)
$browser->visit('/login')
->assertInputValue('phone', $uniquePhone);
});
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
/**
* A basic browser test example.
*/
public function test_basic_example(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertPathIs('/game');
});
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace Tests\Browser;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class GamePageTest extends DuskTestCase
{
/**
* Trang game hiển thị đúng khi chưa đăng nhập.
*/
public function test_game_page_loads_when_guest(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/game')
->assertPathIs('/game');
});
}
/**
* Trang chủ (/) redirect về game.
*/
public function test_root_redirects_to_game(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertPathIs('/game');
});
}
/**
* Trang game hiển thị vòng quay may mắn.
*/
public function test_game_page_shows_lucky_wheel(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/game')
->assertPresent('#wheel, canvas, .wheel, [id*="wheel"], [class*="wheel"]');
});
}
/**
* Popup luật chơi có thể tải được.
*/
public function test_game_rule_endpoint_returns_content(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/game/rule')
->assertSee('luật');
});
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
class HomePage extends Page
{
/**
* Get the URL for the page.
*/
public function url(): string
{
return '/';
}
/**
* Assert that the browser is on the page.
*/
public function assert(Browser $browser): void
{
//
}
/**
* Get the element shortcuts for the page.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@element' => '#selector',
];
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Page as BasePage;
abstract class Page extends BasePage
{
/**
* Get the global element shortcuts for the site.
*
* @return array<string, string>
*/
public static function siteElements(): array
{
return [
'@element' => '#selector',
];
}
}
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
+2
View File
@@ -0,0 +1,2 @@
*
!.gitignore
+52
View File
@@ -0,0 +1,52 @@
<?php
namespace Tests;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Illuminate\Support\Collection;
use Laravel\Dusk\TestCase as BaseTestCase;
use PHPUnit\Framework\Attributes\BeforeClass;
abstract class DuskTestCase extends BaseTestCase
{
/**
* Prepare for Dusk test execution.
*/
#[BeforeClass]
public static function prepare(): void
{
if (! static::runningInSail()) {
static::startChromeDriver(['--port=9515']);
}
}
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
$options = (new ChromeOptions)->addArguments(collect([
$this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1920,1080',
'--disable-search-engine-choice-screen',
'--disable-smooth-scrolling',
'--no-sandbox',
'--disable-dev-shm-usage',
])->unless($this->hasHeadlessDisabled(), function (Collection $items) {
return $items->merge([
'--disable-gpu',
'--headless=new',
]);
})->all());
$options->setBinary('/usr/bin/google-chrome');
return RemoteWebDriver::create(
$_ENV['DUSK_DRIVER_URL'] ?? env('DUSK_DRIVER_URL') ?? 'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
);
}
}
+192
View File
@@ -533,6 +533,167 @@ return array(
'Egulias\\EmailValidator\\Warning\\QuotedString' => $vendorDir . '/egulias/email-validator/src/Warning/QuotedString.php',
'Egulias\\EmailValidator\\Warning\\TLD' => $vendorDir . '/egulias/email-validator/src/Warning/TLD.php',
'Egulias\\EmailValidator\\Warning\\Warning' => $vendorDir . '/egulias/email-validator/src/Warning/Warning.php',
'Facebook\\WebDriver\\AbstractWebDriverCheckboxOrRadio' => $vendorDir . '/php-webdriver/webdriver/lib/AbstractWebDriverCheckboxOrRadio.php',
'Facebook\\WebDriver\\Chrome\\ChromeDevToolsDriver' => $vendorDir . '/php-webdriver/webdriver/lib/Chrome/ChromeDevToolsDriver.php',
'Facebook\\WebDriver\\Chrome\\ChromeDriver' => $vendorDir . '/php-webdriver/webdriver/lib/Chrome/ChromeDriver.php',
'Facebook\\WebDriver\\Chrome\\ChromeDriverService' => $vendorDir . '/php-webdriver/webdriver/lib/Chrome/ChromeDriverService.php',
'Facebook\\WebDriver\\Chrome\\ChromeOptions' => $vendorDir . '/php-webdriver/webdriver/lib/Chrome/ChromeOptions.php',
'Facebook\\WebDriver\\Cookie' => $vendorDir . '/php-webdriver/webdriver/lib/Cookie.php',
'Facebook\\WebDriver\\Exception\\DetachedShadowRootException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/DetachedShadowRootException.php',
'Facebook\\WebDriver\\Exception\\ElementClickInterceptedException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/ElementClickInterceptedException.php',
'Facebook\\WebDriver\\Exception\\ElementNotInteractableException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/ElementNotInteractableException.php',
'Facebook\\WebDriver\\Exception\\ElementNotSelectableException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/ElementNotSelectableException.php',
'Facebook\\WebDriver\\Exception\\ElementNotVisibleException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/ElementNotVisibleException.php',
'Facebook\\WebDriver\\Exception\\ExpectedException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/ExpectedException.php',
'Facebook\\WebDriver\\Exception\\IMEEngineActivationFailedException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/IMEEngineActivationFailedException.php',
'Facebook\\WebDriver\\Exception\\IMENotAvailableException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/IMENotAvailableException.php',
'Facebook\\WebDriver\\Exception\\IndexOutOfBoundsException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/IndexOutOfBoundsException.php',
'Facebook\\WebDriver\\Exception\\InsecureCertificateException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/InsecureCertificateException.php',
'Facebook\\WebDriver\\Exception\\Internal\\DriverServerDiedException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/Internal/DriverServerDiedException.php',
'Facebook\\WebDriver\\Exception\\Internal\\IOException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/Internal/IOException.php',
'Facebook\\WebDriver\\Exception\\Internal\\LogicException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/Internal/LogicException.php',
'Facebook\\WebDriver\\Exception\\Internal\\RuntimeException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/Internal/RuntimeException.php',
'Facebook\\WebDriver\\Exception\\Internal\\UnexpectedResponseException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/Internal/UnexpectedResponseException.php',
'Facebook\\WebDriver\\Exception\\Internal\\WebDriverCurlException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/Internal/WebDriverCurlException.php',
'Facebook\\WebDriver\\Exception\\InvalidArgumentException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/InvalidArgumentException.php',
'Facebook\\WebDriver\\Exception\\InvalidCookieDomainException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/InvalidCookieDomainException.php',
'Facebook\\WebDriver\\Exception\\InvalidCoordinatesException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/InvalidCoordinatesException.php',
'Facebook\\WebDriver\\Exception\\InvalidElementStateException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/InvalidElementStateException.php',
'Facebook\\WebDriver\\Exception\\InvalidSelectorException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/InvalidSelectorException.php',
'Facebook\\WebDriver\\Exception\\InvalidSessionIdException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/InvalidSessionIdException.php',
'Facebook\\WebDriver\\Exception\\JavascriptErrorException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/JavascriptErrorException.php',
'Facebook\\WebDriver\\Exception\\MoveTargetOutOfBoundsException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/MoveTargetOutOfBoundsException.php',
'Facebook\\WebDriver\\Exception\\NoAlertOpenException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoAlertOpenException.php',
'Facebook\\WebDriver\\Exception\\NoCollectionException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoCollectionException.php',
'Facebook\\WebDriver\\Exception\\NoScriptResultException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoScriptResultException.php',
'Facebook\\WebDriver\\Exception\\NoStringException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoStringException.php',
'Facebook\\WebDriver\\Exception\\NoStringLengthException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoStringLengthException.php',
'Facebook\\WebDriver\\Exception\\NoStringWrapperException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoStringWrapperException.php',
'Facebook\\WebDriver\\Exception\\NoSuchAlertException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoSuchAlertException.php',
'Facebook\\WebDriver\\Exception\\NoSuchCollectionException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoSuchCollectionException.php',
'Facebook\\WebDriver\\Exception\\NoSuchCookieException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoSuchCookieException.php',
'Facebook\\WebDriver\\Exception\\NoSuchDocumentException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoSuchDocumentException.php',
'Facebook\\WebDriver\\Exception\\NoSuchDriverException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoSuchDriverException.php',
'Facebook\\WebDriver\\Exception\\NoSuchElementException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoSuchElementException.php',
'Facebook\\WebDriver\\Exception\\NoSuchFrameException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoSuchFrameException.php',
'Facebook\\WebDriver\\Exception\\NoSuchShadowRootException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoSuchShadowRootException.php',
'Facebook\\WebDriver\\Exception\\NoSuchWindowException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NoSuchWindowException.php',
'Facebook\\WebDriver\\Exception\\NullPointerException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/NullPointerException.php',
'Facebook\\WebDriver\\Exception\\PhpWebDriverExceptionInterface' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/PhpWebDriverExceptionInterface.php',
'Facebook\\WebDriver\\Exception\\ScriptTimeoutException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/ScriptTimeoutException.php',
'Facebook\\WebDriver\\Exception\\SessionNotCreatedException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/SessionNotCreatedException.php',
'Facebook\\WebDriver\\Exception\\StaleElementReferenceException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/StaleElementReferenceException.php',
'Facebook\\WebDriver\\Exception\\TimeoutException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/TimeoutException.php',
'Facebook\\WebDriver\\Exception\\UnableToCaptureScreenException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnableToCaptureScreenException.php',
'Facebook\\WebDriver\\Exception\\UnableToSetCookieException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnableToSetCookieException.php',
'Facebook\\WebDriver\\Exception\\UnexpectedAlertOpenException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnexpectedAlertOpenException.php',
'Facebook\\WebDriver\\Exception\\UnexpectedJavascriptException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnexpectedJavascriptException.php',
'Facebook\\WebDriver\\Exception\\UnexpectedTagNameException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnexpectedTagNameException.php',
'Facebook\\WebDriver\\Exception\\UnknownCommandException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnknownCommandException.php',
'Facebook\\WebDriver\\Exception\\UnknownErrorException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnknownErrorException.php',
'Facebook\\WebDriver\\Exception\\UnknownMethodException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnknownMethodException.php',
'Facebook\\WebDriver\\Exception\\UnknownServerException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnknownServerException.php',
'Facebook\\WebDriver\\Exception\\UnrecognizedExceptionException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnrecognizedExceptionException.php',
'Facebook\\WebDriver\\Exception\\UnsupportedOperationException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/UnsupportedOperationException.php',
'Facebook\\WebDriver\\Exception\\WebDriverException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/WebDriverException.php',
'Facebook\\WebDriver\\Exception\\XPathLookupException' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/XPathLookupException.php',
'Facebook\\WebDriver\\Firefox\\FirefoxDriver' => $vendorDir . '/php-webdriver/webdriver/lib/Firefox/FirefoxDriver.php',
'Facebook\\WebDriver\\Firefox\\FirefoxDriverService' => $vendorDir . '/php-webdriver/webdriver/lib/Firefox/FirefoxDriverService.php',
'Facebook\\WebDriver\\Firefox\\FirefoxOptions' => $vendorDir . '/php-webdriver/webdriver/lib/Firefox/FirefoxOptions.php',
'Facebook\\WebDriver\\Firefox\\FirefoxPreferences' => $vendorDir . '/php-webdriver/webdriver/lib/Firefox/FirefoxPreferences.php',
'Facebook\\WebDriver\\Firefox\\FirefoxProfile' => $vendorDir . '/php-webdriver/webdriver/lib/Firefox/FirefoxProfile.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverButtonReleaseAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverButtonReleaseAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverClickAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverClickAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverClickAndHoldAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverClickAndHoldAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverContextClickAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverContextClickAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverCoordinates' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverCoordinates.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverDoubleClickAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverDoubleClickAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverKeyDownAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverKeyDownAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverKeyUpAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverKeyUpAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverKeysRelatedAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverKeysRelatedAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverMouseAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverMouseAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverMouseMoveAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverMouseMoveAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverMoveToOffsetAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverMoveToOffsetAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverSendKeysAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverSendKeysAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverSingleKeyAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverSingleKeyAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverDoubleTapAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverDoubleTapAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverDownAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverDownAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverFlickAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverFlickAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverFlickFromElementAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverFlickFromElementAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverLongPressAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverLongPressAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverMoveAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverMoveAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverScrollAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverScrollAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverScrollFromElementAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverScrollFromElementAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverTapAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverTapAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverTouchAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverTouchAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverTouchScreen' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverTouchScreen.php',
'Facebook\\WebDriver\\Interactions\\WebDriverActions' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/WebDriverActions.php',
'Facebook\\WebDriver\\Interactions\\WebDriverCompositeAction' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/WebDriverCompositeAction.php',
'Facebook\\WebDriver\\Interactions\\WebDriverTouchActions' => $vendorDir . '/php-webdriver/webdriver/lib/Interactions/WebDriverTouchActions.php',
'Facebook\\WebDriver\\Internal\\WebDriverLocatable' => $vendorDir . '/php-webdriver/webdriver/lib/Internal/WebDriverLocatable.php',
'Facebook\\WebDriver\\JavaScriptExecutor' => $vendorDir . '/php-webdriver/webdriver/lib/JavaScriptExecutor.php',
'Facebook\\WebDriver\\Local\\LocalWebDriver' => $vendorDir . '/php-webdriver/webdriver/lib/Local/LocalWebDriver.php',
'Facebook\\WebDriver\\Net\\URLChecker' => $vendorDir . '/php-webdriver/webdriver/lib/Net/URLChecker.php',
'Facebook\\WebDriver\\Remote\\CustomWebDriverCommand' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/CustomWebDriverCommand.php',
'Facebook\\WebDriver\\Remote\\DesiredCapabilities' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/DesiredCapabilities.php',
'Facebook\\WebDriver\\Remote\\DriverCommand' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/DriverCommand.php',
'Facebook\\WebDriver\\Remote\\ExecuteMethod' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/ExecuteMethod.php',
'Facebook\\WebDriver\\Remote\\FileDetector' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/FileDetector.php',
'Facebook\\WebDriver\\Remote\\HttpCommandExecutor' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/HttpCommandExecutor.php',
'Facebook\\WebDriver\\Remote\\JsonWireCompat' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/JsonWireCompat.php',
'Facebook\\WebDriver\\Remote\\LocalFileDetector' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/LocalFileDetector.php',
'Facebook\\WebDriver\\Remote\\RemoteExecuteMethod' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/RemoteExecuteMethod.php',
'Facebook\\WebDriver\\Remote\\RemoteKeyboard' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/RemoteKeyboard.php',
'Facebook\\WebDriver\\Remote\\RemoteMouse' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/RemoteMouse.php',
'Facebook\\WebDriver\\Remote\\RemoteStatus' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/RemoteStatus.php',
'Facebook\\WebDriver\\Remote\\RemoteTargetLocator' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/RemoteTargetLocator.php',
'Facebook\\WebDriver\\Remote\\RemoteTouchScreen' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/RemoteTouchScreen.php',
'Facebook\\WebDriver\\Remote\\RemoteWebDriver' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/RemoteWebDriver.php',
'Facebook\\WebDriver\\Remote\\RemoteWebElement' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/RemoteWebElement.php',
'Facebook\\WebDriver\\Remote\\Service\\DriverCommandExecutor' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/Service/DriverCommandExecutor.php',
'Facebook\\WebDriver\\Remote\\Service\\DriverService' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/Service/DriverService.php',
'Facebook\\WebDriver\\Remote\\ShadowRoot' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/ShadowRoot.php',
'Facebook\\WebDriver\\Remote\\UselessFileDetector' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/UselessFileDetector.php',
'Facebook\\WebDriver\\Remote\\WebDriverBrowserType' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/WebDriverBrowserType.php',
'Facebook\\WebDriver\\Remote\\WebDriverCapabilityType' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/WebDriverCapabilityType.php',
'Facebook\\WebDriver\\Remote\\WebDriverCommand' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/WebDriverCommand.php',
'Facebook\\WebDriver\\Remote\\WebDriverResponse' => $vendorDir . '/php-webdriver/webdriver/lib/Remote/WebDriverResponse.php',
'Facebook\\WebDriver\\Support\\Events\\EventFiringWebDriver' => $vendorDir . '/php-webdriver/webdriver/lib/Support/Events/EventFiringWebDriver.php',
'Facebook\\WebDriver\\Support\\Events\\EventFiringWebDriverNavigation' => $vendorDir . '/php-webdriver/webdriver/lib/Support/Events/EventFiringWebDriverNavigation.php',
'Facebook\\WebDriver\\Support\\Events\\EventFiringWebElement' => $vendorDir . '/php-webdriver/webdriver/lib/Support/Events/EventFiringWebElement.php',
'Facebook\\WebDriver\\Support\\IsElementDisplayedAtom' => $vendorDir . '/php-webdriver/webdriver/lib/Support/IsElementDisplayedAtom.php',
'Facebook\\WebDriver\\Support\\ScreenshotHelper' => $vendorDir . '/php-webdriver/webdriver/lib/Support/ScreenshotHelper.php',
'Facebook\\WebDriver\\Support\\XPathEscaper' => $vendorDir . '/php-webdriver/webdriver/lib/Support/XPathEscaper.php',
'Facebook\\WebDriver\\WebDriver' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriver.php',
'Facebook\\WebDriver\\WebDriverAction' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverAction.php',
'Facebook\\WebDriver\\WebDriverAlert' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverAlert.php',
'Facebook\\WebDriver\\WebDriverBy' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverBy.php',
'Facebook\\WebDriver\\WebDriverCapabilities' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverCapabilities.php',
'Facebook\\WebDriver\\WebDriverCheckboxes' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverCheckboxes.php',
'Facebook\\WebDriver\\WebDriverCommandExecutor' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverCommandExecutor.php',
'Facebook\\WebDriver\\WebDriverDimension' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverDimension.php',
'Facebook\\WebDriver\\WebDriverDispatcher' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverDispatcher.php',
'Facebook\\WebDriver\\WebDriverElement' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverElement.php',
'Facebook\\WebDriver\\WebDriverEventListener' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverEventListener.php',
'Facebook\\WebDriver\\WebDriverExpectedCondition' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverExpectedCondition.php',
'Facebook\\WebDriver\\WebDriverHasInputDevices' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverHasInputDevices.php',
'Facebook\\WebDriver\\WebDriverKeyboard' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverKeyboard.php',
'Facebook\\WebDriver\\WebDriverKeys' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverKeys.php',
'Facebook\\WebDriver\\WebDriverMouse' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverMouse.php',
'Facebook\\WebDriver\\WebDriverNavigation' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverNavigation.php',
'Facebook\\WebDriver\\WebDriverNavigationInterface' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverNavigationInterface.php',
'Facebook\\WebDriver\\WebDriverOptions' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverOptions.php',
'Facebook\\WebDriver\\WebDriverPlatform' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverPlatform.php',
'Facebook\\WebDriver\\WebDriverPoint' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverPoint.php',
'Facebook\\WebDriver\\WebDriverRadios' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverRadios.php',
'Facebook\\WebDriver\\WebDriverSearchContext' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverSearchContext.php',
'Facebook\\WebDriver\\WebDriverSelect' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverSelect.php',
'Facebook\\WebDriver\\WebDriverSelectInterface' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverSelectInterface.php',
'Facebook\\WebDriver\\WebDriverTargetLocator' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverTargetLocator.php',
'Facebook\\WebDriver\\WebDriverTimeouts' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverTimeouts.php',
'Facebook\\WebDriver\\WebDriverUpAction' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverUpAction.php',
'Facebook\\WebDriver\\WebDriverWait' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverWait.php',
'Facebook\\WebDriver\\WebDriverWindow' => $vendorDir . '/php-webdriver/webdriver/lib/WebDriverWindow.php',
'Faker\\Calculator\\Ean' => $vendorDir . '/fakerphp/faker/src/Faker/Calculator/Ean.php',
'Faker\\Calculator\\Iban' => $vendorDir . '/fakerphp/faker/src/Faker/Calculator/Iban.php',
'Faker\\Calculator\\Inn' => $vendorDir . '/fakerphp/faker/src/Faker/Calculator/Inn.php',
@@ -2584,6 +2745,37 @@ return array(
'Jorenvh\\Share\\Providers\\ShareServiceProvider' => $vendorDir . '/jorenvanhocht/laravel-share/src/Providers/ShareServiceProvider.php',
'Jorenvh\\Share\\Share' => $vendorDir . '/jorenvanhocht/laravel-share/src/Share.php',
'Jorenvh\\Share\\ShareFacade' => $vendorDir . '/jorenvanhocht/laravel-share/src/ShareFacade.php',
'Laravel\\Dusk\\Browser' => $vendorDir . '/laravel/dusk/src/Browser.php',
'Laravel\\Dusk\\Chrome\\ChromeProcess' => $vendorDir . '/laravel/dusk/src/Chrome/ChromeProcess.php',
'Laravel\\Dusk\\Chrome\\SupportsChrome' => $vendorDir . '/laravel/dusk/src/Chrome/SupportsChrome.php',
'Laravel\\Dusk\\Component' => $vendorDir . '/laravel/dusk/src/Component.php',
'Laravel\\Dusk\\Concerns\\InteractsWithAuthentication' => $vendorDir . '/laravel/dusk/src/Concerns/InteractsWithAuthentication.php',
'Laravel\\Dusk\\Concerns\\InteractsWithCookies' => $vendorDir . '/laravel/dusk/src/Concerns/InteractsWithCookies.php',
'Laravel\\Dusk\\Concerns\\InteractsWithElements' => $vendorDir . '/laravel/dusk/src/Concerns/InteractsWithElements.php',
'Laravel\\Dusk\\Concerns\\InteractsWithJavascript' => $vendorDir . '/laravel/dusk/src/Concerns/InteractsWithJavascript.php',
'Laravel\\Dusk\\Concerns\\InteractsWithKeyboard' => $vendorDir . '/laravel/dusk/src/Concerns/InteractsWithKeyboard.php',
'Laravel\\Dusk\\Concerns\\InteractsWithMouse' => $vendorDir . '/laravel/dusk/src/Concerns/InteractsWithMouse.php',
'Laravel\\Dusk\\Concerns\\MakesAssertions' => $vendorDir . '/laravel/dusk/src/Concerns/MakesAssertions.php',
'Laravel\\Dusk\\Concerns\\MakesUrlAssertions' => $vendorDir . '/laravel/dusk/src/Concerns/MakesUrlAssertions.php',
'Laravel\\Dusk\\Concerns\\ProvidesBrowser' => $vendorDir . '/laravel/dusk/src/Concerns/ProvidesBrowser.php',
'Laravel\\Dusk\\Concerns\\WaitsForElements' => $vendorDir . '/laravel/dusk/src/Concerns/WaitsForElements.php',
'Laravel\\Dusk\\Console\\ChromeDriverCommand' => $vendorDir . '/laravel/dusk/src/Console/ChromeDriverCommand.php',
'Laravel\\Dusk\\Console\\ComponentCommand' => $vendorDir . '/laravel/dusk/src/Console/ComponentCommand.php',
'Laravel\\Dusk\\Console\\Concerns\\InteractsWithTestingFrameworks' => $vendorDir . '/laravel/dusk/src/Console/Concerns/InteractsWithTestingFrameworks.php',
'Laravel\\Dusk\\Console\\DuskCommand' => $vendorDir . '/laravel/dusk/src/Console/DuskCommand.php',
'Laravel\\Dusk\\Console\\DuskFailsCommand' => $vendorDir . '/laravel/dusk/src/Console/DuskFailsCommand.php',
'Laravel\\Dusk\\Console\\InstallCommand' => $vendorDir . '/laravel/dusk/src/Console/InstallCommand.php',
'Laravel\\Dusk\\Console\\MakeCommand' => $vendorDir . '/laravel/dusk/src/Console/MakeCommand.php',
'Laravel\\Dusk\\Console\\PageCommand' => $vendorDir . '/laravel/dusk/src/Console/PageCommand.php',
'Laravel\\Dusk\\Console\\PurgeCommand' => $vendorDir . '/laravel/dusk/src/Console/PurgeCommand.php',
'Laravel\\Dusk\\Dusk' => $vendorDir . '/laravel/dusk/src/Dusk.php',
'Laravel\\Dusk\\DuskServiceProvider' => $vendorDir . '/laravel/dusk/src/DuskServiceProvider.php',
'Laravel\\Dusk\\ElementResolver' => $vendorDir . '/laravel/dusk/src/ElementResolver.php',
'Laravel\\Dusk\\Http\\Controllers\\UserController' => $vendorDir . '/laravel/dusk/src/Http/Controllers/UserController.php',
'Laravel\\Dusk\\Keyboard' => $vendorDir . '/laravel/dusk/src/Keyboard.php',
'Laravel\\Dusk\\OperatingSystem' => $vendorDir . '/laravel/dusk/src/OperatingSystem.php',
'Laravel\\Dusk\\Page' => $vendorDir . '/laravel/dusk/src/Page.php',
'Laravel\\Dusk\\TestCase' => $vendorDir . '/laravel/dusk/src/TestCase.php',
'Laravel\\Pail\\Console\\Commands\\PailCommand' => $vendorDir . '/laravel/pail/src/Console/Commands/PailCommand.php',
'Laravel\\Pail\\Contracts\\Printer' => $vendorDir . '/laravel/pail/src/Contracts/Printer.php',
'Laravel\\Pail\\File' => $vendorDir . '/laravel/pail/src/File.php',
+2 -1
View File
@@ -20,8 +20,8 @@ return array(
'ce9671a430e4846b44e1c68c7611f9f5' => $vendorDir . '/mockery/mockery/library/Mockery.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => $vendorDir . '/symfony/translation/Resources/functions.php',
'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'35a6ad97d21e794e7e22a17d806652e4' => $vendorDir . '/nunomaduro/termwind/src/Functions.php',
'47e1160838b5e5a10346ac4084b58c23' => $vendorDir . '/laravel/prompts/src/helpers.php',
@@ -38,6 +38,7 @@ return array(
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'2a3c2110e8e0295330dc3d11a4cbc4cb' => $vendorDir . '/php-webdriver/webdriver/lib/Exception/TimeoutException.php',
'a1cfe24d14977df6878b9bf804af2d1c' => $vendorDir . '/nunomaduro/collision/src/Adapters/Phpunit/Autoload.php',
'8fde5feda9697fe0ee53a3938c839fb0' => $baseDir . '/app/Helpers/Helper.php',
);
+2
View File
@@ -77,6 +77,7 @@ return array(
'Laravel\\Sail\\' => array($vendorDir . '/laravel/sail/src'),
'Laravel\\Prompts\\' => array($vendorDir . '/laravel/prompts/src'),
'Laravel\\Pail\\' => array($vendorDir . '/laravel/pail/src'),
'Laravel\\Dusk\\' => array($vendorDir . '/laravel/dusk/src'),
'Jorenvh\\Share\\' => array($vendorDir . '/jorenvanhocht/laravel-share/src'),
'Illuminate\\Support\\' => array($vendorDir . '/laravel/framework/src/Illuminate/Macroable', $vendorDir . '/laravel/framework/src/Illuminate/Collections', $vendorDir . '/laravel/framework/src/Illuminate/Conditionable'),
'Illuminate\\' => array($vendorDir . '/laravel/framework/src/Illuminate'),
@@ -89,6 +90,7 @@ return array(
'FontLib\\' => array($vendorDir . '/dompdf/php-font-lib/src/FontLib'),
'Firebase\\JWT\\' => array($vendorDir . '/firebase/php-jwt/src'),
'Faker\\' => array($vendorDir . '/fakerphp/faker/src/Faker'),
'Facebook\\WebDriver\\' => array($vendorDir . '/php-webdriver/webdriver/lib'),
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
'Dotenv\\' => array($vendorDir . '/vlucas/phpdotenv/src'),
'Dompdf\\' => array($vendorDir . '/dompdf/dompdf/src'),
+204 -1
View File
@@ -21,8 +21,8 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'ce9671a430e4846b44e1c68c7611f9f5' => __DIR__ . '/..' . '/mockery/mockery/library/Mockery.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'a1105708a18b76903365ca1c4aa61b02' => __DIR__ . '/..' . '/symfony/translation/Resources/functions.php',
'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'35a6ad97d21e794e7e22a17d806652e4' => __DIR__ . '/..' . '/nunomaduro/termwind/src/Functions.php',
'47e1160838b5e5a10346ac4084b58c23' => __DIR__ . '/..' . '/laravel/prompts/src/helpers.php',
@@ -39,6 +39,7 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'6124b4c8570aa390c21fafd04a26c69f' => __DIR__ . '/..' . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'2a3c2110e8e0295330dc3d11a4cbc4cb' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/TimeoutException.php',
'a1cfe24d14977df6878b9bf804af2d1c' => __DIR__ . '/..' . '/nunomaduro/collision/src/Adapters/Phpunit/Autoload.php',
'8fde5feda9697fe0ee53a3938c839fb0' => __DIR__ . '/../..' . '/app/Helpers/Helper.php',
);
@@ -147,6 +148,7 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'Laravel\\Sail\\' => 13,
'Laravel\\Prompts\\' => 16,
'Laravel\\Pail\\' => 13,
'Laravel\\Dusk\\' => 13,
),
'J' =>
array (
@@ -171,6 +173,7 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'FontLib\\' => 8,
'Firebase\\JWT\\' => 13,
'Faker\\' => 6,
'Facebook\\WebDriver\\' => 19,
),
'E' =>
array (
@@ -495,6 +498,10 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
array (
0 => __DIR__ . '/..' . '/laravel/pail/src',
),
'Laravel\\Dusk\\' =>
array (
0 => __DIR__ . '/..' . '/laravel/dusk/src',
),
'Jorenvh\\Share\\' =>
array (
0 => __DIR__ . '/..' . '/jorenvanhocht/laravel-share/src',
@@ -545,6 +552,10 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
array (
0 => __DIR__ . '/..' . '/fakerphp/faker/src/Faker',
),
'Facebook\\WebDriver\\' =>
array (
0 => __DIR__ . '/..' . '/php-webdriver/webdriver/lib',
),
'Egulias\\EmailValidator\\' =>
array (
0 => __DIR__ . '/..' . '/egulias/email-validator/src',
@@ -1154,6 +1165,167 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'Egulias\\EmailValidator\\Warning\\QuotedString' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/QuotedString.php',
'Egulias\\EmailValidator\\Warning\\TLD' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/TLD.php',
'Egulias\\EmailValidator\\Warning\\Warning' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/Warning.php',
'Facebook\\WebDriver\\AbstractWebDriverCheckboxOrRadio' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/AbstractWebDriverCheckboxOrRadio.php',
'Facebook\\WebDriver\\Chrome\\ChromeDevToolsDriver' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Chrome/ChromeDevToolsDriver.php',
'Facebook\\WebDriver\\Chrome\\ChromeDriver' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Chrome/ChromeDriver.php',
'Facebook\\WebDriver\\Chrome\\ChromeDriverService' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Chrome/ChromeDriverService.php',
'Facebook\\WebDriver\\Chrome\\ChromeOptions' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Chrome/ChromeOptions.php',
'Facebook\\WebDriver\\Cookie' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Cookie.php',
'Facebook\\WebDriver\\Exception\\DetachedShadowRootException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/DetachedShadowRootException.php',
'Facebook\\WebDriver\\Exception\\ElementClickInterceptedException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/ElementClickInterceptedException.php',
'Facebook\\WebDriver\\Exception\\ElementNotInteractableException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/ElementNotInteractableException.php',
'Facebook\\WebDriver\\Exception\\ElementNotSelectableException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/ElementNotSelectableException.php',
'Facebook\\WebDriver\\Exception\\ElementNotVisibleException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/ElementNotVisibleException.php',
'Facebook\\WebDriver\\Exception\\ExpectedException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/ExpectedException.php',
'Facebook\\WebDriver\\Exception\\IMEEngineActivationFailedException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/IMEEngineActivationFailedException.php',
'Facebook\\WebDriver\\Exception\\IMENotAvailableException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/IMENotAvailableException.php',
'Facebook\\WebDriver\\Exception\\IndexOutOfBoundsException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/IndexOutOfBoundsException.php',
'Facebook\\WebDriver\\Exception\\InsecureCertificateException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/InsecureCertificateException.php',
'Facebook\\WebDriver\\Exception\\Internal\\DriverServerDiedException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/Internal/DriverServerDiedException.php',
'Facebook\\WebDriver\\Exception\\Internal\\IOException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/Internal/IOException.php',
'Facebook\\WebDriver\\Exception\\Internal\\LogicException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/Internal/LogicException.php',
'Facebook\\WebDriver\\Exception\\Internal\\RuntimeException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/Internal/RuntimeException.php',
'Facebook\\WebDriver\\Exception\\Internal\\UnexpectedResponseException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/Internal/UnexpectedResponseException.php',
'Facebook\\WebDriver\\Exception\\Internal\\WebDriverCurlException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/Internal/WebDriverCurlException.php',
'Facebook\\WebDriver\\Exception\\InvalidArgumentException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/InvalidArgumentException.php',
'Facebook\\WebDriver\\Exception\\InvalidCookieDomainException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/InvalidCookieDomainException.php',
'Facebook\\WebDriver\\Exception\\InvalidCoordinatesException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/InvalidCoordinatesException.php',
'Facebook\\WebDriver\\Exception\\InvalidElementStateException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/InvalidElementStateException.php',
'Facebook\\WebDriver\\Exception\\InvalidSelectorException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/InvalidSelectorException.php',
'Facebook\\WebDriver\\Exception\\InvalidSessionIdException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/InvalidSessionIdException.php',
'Facebook\\WebDriver\\Exception\\JavascriptErrorException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/JavascriptErrorException.php',
'Facebook\\WebDriver\\Exception\\MoveTargetOutOfBoundsException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/MoveTargetOutOfBoundsException.php',
'Facebook\\WebDriver\\Exception\\NoAlertOpenException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoAlertOpenException.php',
'Facebook\\WebDriver\\Exception\\NoCollectionException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoCollectionException.php',
'Facebook\\WebDriver\\Exception\\NoScriptResultException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoScriptResultException.php',
'Facebook\\WebDriver\\Exception\\NoStringException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoStringException.php',
'Facebook\\WebDriver\\Exception\\NoStringLengthException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoStringLengthException.php',
'Facebook\\WebDriver\\Exception\\NoStringWrapperException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoStringWrapperException.php',
'Facebook\\WebDriver\\Exception\\NoSuchAlertException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoSuchAlertException.php',
'Facebook\\WebDriver\\Exception\\NoSuchCollectionException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoSuchCollectionException.php',
'Facebook\\WebDriver\\Exception\\NoSuchCookieException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoSuchCookieException.php',
'Facebook\\WebDriver\\Exception\\NoSuchDocumentException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoSuchDocumentException.php',
'Facebook\\WebDriver\\Exception\\NoSuchDriverException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoSuchDriverException.php',
'Facebook\\WebDriver\\Exception\\NoSuchElementException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoSuchElementException.php',
'Facebook\\WebDriver\\Exception\\NoSuchFrameException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoSuchFrameException.php',
'Facebook\\WebDriver\\Exception\\NoSuchShadowRootException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoSuchShadowRootException.php',
'Facebook\\WebDriver\\Exception\\NoSuchWindowException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NoSuchWindowException.php',
'Facebook\\WebDriver\\Exception\\NullPointerException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/NullPointerException.php',
'Facebook\\WebDriver\\Exception\\PhpWebDriverExceptionInterface' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/PhpWebDriverExceptionInterface.php',
'Facebook\\WebDriver\\Exception\\ScriptTimeoutException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/ScriptTimeoutException.php',
'Facebook\\WebDriver\\Exception\\SessionNotCreatedException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/SessionNotCreatedException.php',
'Facebook\\WebDriver\\Exception\\StaleElementReferenceException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/StaleElementReferenceException.php',
'Facebook\\WebDriver\\Exception\\TimeoutException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/TimeoutException.php',
'Facebook\\WebDriver\\Exception\\UnableToCaptureScreenException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnableToCaptureScreenException.php',
'Facebook\\WebDriver\\Exception\\UnableToSetCookieException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnableToSetCookieException.php',
'Facebook\\WebDriver\\Exception\\UnexpectedAlertOpenException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnexpectedAlertOpenException.php',
'Facebook\\WebDriver\\Exception\\UnexpectedJavascriptException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnexpectedJavascriptException.php',
'Facebook\\WebDriver\\Exception\\UnexpectedTagNameException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnexpectedTagNameException.php',
'Facebook\\WebDriver\\Exception\\UnknownCommandException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnknownCommandException.php',
'Facebook\\WebDriver\\Exception\\UnknownErrorException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnknownErrorException.php',
'Facebook\\WebDriver\\Exception\\UnknownMethodException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnknownMethodException.php',
'Facebook\\WebDriver\\Exception\\UnknownServerException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnknownServerException.php',
'Facebook\\WebDriver\\Exception\\UnrecognizedExceptionException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnrecognizedExceptionException.php',
'Facebook\\WebDriver\\Exception\\UnsupportedOperationException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/UnsupportedOperationException.php',
'Facebook\\WebDriver\\Exception\\WebDriverException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/WebDriverException.php',
'Facebook\\WebDriver\\Exception\\XPathLookupException' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Exception/XPathLookupException.php',
'Facebook\\WebDriver\\Firefox\\FirefoxDriver' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Firefox/FirefoxDriver.php',
'Facebook\\WebDriver\\Firefox\\FirefoxDriverService' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Firefox/FirefoxDriverService.php',
'Facebook\\WebDriver\\Firefox\\FirefoxOptions' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Firefox/FirefoxOptions.php',
'Facebook\\WebDriver\\Firefox\\FirefoxPreferences' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Firefox/FirefoxPreferences.php',
'Facebook\\WebDriver\\Firefox\\FirefoxProfile' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Firefox/FirefoxProfile.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverButtonReleaseAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverButtonReleaseAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverClickAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverClickAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverClickAndHoldAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverClickAndHoldAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverContextClickAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverContextClickAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverCoordinates' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverCoordinates.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverDoubleClickAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverDoubleClickAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverKeyDownAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverKeyDownAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverKeyUpAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverKeyUpAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverKeysRelatedAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverKeysRelatedAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverMouseAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverMouseAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverMouseMoveAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverMouseMoveAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverMoveToOffsetAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverMoveToOffsetAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverSendKeysAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverSendKeysAction.php',
'Facebook\\WebDriver\\Interactions\\Internal\\WebDriverSingleKeyAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Internal/WebDriverSingleKeyAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverDoubleTapAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverDoubleTapAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverDownAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverDownAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverFlickAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverFlickAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverFlickFromElementAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverFlickFromElementAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverLongPressAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverLongPressAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverMoveAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverMoveAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverScrollAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverScrollAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverScrollFromElementAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverScrollFromElementAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverTapAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverTapAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverTouchAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverTouchAction.php',
'Facebook\\WebDriver\\Interactions\\Touch\\WebDriverTouchScreen' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/Touch/WebDriverTouchScreen.php',
'Facebook\\WebDriver\\Interactions\\WebDriverActions' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/WebDriverActions.php',
'Facebook\\WebDriver\\Interactions\\WebDriverCompositeAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/WebDriverCompositeAction.php',
'Facebook\\WebDriver\\Interactions\\WebDriverTouchActions' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Interactions/WebDriverTouchActions.php',
'Facebook\\WebDriver\\Internal\\WebDriverLocatable' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Internal/WebDriverLocatable.php',
'Facebook\\WebDriver\\JavaScriptExecutor' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/JavaScriptExecutor.php',
'Facebook\\WebDriver\\Local\\LocalWebDriver' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Local/LocalWebDriver.php',
'Facebook\\WebDriver\\Net\\URLChecker' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Net/URLChecker.php',
'Facebook\\WebDriver\\Remote\\CustomWebDriverCommand' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/CustomWebDriverCommand.php',
'Facebook\\WebDriver\\Remote\\DesiredCapabilities' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/DesiredCapabilities.php',
'Facebook\\WebDriver\\Remote\\DriverCommand' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/DriverCommand.php',
'Facebook\\WebDriver\\Remote\\ExecuteMethod' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/ExecuteMethod.php',
'Facebook\\WebDriver\\Remote\\FileDetector' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/FileDetector.php',
'Facebook\\WebDriver\\Remote\\HttpCommandExecutor' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/HttpCommandExecutor.php',
'Facebook\\WebDriver\\Remote\\JsonWireCompat' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/JsonWireCompat.php',
'Facebook\\WebDriver\\Remote\\LocalFileDetector' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/LocalFileDetector.php',
'Facebook\\WebDriver\\Remote\\RemoteExecuteMethod' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/RemoteExecuteMethod.php',
'Facebook\\WebDriver\\Remote\\RemoteKeyboard' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/RemoteKeyboard.php',
'Facebook\\WebDriver\\Remote\\RemoteMouse' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/RemoteMouse.php',
'Facebook\\WebDriver\\Remote\\RemoteStatus' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/RemoteStatus.php',
'Facebook\\WebDriver\\Remote\\RemoteTargetLocator' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/RemoteTargetLocator.php',
'Facebook\\WebDriver\\Remote\\RemoteTouchScreen' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/RemoteTouchScreen.php',
'Facebook\\WebDriver\\Remote\\RemoteWebDriver' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/RemoteWebDriver.php',
'Facebook\\WebDriver\\Remote\\RemoteWebElement' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/RemoteWebElement.php',
'Facebook\\WebDriver\\Remote\\Service\\DriverCommandExecutor' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/Service/DriverCommandExecutor.php',
'Facebook\\WebDriver\\Remote\\Service\\DriverService' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/Service/DriverService.php',
'Facebook\\WebDriver\\Remote\\ShadowRoot' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/ShadowRoot.php',
'Facebook\\WebDriver\\Remote\\UselessFileDetector' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/UselessFileDetector.php',
'Facebook\\WebDriver\\Remote\\WebDriverBrowserType' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/WebDriverBrowserType.php',
'Facebook\\WebDriver\\Remote\\WebDriverCapabilityType' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/WebDriverCapabilityType.php',
'Facebook\\WebDriver\\Remote\\WebDriverCommand' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/WebDriverCommand.php',
'Facebook\\WebDriver\\Remote\\WebDriverResponse' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Remote/WebDriverResponse.php',
'Facebook\\WebDriver\\Support\\Events\\EventFiringWebDriver' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Support/Events/EventFiringWebDriver.php',
'Facebook\\WebDriver\\Support\\Events\\EventFiringWebDriverNavigation' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Support/Events/EventFiringWebDriverNavigation.php',
'Facebook\\WebDriver\\Support\\Events\\EventFiringWebElement' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Support/Events/EventFiringWebElement.php',
'Facebook\\WebDriver\\Support\\IsElementDisplayedAtom' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Support/IsElementDisplayedAtom.php',
'Facebook\\WebDriver\\Support\\ScreenshotHelper' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Support/ScreenshotHelper.php',
'Facebook\\WebDriver\\Support\\XPathEscaper' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/Support/XPathEscaper.php',
'Facebook\\WebDriver\\WebDriver' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriver.php',
'Facebook\\WebDriver\\WebDriverAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverAction.php',
'Facebook\\WebDriver\\WebDriverAlert' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverAlert.php',
'Facebook\\WebDriver\\WebDriverBy' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverBy.php',
'Facebook\\WebDriver\\WebDriverCapabilities' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverCapabilities.php',
'Facebook\\WebDriver\\WebDriverCheckboxes' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverCheckboxes.php',
'Facebook\\WebDriver\\WebDriverCommandExecutor' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverCommandExecutor.php',
'Facebook\\WebDriver\\WebDriverDimension' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverDimension.php',
'Facebook\\WebDriver\\WebDriverDispatcher' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverDispatcher.php',
'Facebook\\WebDriver\\WebDriverElement' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverElement.php',
'Facebook\\WebDriver\\WebDriverEventListener' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverEventListener.php',
'Facebook\\WebDriver\\WebDriverExpectedCondition' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverExpectedCondition.php',
'Facebook\\WebDriver\\WebDriverHasInputDevices' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverHasInputDevices.php',
'Facebook\\WebDriver\\WebDriverKeyboard' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverKeyboard.php',
'Facebook\\WebDriver\\WebDriverKeys' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverKeys.php',
'Facebook\\WebDriver\\WebDriverMouse' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverMouse.php',
'Facebook\\WebDriver\\WebDriverNavigation' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverNavigation.php',
'Facebook\\WebDriver\\WebDriverNavigationInterface' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverNavigationInterface.php',
'Facebook\\WebDriver\\WebDriverOptions' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverOptions.php',
'Facebook\\WebDriver\\WebDriverPlatform' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverPlatform.php',
'Facebook\\WebDriver\\WebDriverPoint' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverPoint.php',
'Facebook\\WebDriver\\WebDriverRadios' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverRadios.php',
'Facebook\\WebDriver\\WebDriverSearchContext' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverSearchContext.php',
'Facebook\\WebDriver\\WebDriverSelect' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverSelect.php',
'Facebook\\WebDriver\\WebDriverSelectInterface' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverSelectInterface.php',
'Facebook\\WebDriver\\WebDriverTargetLocator' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverTargetLocator.php',
'Facebook\\WebDriver\\WebDriverTimeouts' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverTimeouts.php',
'Facebook\\WebDriver\\WebDriverUpAction' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverUpAction.php',
'Facebook\\WebDriver\\WebDriverWait' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverWait.php',
'Facebook\\WebDriver\\WebDriverWindow' => __DIR__ . '/..' . '/php-webdriver/webdriver/lib/WebDriverWindow.php',
'Faker\\Calculator\\Ean' => __DIR__ . '/..' . '/fakerphp/faker/src/Faker/Calculator/Ean.php',
'Faker\\Calculator\\Iban' => __DIR__ . '/..' . '/fakerphp/faker/src/Faker/Calculator/Iban.php',
'Faker\\Calculator\\Inn' => __DIR__ . '/..' . '/fakerphp/faker/src/Faker/Calculator/Inn.php',
@@ -3205,6 +3377,37 @@ class ComposerStaticInit626b9e7ddd47fb7eff9aaa53cce0c9ad
'Jorenvh\\Share\\Providers\\ShareServiceProvider' => __DIR__ . '/..' . '/jorenvanhocht/laravel-share/src/Providers/ShareServiceProvider.php',
'Jorenvh\\Share\\Share' => __DIR__ . '/..' . '/jorenvanhocht/laravel-share/src/Share.php',
'Jorenvh\\Share\\ShareFacade' => __DIR__ . '/..' . '/jorenvanhocht/laravel-share/src/ShareFacade.php',
'Laravel\\Dusk\\Browser' => __DIR__ . '/..' . '/laravel/dusk/src/Browser.php',
'Laravel\\Dusk\\Chrome\\ChromeProcess' => __DIR__ . '/..' . '/laravel/dusk/src/Chrome/ChromeProcess.php',
'Laravel\\Dusk\\Chrome\\SupportsChrome' => __DIR__ . '/..' . '/laravel/dusk/src/Chrome/SupportsChrome.php',
'Laravel\\Dusk\\Component' => __DIR__ . '/..' . '/laravel/dusk/src/Component.php',
'Laravel\\Dusk\\Concerns\\InteractsWithAuthentication' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/InteractsWithAuthentication.php',
'Laravel\\Dusk\\Concerns\\InteractsWithCookies' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/InteractsWithCookies.php',
'Laravel\\Dusk\\Concerns\\InteractsWithElements' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/InteractsWithElements.php',
'Laravel\\Dusk\\Concerns\\InteractsWithJavascript' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/InteractsWithJavascript.php',
'Laravel\\Dusk\\Concerns\\InteractsWithKeyboard' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/InteractsWithKeyboard.php',
'Laravel\\Dusk\\Concerns\\InteractsWithMouse' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/InteractsWithMouse.php',
'Laravel\\Dusk\\Concerns\\MakesAssertions' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/MakesAssertions.php',
'Laravel\\Dusk\\Concerns\\MakesUrlAssertions' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/MakesUrlAssertions.php',
'Laravel\\Dusk\\Concerns\\ProvidesBrowser' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/ProvidesBrowser.php',
'Laravel\\Dusk\\Concerns\\WaitsForElements' => __DIR__ . '/..' . '/laravel/dusk/src/Concerns/WaitsForElements.php',
'Laravel\\Dusk\\Console\\ChromeDriverCommand' => __DIR__ . '/..' . '/laravel/dusk/src/Console/ChromeDriverCommand.php',
'Laravel\\Dusk\\Console\\ComponentCommand' => __DIR__ . '/..' . '/laravel/dusk/src/Console/ComponentCommand.php',
'Laravel\\Dusk\\Console\\Concerns\\InteractsWithTestingFrameworks' => __DIR__ . '/..' . '/laravel/dusk/src/Console/Concerns/InteractsWithTestingFrameworks.php',
'Laravel\\Dusk\\Console\\DuskCommand' => __DIR__ . '/..' . '/laravel/dusk/src/Console/DuskCommand.php',
'Laravel\\Dusk\\Console\\DuskFailsCommand' => __DIR__ . '/..' . '/laravel/dusk/src/Console/DuskFailsCommand.php',
'Laravel\\Dusk\\Console\\InstallCommand' => __DIR__ . '/..' . '/laravel/dusk/src/Console/InstallCommand.php',
'Laravel\\Dusk\\Console\\MakeCommand' => __DIR__ . '/..' . '/laravel/dusk/src/Console/MakeCommand.php',
'Laravel\\Dusk\\Console\\PageCommand' => __DIR__ . '/..' . '/laravel/dusk/src/Console/PageCommand.php',
'Laravel\\Dusk\\Console\\PurgeCommand' => __DIR__ . '/..' . '/laravel/dusk/src/Console/PurgeCommand.php',
'Laravel\\Dusk\\Dusk' => __DIR__ . '/..' . '/laravel/dusk/src/Dusk.php',
'Laravel\\Dusk\\DuskServiceProvider' => __DIR__ . '/..' . '/laravel/dusk/src/DuskServiceProvider.php',
'Laravel\\Dusk\\ElementResolver' => __DIR__ . '/..' . '/laravel/dusk/src/ElementResolver.php',
'Laravel\\Dusk\\Http\\Controllers\\UserController' => __DIR__ . '/..' . '/laravel/dusk/src/Http/Controllers/UserController.php',
'Laravel\\Dusk\\Keyboard' => __DIR__ . '/..' . '/laravel/dusk/src/Keyboard.php',
'Laravel\\Dusk\\OperatingSystem' => __DIR__ . '/..' . '/laravel/dusk/src/OperatingSystem.php',
'Laravel\\Dusk\\Page' => __DIR__ . '/..' . '/laravel/dusk/src/Page.php',
'Laravel\\Dusk\\TestCase' => __DIR__ . '/..' . '/laravel/dusk/src/TestCase.php',
'Laravel\\Pail\\Console\\Commands\\PailCommand' => __DIR__ . '/..' . '/laravel/pail/src/Console/Commands/PailCommand.php',
'Laravel\\Pail\\Contracts\\Printer' => __DIR__ . '/..' . '/laravel/pail/src/Contracts/Printer.php',
'Laravel\\Pail\\File' => __DIR__ . '/..' . '/laravel/pail/src/File.php',
+148
View File
@@ -1849,6 +1849,83 @@
},
"install-path": "../jorenvanhocht/laravel-share"
},
{
"name": "laravel/dusk",
"version": "v8.5.0",
"version_normalized": "8.5.0.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/dusk.git",
"reference": "f9f75666bed46d1ebca13792447be6e753f4e790"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/dusk/zipball/f9f75666bed46d1ebca13792447be6e753f4e790",
"reference": "f9f75666bed46d1ebca13792447be6e753f4e790",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-zip": "*",
"guzzlehttp/guzzle": "^7.5",
"illuminate/console": "^10.0|^11.0|^12.0|^13.0",
"illuminate/support": "^10.0|^11.0|^12.0|^13.0",
"php": "^8.1",
"php-webdriver/webdriver": "^1.15.2",
"symfony/console": "^6.2|^7.0|^8.0",
"symfony/finder": "^6.2|^7.0|^8.0",
"symfony/process": "^6.2|^7.0|^8.0",
"vlucas/phpdotenv": "^5.2"
},
"require-dev": {
"laravel/framework": "^10.0|^11.0|^12.0|^13.0",
"mockery/mockery": "^1.6",
"orchestra/testbench-core": "^8.19|^9.17|^10.8|^11.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.1|^11.0|^12.0.1",
"psy/psysh": "^0.11.12|^0.12",
"symfony/yaml": "^6.2|^7.0|^8.0"
},
"suggest": {
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
},
"time": "2026-03-21T11:50:49+00:00",
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Dusk\\DuskServiceProvider"
]
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Laravel\\Dusk\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel Dusk provides simple end-to-end testing and browser automation.",
"keywords": [
"laravel",
"testing",
"webdriver"
],
"support": {
"issues": "https://github.com/laravel/dusk/issues",
"source": "https://github.com/laravel/dusk/tree/v8.5.0"
},
"install-path": "../laravel/dusk"
},
{
"name": "laravel/framework",
"version": "v11.39.1",
@@ -4483,6 +4560,75 @@
},
"install-path": "../phar-io/version"
},
{
"name": "php-webdriver/webdriver",
"version": "1.16.0",
"version_normalized": "1.16.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-webdriver/php-webdriver.git",
"reference": "ac0662863aa120b4f645869f584013e4c4dba46a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/ac0662863aa120b4f645869f584013e4c4dba46a",
"reference": "ac0662863aa120b4f645869f584013e4c4dba46a",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-zip": "*",
"php": "^7.3 || ^8.0",
"symfony/polyfill-mbstring": "^1.12",
"symfony/process": "^5.0 || ^6.0 || ^7.0 || ^8.0"
},
"replace": {
"facebook/webdriver": "*"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.20.0",
"ondram/ci-detector": "^4.0",
"php-coveralls/php-coveralls": "^2.4",
"php-mock/php-mock-phpunit": "^2.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.5",
"symfony/var-dumper": "^5.0 || ^6.0 || ^7.0 || ^8.0"
},
"suggest": {
"ext-simplexml": "For Firefox profile creation"
},
"time": "2025-12-28T23:57:40+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"files": [
"lib/Exception/TimeoutException.php"
],
"psr-4": {
"Facebook\\WebDriver\\": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
"homepage": "https://github.com/php-webdriver/php-webdriver",
"keywords": [
"Chromedriver",
"geckodriver",
"php",
"selenium",
"webdriver"
],
"support": {
"issues": "https://github.com/php-webdriver/php-webdriver/issues",
"source": "https://github.com/php-webdriver/php-webdriver/tree/1.16.0"
},
"install-path": "../php-webdriver/webdriver"
},
{
"name": "phpoffice/phpspreadsheet",
"version": "4.2.0",
@@ -9871,6 +10017,7 @@
"fakerphp/faker",
"filp/whoops",
"hamcrest/hamcrest-php",
"laravel/dusk",
"laravel/pail",
"laravel/pint",
"laravel/sail",
@@ -9879,6 +10026,7 @@
"nunomaduro/collision",
"phar-io/manifest",
"phar-io/version",
"php-webdriver/webdriver",
"phpunit/php-code-coverage",
"phpunit/php-file-iterator",
"phpunit/php-invoker",
+30 -6
View File
@@ -1,11 +1,11 @@
<?php return array(
'root' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => NULL,
'reference' => '9d0a872e912d960d1610dbb1bc23ecfc024e94c9',
'name' => 'laravel/laravel',
'dev' => true,
),
@@ -148,6 +148,12 @@
'reference' => 'b115554301161fa21467629f1e1391c1936de517',
'dev_requirement' => false,
),
'facebook/webdriver' => array(
'dev_requirement' => true,
'replaced' => array(
0 => '*',
),
),
'fakerphp/faker' => array(
'pretty_version' => 'v1.24.1',
'version' => '1.24.1.0',
@@ -457,6 +463,15 @@
0 => '*',
),
),
'laravel/dusk' => array(
'pretty_version' => 'v8.5.0',
'version' => '8.5.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/dusk',
'aliases' => array(),
'reference' => 'f9f75666bed46d1ebca13792447be6e753f4e790',
'dev_requirement' => true,
),
'laravel/framework' => array(
'pretty_version' => 'v11.39.1',
'version' => '11.39.1.0',
@@ -467,12 +482,12 @@
'dev_requirement' => false,
),
'laravel/laravel' => array(
'pretty_version' => '1.0.0+no-version-set',
'version' => '1.0.0.0',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'reference' => NULL,
'reference' => '9d0a872e912d960d1610dbb1bc23ecfc024e94c9',
'dev_requirement' => false,
),
'laravel/pail' => array(
@@ -769,6 +784,15 @@
'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74',
'dev_requirement' => true,
),
'php-webdriver/webdriver' => array(
'pretty_version' => '1.16.0',
'version' => '1.16.0.0',
'type' => 'library',
'install_path' => __DIR__ . '/../php-webdriver/webdriver',
'aliases' => array(),
'reference' => 'ac0662863aa120b4f645869f584013e4c4dba46a',
'dev_requirement' => true,
),
'phpoffice/phpspreadsheet' => array(
'pretty_version' => '4.2.0',
'version' => '4.2.0.0',
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+32
View File
@@ -0,0 +1,32 @@
<p align="center"><img width="309" height="86" 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://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
Laravel Dusk provides an expressive, easy-to-use browser automation and testing API. By default, Dusk does not require you to install JDK or Selenium on your machine. Instead, Dusk uses a standalone Chromedriver. However, you are free to utilize any other Selenium driver you wish.
## Official Documentation
Documentation for Dusk can be found on the [Laravel website](https://laravel.com/docs/dusk).
## Contributing
Thank you for considering contributing to Dusk! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
Please review [our security policy](https://github.com/laravel/dusk/security/policy) on how to report security vulnerabilities.
## License
Laravel Dusk is open-sourced software licensed under the [MIT license](LICENSE.md).
+19
View File
@@ -0,0 +1,19 @@
<svg width="440" height="64" viewBox="0 0 440 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<style>
.laravel { fill: #1B1B18 } @media (prefers-color-scheme:dark) { .laravel { fill: #fff } }
.dusk { fill: #868682 } @media (prefers-color-scheme:dark) { .dusk { fill: #A1A09A } }
</style>
<rect width="64" height="64" fill="#B13BC7"/>
<path d="M28.7056 35.2944C22.434 29.0229 20.9407 19.6901 24.3752 12C22.1353 13.0453 19.9701 14.4638 18.1036 16.3304C9.96547 24.4685 9.96547 37.7583 18.1036 45.8965C26.2417 54.0345 39.5315 54.0345 47.6696 45.8965C49.5362 44.0299 50.9547 41.9393 52 39.6248C44.3845 43.0592 35.0518 41.6407 28.7056 35.2944Z" fill="#FDFDFC"/>
<path class="laravel" d="M103.659 7.625H96V55.5535H118.046V48.5012H103.659V7.625Z"/>
<path class="laravel" d="M145.087 27.9603C144.109 26.3627 142.722 25.1079 140.924 24.1946C139.126 23.2817 137.316 22.8251 135.496 22.8251C133.141 22.8251 130.988 23.2703 129.035 24.1602C127.08 25.0501 125.404 26.2723 124.006 27.8235C122.607 29.3757 121.519 31.1675 120.742 33.1978C119.964 35.2297 119.576 37.3643 119.576 39.6003C119.576 41.8831 119.964 44.0283 120.742 46.0362C121.519 48.0455 122.607 49.8258 124.006 51.377C125.404 52.9292 127.08 54.15 129.035 55.0399C130.988 55.9298 133.141 56.375 135.496 56.375C137.316 56.375 139.126 55.9183 140.924 55.0059C142.722 54.0936 144.11 52.8383 145.087 51.2402V55.5539H152.346V23.6471H145.087V27.9603ZM144.421 43.5027C143.977 44.7355 143.355 45.8081 142.556 46.7209C141.756 47.6342 140.791 48.3644 139.659 48.912C138.526 49.46 137.271 49.7335 135.895 49.7335C134.518 49.7335 133.275 49.46 132.165 48.912C131.054 48.364 130.1 47.6342 129.301 46.7209C128.502 45.8081 127.891 44.7355 127.469 43.5027C127.047 42.2705 126.837 40.9693 126.837 39.6003C126.837 38.2307 127.047 36.9296 127.469 35.6973C127.89 34.465 128.502 33.3929 129.301 32.4796C130.1 31.5672 131.055 30.8361 132.165 30.2886C133.275 29.7406 134.518 29.467 135.895 29.467C137.271 29.467 138.526 29.7406 139.659 30.2886C140.791 30.8366 141.756 31.5672 142.556 32.4796C143.355 33.3929 143.977 34.465 144.421 35.6973C144.864 36.9296 145.087 38.2307 145.087 39.6003C145.087 40.9693 144.864 42.2705 144.421 43.5027Z"/>
<path class="laravel" d="M204.099 27.9603C203.121 26.3627 201.734 25.1079 199.936 24.1946C198.138 23.2817 196.328 22.8251 194.508 22.8251C192.153 22.8251 190.001 23.2703 188.047 24.1602C186.093 25.0501 184.417 26.2723 183.018 27.8235C181.619 29.3757 180.531 31.1675 179.754 33.1978C178.977 35.2297 178.589 37.3643 178.589 39.6003C178.589 41.8831 178.977 44.0283 179.754 46.0362C180.531 48.0455 181.619 49.8258 183.018 51.377C184.417 52.9292 186.092 54.15 188.047 55.0399C190 55.9298 192.153 56.375 194.508 56.375C196.328 56.375 198.138 55.9183 199.936 55.0059C201.734 54.0936 203.122 52.8383 204.099 51.2402V55.5539H211.359V23.6471H204.099V27.9603ZM203.433 43.5027C202.988 44.7355 202.367 45.8081 201.568 46.7209C200.768 47.6342 199.803 48.3644 198.67 48.912C197.538 49.46 196.283 49.7335 194.907 49.7335C193.53 49.7335 192.286 49.46 191.177 48.912C190.066 48.364 189.112 47.6342 188.313 46.7209C187.513 45.8081 186.903 44.7355 186.481 43.5027C186.058 42.2705 185.849 40.9693 185.849 39.6003C185.849 38.2307 186.058 36.9296 186.481 35.6973C186.902 34.465 187.513 33.3929 188.313 32.4796C189.112 31.5672 190.067 30.8361 191.177 30.2886C192.286 29.7406 193.53 29.467 194.907 29.467C196.283 29.467 197.538 29.7406 198.67 30.2886C199.803 30.8366 200.768 31.5672 201.568 32.4796C202.367 33.3929 202.988 34.465 203.433 35.6973C203.876 36.9296 204.099 38.2307 204.099 39.6003C204.099 40.9693 203.876 42.2705 203.433 43.5027Z"/>
<path class="laravel" d="M291 7.625H283.741V55.5535H291V7.625Z"/>
<path class="laravel" d="M158.075 55.5535H165.335V30.9901H177.79V23.6471H158.075V55.5535Z"/>
<path class="laravel" d="M240.465 23.6471L231.34 48.0804L222.216 23.6471H214.862L226.778 55.5535H235.902L247.818 23.6471H240.465Z"/>
<path class="laravel" d="M263.652 22.8265C254.762 22.8265 247.725 30.3369 247.725 39.6007C247.725 49.8416 254.536 56.375 264.582 56.375C270.205 56.375 273.795 54.1648 278.181 49.3514L273.276 45.4504C273.273 45.4542 269.574 50.4469 264.051 50.4469C257.631 50.4469 254.928 45.1262 254.928 42.3733H279.013C280.278 31.8326 273.537 22.8265 263.652 22.8265ZM254.947 36.8278C255.003 36.2138 255.839 28.7541 263.594 28.7541C271.349 28.7541 272.291 36.2128 272.345 36.8278H254.947Z"/>
<path class="dusk" d="M411.686 55.6851V7.68506H416.726V55.6851H411.686ZM416.096 40.8801L432.476 23.5551H438.776L424.916 38.2341L424.412 38.2971L415.718 47.2431L416.096 40.8801ZM426.176 36.5331L439.595 55.6851H433.673L421.577 38.0451L426.176 36.5331Z"/>
<path class="dusk" d="M393.623 56.3151C389.675 56.3151 386.525 55.4121 384.173 53.6061C381.821 51.7581 380.477 49.1541 380.141 45.7941H384.992C385.286 47.8521 386.189 49.4271 387.701 50.5191C389.213 51.611 391.229 52.1571 393.749 52.1571C395.975 52.1571 397.718 51.737 398.978 50.897C400.238 50.057 400.868 48.8601 400.868 47.306C400.868 46.2141 400.511 45.2691 399.797 44.4711C399.083 43.6311 397.655 42.9381 395.513 42.3921L390.347 41.069C387.407 40.313 385.202 39.1581 383.732 37.604C382.262 36.008 381.527 34.1181 381.527 31.9341C381.527 29.1621 382.556 26.9781 384.614 25.382C386.672 23.744 389.465 22.925 392.993 22.925C396.479 22.925 399.314 23.765 401.498 25.445C403.682 27.125 404.921 29.456 405.215 32.438H400.364C400.07 30.716 399.272 29.393 397.97 28.469C396.668 27.545 394.946 27.083 392.804 27.083C390.746 27.083 389.15 27.482 388.016 28.28C386.882 29.036 386.315 30.128 386.315 31.556C386.315 32.648 386.714 33.593 387.512 34.391C388.352 35.189 389.738 35.8611 391.67 36.4071L396.71 37.793C399.692 38.591 401.939 39.8091 403.451 41.4471C404.963 43.0851 405.719 45.0591 405.719 47.3691C405.719 50.1831 404.648 52.3881 402.506 53.9841C400.364 55.5381 397.403 56.3151 393.623 56.3151Z"/>
<path class="dusk" d="M358.207 56.3151C356.107 56.3151 354.238 55.8951 352.6 55.0551C350.962 54.1731 349.681 52.9551 348.757 51.4011C347.833 49.8051 347.371 47.9781 347.371 45.9201V23.5551H352.411V44.5971C352.411 47.0331 352.999 48.8601 354.175 50.0781C355.393 51.2961 357.115 51.9051 359.341 51.9051C361.357 51.9051 363.142 51.4431 364.696 50.5191C366.25 49.5951 367.468 48.3141 368.35 46.6761C369.274 44.9961 369.736 43.0851 369.736 40.9431L370.555 48.5661C369.505 50.9601 367.867 52.8501 365.641 54.2361C363.415 55.6221 360.937 56.3151 358.207 56.3151ZM370.051 55.6851V48.1251H369.736V23.5551H374.776V55.6851H370.051Z"/>
<path class="dusk" d="M306.087 55.6851V51.1491H318.246C322.026 51.1491 325.281 50.3931 328.011 48.8811C330.741 47.3271 332.841 45.1641 334.311 42.3921C335.781 39.6201 336.516 34.7671 336.516 31.1131C336.516 27.5011 335.781 23.3511 334.311 20.6631C332.841 17.9751 330.741 15.8961 328.011 14.4261C325.323 12.9561 322.068 12.2211 318.246 12.2211H306.087V7.68506H318.246C322.992 7.68506 327.129 8.63006 330.657 10.5201C334.185 12.3681 336.915 14.9931 338.847 18.3951C340.821 21.7551 341.808 26.7031 341.808 31.2391C341.808 35.7751 340.821 41.4051 338.847 44.8491C336.873 48.2511 334.122 50.9181 330.594 52.8501C327.108 54.7401 323.013 55.6851 318.309 55.6851H306.087ZM303 55.6851V7.68506H308.166V55.6851H303Z"/>
</svg>

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
View File
Binary file not shown.
File diff suppressed because one or more lines are too long
+86
View File
@@ -0,0 +1,86 @@
{
"name": "laravel/dusk",
"description": "Laravel Dusk provides simple end-to-end testing and browser automation.",
"keywords": [
"laravel",
"testing",
"webdriver"
],
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^8.1",
"ext-json": "*",
"ext-zip": "*",
"guzzlehttp/guzzle": "^7.5",
"illuminate/console": "^10.0|^11.0|^12.0|^13.0",
"illuminate/support": "^10.0|^11.0|^12.0|^13.0",
"php-webdriver/webdriver": "^1.15.2",
"symfony/console": "^6.2|^7.0|^8.0",
"symfony/finder": "^6.2|^7.0|^8.0",
"symfony/process": "^6.2|^7.0|^8.0",
"vlucas/phpdotenv": "^5.2"
},
"require-dev": {
"laravel/framework": "^10.0|^11.0|^12.0|^13.0",
"mockery/mockery": "^1.6",
"orchestra/testbench-core": "^8.19|^9.17|^10.8|^11.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.1|^11.0|^12.0.1",
"psy/psysh": "^0.11.12|^0.12",
"symfony/yaml": "^6.2|^7.0|^8.0"
},
"suggest": {
"ext-pcntl": "Used to gracefully terminate Dusk when tests are running."
},
"autoload": {
"psr-4": {
"Laravel\\Dusk\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Laravel\\Dusk\\Tests\\": "tests/"
}
},
"extra": {
"laravel": {
"providers": [
"Laravel\\Dusk\\DuskServiceProvider"
]
}
},
"config": {
"audit": {
"block-insecure": false
},
"sort-packages": true
},
"scripts": {
"post-autoload-dump": [
"@clear",
"@prepare"
],
"clear": "@php vendor/bin/testbench package:purge-skeleton --ansi",
"prepare": "@php vendor/bin/testbench package:discover --ansi",
"build": "@php vendor/bin/testbench workbench:build --ansi",
"serve": [
"@build",
"@php vendor/bin/testbench serve"
],
"lint": [
"@php vendor/bin/phpstan analyse"
],
"test": [
"@build",
"@php vendor/bin/phpunit"
]
},
"minimum-stability": "dev",
"prefer-stable": true
}
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
beStrictAboutTestsThatDoNotTestAnything="false"
colors="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
cacheDirectory=".phpunit.cache"
backupStaticProperties="false">
<testsuites>
<testsuite name="Browser Test Suite">
<directory suffix="Test.php">./tests/Browser</directory>
</testsuite>
</testsuites>
</phpunit>
+850
View File
@@ -0,0 +1,850 @@
<?php
namespace Laravel\Dusk;
use BadMethodCallException;
use Closure;
use Facebook\WebDriver\Remote\WebDriverBrowserType;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverDimension;
use Facebook\WebDriver\WebDriverPoint;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
class Browser
{
use Concerns\InteractsWithAuthentication,
Concerns\InteractsWithCookies,
Concerns\InteractsWithElements,
Concerns\InteractsWithJavascript,
Concerns\InteractsWithMouse,
Concerns\MakesAssertions,
Concerns\MakesUrlAssertions,
Concerns\WaitsForElements,
Macroable {
__call as macroCall;
}
/**
* The base URL for all URLs.
*
* @var string
*/
public static $baseUrl;
/**
* The directory that will contain any screenshots.
*
* @var string
*/
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.
*
* @var string
*/
public static $storeConsoleLogAt;
/**
* The directory where source code snapshots will be stored.
*
* @var string
*/
public static $storeSourceAt;
/**
* Console log messages to ignore when storing console logs.
*
* @var array
*/
public static $ignoreConsoleMessages = [
'favicon.ico',
];
/**
* The browsers that support retrieving logs.
*
* @var array
*/
public static $supportsRemoteLogs = [
WebDriverBrowserType::CHROME,
WebDriverBrowserType::PHANTOMJS,
];
/**
* Get the callback which resolves the default user to authenticate.
*
* @var \Closure
*/
public static $userResolver;
/**
* The default wait time in seconds.
*
* @var int
*/
public static $waitSeconds = 5;
/**
* The RemoteWebDriver instance.
*
* @var \Facebook\WebDriver\Remote\RemoteWebDriver
*/
public $driver;
/**
* The element resolver instance.
*
* @var \Laravel\Dusk\ElementResolver
*/
public $resolver;
/**
* The page object currently being viewed.
*
* @var mixed
*/
public $page;
/**
* The component object currently being viewed.
*
* @var mixed
*/
public $component;
/**
* Indicates that the browser should be resized to fit the entire "body" before screenshotting failures.
*
* @var bool
*/
public $fitOnFailure = true;
/**
* Create a browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @param \Laravel\Dusk\ElementResolver|null $resolver
* @return void
*/
public function __construct($driver, $resolver = null)
{
$this->driver = $driver;
$this->resolver = $resolver ?: new ElementResolver($driver);
}
/**
* Browse to the given URL.
*
* @param string|Page $url
* @return $this
*/
public function visit($url)
{
// First, if the URL is an object it means we are actually dealing with a page
// and we need to create this page then get the URL from the page object as
// it contains the URL. Once that is done, we will be ready to format it.
if (is_object($url)) {
$page = $url;
$url = $page->url();
}
// If the URL does not start with http or https, then we will prepend the base
// URL onto the URL and navigate to the URL. This will actually navigate to
// the URL in the browser. Then we will be ready to make assertions, etc.
if (! Str::startsWith($url, ['http://', 'https://'])) {
$url = static::$baseUrl.'/'.ltrim($url, '/');
}
$this->driver->navigate()->to($url);
// If the page variable was set, we will call the "on" method which will set a
// page instance variable and call an assert method on the page so that the
// page can have the chance to verify that we are within the right pages.
if (isset($page)) {
$this->on($page);
}
return $this;
}
/**
* Browse to the given route.
*
* @param string $route
* @param array $parameters
* @return $this
*/
public function visitRoute($route, $parameters = [])
{
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.
*
* @param mixed $page
* @return $this
*/
public function on($page)
{
$this->onWithoutAssert($page);
$page->assert($this);
return $this;
}
/**
* Set the current page object without executing the assertions.
*
* @param mixed $page
* @return $this
*/
public function onWithoutAssert($page)
{
$this->page = $page;
// Here we will set the page elements on the resolver instance, which will allow
// the developer to access short-cuts for CSS selectors on the page which can
// allow for more expressive navigation and interaction with all the pages.
$this->resolver->pageElements(array_merge(
$page::siteElements(), $page->elements()
));
return $this;
}
/**
* Refresh the page.
*
* @return $this
*/
public function refresh()
{
$this->driver->navigate()->refresh();
return $this;
}
/**
* Navigate to the previous page.
*
* @return $this
*/
public function back()
{
$this->driver->navigate()->back();
return $this;
}
/**
* Navigate to the next page.
*
* @return $this
*/
public function forward()
{
$this->driver->navigate()->forward();
return $this;
}
/**
* Maximize the browser window.
*
* @return $this
*/
public function maximize()
{
$this->driver->manage()->window()->maximize();
return $this;
}
/**
* Resize the browser window.
*
* @param int $width
* @param int $height
* @return $this
*/
public function resize($width, $height)
{
$this->driver->manage()->window()->setSize(
new WebDriverDimension($width, $height)
);
return $this;
}
/**
* Make the browser window as large as the content.
*
* @return $this
*/
public function fitContent()
{
$this->driver->switchTo()->defaultContent();
$html = $this->driver->findElement(WebDriverBy::tagName('html'));
if (! empty($html) && $html->getSize()->getWidth() > 0 && $html->getSize()->getHeight() > 0) {
$this->resize($html->getSize()->getWidth(), $html->getSize()->getHeight());
}
return $this;
}
/**
* Disable fit on failures.
*
* @return $this
*/
public function disableFitOnFailure()
{
$this->fitOnFailure = false;
return $this;
}
/**
* Enable fit on failures.
*
* @return $this
*/
public function enableFitOnFailure()
{
$this->fitOnFailure = true;
return $this;
}
/**
* Move the browser window.
*
* @param int $x
* @param int $y
* @return $this
*/
public function move($x, $y)
{
$this->driver->manage()->window()->setPosition(
new WebDriverPoint($x, $y)
);
return $this;
}
/**
* Scroll 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.
*
* @param string $selector
* @return $this
*/
public function scrollTo($selector)
{
$this->ensurejQueryIsAvailable();
$selector = addslashes($this->resolver->format($selector));
$this->driver->executeScript("jQuery(\"html, body\").animate({scrollTop: jQuery(\"$selector\").offset().top}, 0);");
return $this;
}
/**
* Take a screenshot and store it with the given name.
*
* @param string $name
* @return $this
*/
public function screenshot($name)
{
$filePath = sprintf('%s/%s.png', rtrim(static::$storeScreenshotsAt, '/'), $name);
$directoryPath = dirname($filePath);
if (! is_dir($directoryPath)) {
mkdir($directoryPath, 0777, true);
}
$this->driver->takeScreenshot($filePath);
return $this;
}
/**
* 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;
}
/**
* Take a screenshot of a specific element and store it with the given name.
*
* @param string $selector
* @param string $name
* @return $this
*/
public function screenshotElement($selector, $name)
{
$filePath = sprintf('%s/%s.png', rtrim(static::$storeScreenshotsAt, '/'), $name);
$directoryPath = dirname($filePath);
if (! is_dir($directoryPath)) {
mkdir($directoryPath, 0777, true);
}
$this->scrollIntoView($selector)
->driver->findElement(WebDriverBy::cssSelector($this->resolver->format($selector)))
->takeElementScreenshot($filePath);
return $this;
}
/**
* Store the console output with the given name.
*
* @param string $name
* @return $this
*/
public function storeConsoleLog($name)
{
if (in_array($this->driver->getCapabilities()->getBrowserName(), static::$supportsRemoteLogs)) {
$console = collect($this->driver->manage()->getLog('browser'))
->reject(fn ($entry) => Str::contains($entry['message'] ?? '', static::$ignoreConsoleMessages))
->values()
->all();
if (! empty($console)) {
$filePath = sprintf('%s/%s.log', rtrim(static::$storeConsoleLogAt, '/'), $name);
$directoryPath = dirname($filePath);
if (! is_dir($directoryPath)) {
mkdir($directoryPath, 0777, true);
}
file_put_contents(
$filePath, json_encode($console, JSON_PRETTY_PRINT)
);
}
}
return $this;
}
/**
* Store a snapshot of the page's current source code with the given name.
*
* @param string $name
* @return $this
*/
public function storeSource($name)
{
$source = $this->driver->getPageSource();
if (! empty($source)) {
$filePath = sprintf('%s/%s.txt', rtrim(static::$storeSourceAt, '/'), $name);
$directoryPath = dirname($filePath);
if (! is_dir($directoryPath)) {
mkdir($directoryPath, 0777, true);
}
file_put_contents($filePath, $source);
}
return $this;
}
/**
* Switch to a specified frame in the browser and execute the given callback.
*
* @param string $selector
* @param \Closure $callback
* @return $this
*/
public function withinFrame($selector, Closure $callback)
{
$this->driver->switchTo()->frame($this->resolver->findOrFail($selector));
$callback($this);
$this->driver->switchTo()->defaultContent();
return $this;
}
/**
* Execute a Closure with a scoped browser instance.
*
* @param string|\Laravel\Dusk\Component $selector
* @param \Closure $callback
* @return $this
*/
public function within($selector, Closure $callback)
{
return $this->with($selector, $callback);
}
/**
* Execute a Closure with a scoped browser instance.
*
* @param string|\Laravel\Dusk\Component $selector
* @param \Closure $callback
* @return $this
*/
public function with($selector, Closure $callback)
{
$browser = new static(
$this->driver, new ElementResolver($this->driver, $this->resolver->format($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.
*
* @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);
});
}
/**
* Return a browser scoped to the given component.
*
* @param \Laravel\Dusk\Component $component
* @return \Laravel\Dusk\Browser
*/
public function component(Component $component)
{
$browser = new static(
$this->driver, new ElementResolver($this->driver, $this->resolver->format($component))
);
if ($this->page) {
$browser->onWithoutAssert($this->page);
}
$browser->onComponent($component, $this->resolver);
return $browser;
}
/**
* Set the current component state.
*
* @param \Laravel\Dusk\Component $component
* @param \Laravel\Dusk\ElementResolver $parentResolver
* @return void
*/
public function onComponent($component, $parentResolver)
{
$this->component = $component;
// Here we will set the component elements on the resolver instance, which will allow
// the developer to access short-cuts for CSS selectors on the component which can
// allow for more expressive navigation and interaction with all the components.
$this->resolver->pageElements(
$component->elements() + $parentResolver->elements
);
$component->assert($this);
$this->resolver->prefix = $this->resolver->format(
$component->selector()
);
}
/**
* Ensure that jQuery is available on the page.
*
* @return void
*/
public function ensurejQueryIsAvailable()
{
if ($this->driver->executeScript('return window.jQuery == null')) {
$this->driver->executeScript(file_get_contents(__DIR__.'/../bin/jquery.js'));
}
}
/**
* Pause for the given amount of milliseconds.
*
* @param int $milliseconds
* @return $this
*/
public function pause($milliseconds)
{
usleep($milliseconds * 1000);
return $this;
}
/**
* Pause for the given amount of milliseconds if the given condition is true.
*
* @param bool $boolean
* @param int $milliseconds
* @return $this
*/
public function pauseIf($boolean, $milliseconds)
{
if ($boolean) {
return $this->pause($milliseconds);
}
return $this;
}
/**
* Pause for the given amount of milliseconds unless the given condition is true.
*
* @param bool $boolean
* @param int $milliseconds
* @return $this
*/
public function pauseUnless($boolean, $milliseconds)
{
if (! $boolean) {
return $this->pause($milliseconds);
}
return $this;
}
/**
* Close the browser.
*
* @return void
*/
public function quit()
{
$this->driver->quit();
}
/**
* Tap the browser into a callback.
*
* @param \Closure $callback
* @return $this
*/
public function tap($callback)
{
$callback($this);
return $this;
}
/**
* Dump the content from the last response.
*
* @return $this
*/
public function dump()
{
dump($this->driver->getPageSource());
return $this;
}
/**
* Dump and die the content from the last response.
*
* @return void
*/
public function dd()
{
dump($this->driver->getPageSource());
$this->quit();
exit;
}
/**
* Pause execution of test and open Laravel Tinker (PsySH) REPL.
*
* @return $this
*/
public function tinker()
{
\Psy\debug([
'browser' => $this,
'driver' => $this->driver,
'resolver' => $this->resolver,
'page' => $this->page,
], $this);
return $this;
}
/**
* Stop running tests but leave the browser open.
*
* @return void
*/
public function stop()
{
exit;
}
/**
* Dynamically call a method on the browser.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
if ($this->component && method_exists($this->component, $method)) {
array_unshift($parameters, $this);
$this->component->{$method}(...$parameters);
return $this;
}
if ($this->page && method_exists($this->page, $method)) {
array_unshift($parameters, $this);
$this->page->{$method}(...$parameters);
return $this;
}
throw new BadMethodCallException("Call to undefined method [{$method}].");
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
namespace Laravel\Dusk\Chrome;
use Laravel\Dusk\OperatingSystem;
use RuntimeException;
use Symfony\Component\Process\Process;
class ChromeProcess
{
/**
* The path to the Chromedriver.
*
* @var string|null
*/
protected $driver;
/**
* Create a new ChromeProcess instance.
*
* @param string|null $driver
* @return void
*/
public function __construct($driver = null)
{
$this->driver = $driver;
}
/**
* Build the process to run Chromedriver.
*
* @param array $arguments
* @return \Symfony\Component\Process\Process
*
* @throws \RuntimeException
*/
public function toProcess(array $arguments = [])
{
if ($this->driver) {
$driver = $this->driver;
} else {
$filenames = [
'linux' => 'chromedriver-linux',
'mac' => 'chromedriver-mac',
'mac-intel' => 'chromedriver-mac-intel',
'mac-arm' => 'chromedriver-mac-arm',
'win' => 'chromedriver-win.exe',
];
$driver = __DIR__.'/../../bin'.DIRECTORY_SEPARATOR.$filenames[$this->operatingSystemId()];
}
$this->driver = realpath($driver);
if ($this->driver === false) {
throw new RuntimeException(
"Invalid path to Chromedriver [{$driver}]. Make sure to install the Chromedriver first by running the dusk:chrome-driver command."
);
}
return $this->process($arguments);
}
/**
* Build the Chromedriver with Symfony Process.
*
* @param array $arguments
* @return \Symfony\Component\Process\Process
*/
protected function process(array $arguments = [])
{
return new Process(
array_merge([$this->driver], $arguments), null, $this->chromeEnvironment()
);
}
/**
* Get the Chromedriver environment variables.
*
* @return array
*/
protected function chromeEnvironment()
{
if ($this->onMac() || $this->onWindows()) {
return [];
}
return ['DISPLAY' => $_ENV['DISPLAY'] ?? ':0'];
}
/**
* Determine if Dusk is running on Windows or Windows Subsystem for Linux.
*
* @return bool
*/
protected function onWindows()
{
return OperatingSystem::onWindows();
}
/**
* Determine if Dusk is running on Mac.
*
* @return bool
*/
protected function onMac()
{
return OperatingSystem::onMac();
}
/**
* Determine OS ID.
*
* @return string
*/
protected function operatingSystemId()
{
return OperatingSystem::id();
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
namespace Laravel\Dusk\Chrome;
trait SupportsChrome
{
/**
* The path to the custom Chromedriver binary.
*
* @var string|null
*/
protected static $chromeDriver;
/**
* The Chromedriver process instance.
*
* @var \Symfony\Component\Process\Process
*/
protected static $chromeProcess;
/**
* Start the Chromedriver process.
*
* @param array $arguments
* @return void
*
* @throws \RuntimeException
*/
public static function startChromeDriver(array $arguments = [])
{
static::$chromeProcess = static::buildChromeProcess($arguments);
static::$chromeProcess->start();
static::afterClass(function () {
static::stopChromeDriver();
});
}
/**
* Stop the Chromedriver process.
*
* @return void
*/
public static function stopChromeDriver()
{
if (static::$chromeProcess) {
static::$chromeProcess->stop();
}
}
/**
* Build the process to run the Chromedriver.
*
* @param array $arguments
* @return \Symfony\Component\Process\Process
*
* @throws \RuntimeException
*/
protected static function buildChromeProcess(array $arguments = [])
{
return (new ChromeProcess(static::$chromeDriver))->toProcess($arguments);
}
/**
* Set the path to the custom Chromedriver.
*
* @param string $path
* @return void
*/
public static function useChromedriver($path)
{
static::$chromeDriver = $path;
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace Laravel\Dusk;
abstract class Component
{
/**
* Get the root selector associated with this component.
*
* @return string
*/
abstract public function selector();
/**
* Assert that the current page contains this component.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
//
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [];
}
/**
* Allow this class to be used in place of a selector string.
*
* @return string
*/
public function __toString()
{
return '';
}
}
@@ -0,0 +1,123 @@
<?php
namespace Laravel\Dusk\Concerns;
use Laravel\Dusk\Browser;
use PHPUnit\Framework\Assert as PHPUnit;
trait InteractsWithAuthentication
{
/**
* Log into the application as the default user.
*
* @return $this
*/
public function login()
{
return $this->loginAs(call_user_func(Browser::$userResolver));
}
/**
* Log into the application using a given user ID or email.
*
* @param object|string $userId
* @param string|null $guard
* @return $this
*/
public function loginAs($userId, $guard = null)
{
$userId = is_object($userId) && method_exists($userId, 'getKey') ? $userId->getKey() : $userId;
return $this->visit(rtrim(route('dusk.login', ['userId' => $userId, 'guard' => $guard], $this->shouldUseAbsoluteRouteForAuthentication())));
}
/**
* Log out of the application.
*
* @param string|null $guard
* @return $this
*/
public function logout($guard = null)
{
return $this->visit(rtrim(route('dusk.logout', ['guard' => $guard], $this->shouldUseAbsoluteRouteForAuthentication()), '/'));
}
/**
* Get the ID and the class name of the authenticated user.
*
* @param string|null $guard
* @return array
*/
protected function currentUserInfo($guard = null)
{
$response = $this->visit(route('dusk.user', ['guard' => $guard], $this->shouldUseAbsoluteRouteForAuthentication()));
return json_decode(strip_tags($response->driver->getPageSource()), true);
}
/**
* Assert that the user is authenticated.
*
* @param string|null $guard
* @return $this
*/
public function assertAuthenticated($guard = null)
{
$currentUrl = $this->driver->getCurrentURL();
PHPUnit::assertNotEmpty($this->currentUserInfo($guard), 'The user is not authenticated.');
return $this->visit($currentUrl);
}
/**
* Assert that the user is not authenticated.
*
* @param string|null $guard
* @return $this
*/
public function assertGuest($guard = null)
{
$currentUrl = $this->driver->getCurrentURL();
PHPUnit::assertEmpty(
$this->currentUserInfo($guard), 'The user is unexpectedly authenticated.'
);
return $this->visit($currentUrl);
}
/**
* Assert that the user is authenticated as the given user.
*
* @param mixed $user
* @param string|null $guard
* @return $this
*/
public function assertAuthenticatedAs($user, $guard = null)
{
$currentUrl = $this->driver->getCurrentURL();
$expected = [
'id' => $user->getAuthIdentifier(),
'className' => get_class($user),
];
PHPUnit::assertSame(
$expected, $this->currentUserInfo($guard),
'The currently authenticated user is not who was expected.'
);
return $this->visit($currentUrl);
}
/**
* Determine if route() should use an absolute path.
*
* @return bool
*/
private function shouldUseAbsoluteRouteForAuthentication()
{
return config('dusk.domain') !== null;
}
}
@@ -0,0 +1,109 @@
<?php
namespace Laravel\Dusk\Concerns;
use DateTimeInterface;
use Facebook\WebDriver\Exception\NoSuchCookieException;
use Illuminate\Cookie\CookieValuePrefix;
use Illuminate\Support\Facades\Crypt;
trait InteractsWithCookies
{
/**
* Get or set an encrypted cookie's value.
*
* @param string $name
* @param string|null $value
* @param int|DateTimeInterface|null $expiry
* @param array $options
* @return $this|string|null
*/
public function cookie($name, $value = null, $expiry = null, array $options = [])
{
if (! is_null($value)) {
return $this->addCookie($name, $value, $expiry, $options);
}
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 an unencrypted cookie's value.
*
* @param string $name
* @param string|null $value
* @param int|DateTimeInterface|null $expiry
* @param array $options
* @return $this|string|null
*/
public function plainCookie($name, $value = null, $expiry = null, array $options = [])
{
if (! is_null($value)) {
return $this->addCookie($name, $value, $expiry, $options, false);
}
try {
$cookie = $this->driver->manage()->getCookieNamed($name);
} catch (NoSuchCookieException $e) {
$cookie = null;
}
if ($cookie) {
return rawurldecode($cookie['value']);
}
}
/**
* Add the given cookie.
*
* @param string $name
* @param string $value
* @param int|DateTimeInterface|null $expiry
* @param array $options
* @param bool $encrypt
* @return $this
*/
public function addCookie($name, $value, $expiry = null, array $options = [], $encrypt = true)
{
if ($encrypt) {
$prefix = CookieValuePrefix::create($name, Crypt::getKey());
$value = encrypt($prefix.$value, $serialize = false);
}
if ($expiry instanceof DateTimeInterface) {
$expiry = $expiry->getTimestamp();
}
$this->driver->manage()->addCookie(
array_merge($options, compact('expiry', 'name', 'value'))
);
return $this;
}
/**
* Delete the given cookie.
*
* @param string $name
* @return $this
*/
public function deleteCookie($name)
{
$this->driver->manage()->deleteCookieNamed($name);
return $this;
}
}
@@ -0,0 +1,455 @@
<?php
namespace Laravel\Dusk\Concerns;
use Facebook\WebDriver\Interactions\WebDriverActions;
use Facebook\WebDriver\Remote\LocalFileDetector;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverSelect;
use Illuminate\Support\Arr;
trait InteractsWithElements
{
use InteractsWithKeyboard;
/**
* Get all of the elements matching the given selector.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
*/
public function elements($selector)
{
return $this->resolver->all($selector);
}
/**
* Get the element matching the given selector.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
public function element($selector)
{
return $this->resolver->find($selector);
}
/**
* Click the link with the given text.
*
* @param string $link
* @param string $element
* @return $this
*/
public function clickLink($link, $element = 'a')
{
$this->ensurejQueryIsAvailable();
$selector = addslashes(trim($this->resolver->format("{$element}")));
$link = str_replace("'", "\\\\'", $link);
$this->driver->executeScript("jQuery.find(`{$selector}:contains('{$link}'):visible`)[0].click();");
return $this;
}
/**
* Directly get or set the value attribute of an input field.
*
* @param string $selector
* @param string|null $value
* @return $this
*/
public function value($selector, $value = null)
{
if (is_null($value)) {
return $this->resolver->findOrFail($selector)->getAttribute('value');
}
$selector = $this->resolver->format($selector);
$this->driver->executeScript(
'document.querySelector('.json_encode($selector).').value = '.json_encode($value).';'
);
return $this;
}
/**
* Get the text of the element matching the given selector.
*
* @param string $selector
* @return string
*/
public function text($selector)
{
return $this->resolver->findOrFail($selector)->getText();
}
/**
* Get the given attribute from the element matching the given selector.
*
* @param string $selector
* @param string $attribute
* @return string
*/
public function attribute($selector, $attribute)
{
return $this->resolver->findOrFail($selector)->getAttribute($attribute);
}
/**
* Send the given keys to the element matching the given selector.
*
* @param string $selector
* @param mixed $keys
* @return $this
*/
public function keys($selector, ...$keys)
{
$this->resolver->findOrFail($selector)->sendKeys($this->parseKeys($keys));
return $this;
}
/**
* Type the given value in the given field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function type($field, $value)
{
$this->resolver->resolveForTyping($field)->clear()->sendKeys($value);
return $this;
}
/**
* Type the given value in the given field slowly.
*
* @param string $field
* @param string $value
* @param int $pause
* @return $this
*/
public function typeSlowly($field, $value, $pause = 100)
{
$this->clear($field)->appendSlowly($field, $value, $pause);
return $this;
}
/**
* Type the given value in the given field without clearing it.
*
* @param string $field
* @param string $value
* @return $this
*/
public function append($field, $value)
{
$this->resolver->resolveForTyping($field)->sendKeys($value);
return $this;
}
/**
* Type the given value in the given field slowly without clearing it.
*
* @param string $field
* @param string $value
* @param int $pause
* @return $this
*/
public function appendSlowly($field, $value, $pause = 100)
{
$characters = preg_split('//u', $value, -1, PREG_SPLIT_NO_EMPTY);
if (is_array($characters)) {
foreach ($characters as $character) {
$this->append($field, $character)->pause($pause);
}
}
return $this;
}
/**
* Clear the given field.
*
* @param string $field
* @return $this
*/
public function clear($field)
{
$this->resolver->resolveForTyping($field)->clear();
return $this;
}
/**
* Select the given value or random value of a drop-down field.
*
* @param string $field
* @param string|array|null $value
* @return $this
*/
public function select($field, $value = null)
{
$element = $this->resolver->resolveForSelection($field);
$options = $element->findElements(WebDriverBy::cssSelector('option:not([disabled])'));
$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 {
$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 (in_array((string) $option->getAttribute('value'), $value)) {
$option->click();
if (! $isMultiple) {
break;
}
}
}
}
return $this;
}
/**
* Select the given value of a radio button field.
*
* @param string $field
* @param string $value
* @return $this
*/
public function radio($field, $value)
{
$this->resolver->resolveForRadioSelection($field, $value)->click();
return $this;
}
/**
* Check the given checkbox.
*
* @param string $field
* @param string|null $value
* @return $this
*/
public function check($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
if (! $element->isSelected()) {
$element->click();
}
return $this;
}
/**
* Uncheck the given checkbox.
*
* @param string $field
* @param string|null $value
* @return $this
*/
public function uncheck($field, $value = null)
{
$element = $this->resolver->resolveForChecking($field, $value);
if ($element->isSelected()) {
$element->click();
}
return $this;
}
/**
* Attach the given file to the field.
*
* @param string $field
* @param string $path
* @return $this
*/
public function attach($field, $path)
{
$element = $this->resolver->resolveForAttachment($field);
$element->setFileDetector(new LocalFileDetector)->sendKeys($path);
return $this;
}
/**
* Press the button with the given text or name.
*
* @param string $button
* @return $this
*/
public function press($button)
{
$this->resolver->resolveForButtonPress($button)->click();
return $this;
}
/**
* Press the button with the given text or name.
*
* @param string $button
* @param int $seconds
* @return $this
*/
public function pressAndWaitFor($button, $seconds = 5)
{
$element = $this->resolver->resolveForButtonPress($button);
$element->click();
return $this->waitUsing($seconds, 100, function () use ($element) {
return $element->isEnabled();
});
}
/**
* Drag an element to another element using selectors.
*
* @param string $from
* @param string $to
* @return $this
*/
public function drag($from, $to)
{
(new WebDriverActions($this->driver))->dragAndDrop(
$this->resolver->findOrFail($from), $this->resolver->findOrFail($to)
)->perform();
return $this;
}
/**
* Drag an element up.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragUp($selector, $offset)
{
return $this->dragOffset($selector, 0, -$offset);
}
/**
* Drag an element down.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragDown($selector, $offset)
{
return $this->dragOffset($selector, 0, $offset);
}
/**
* Drag an element to the left.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragLeft($selector, $offset)
{
return $this->dragOffset($selector, -$offset, 0);
}
/**
* Drag an element to the right.
*
* @param string $selector
* @param int $offset
* @return $this
*/
public function dragRight($selector, $offset)
{
return $this->dragOffset($selector, $offset, 0);
}
/**
* Drag an element by the given offset.
*
* @param string $selector
* @param int $x
* @param int $y
* @return $this
*/
public function dragOffset($selector, $x = 0, $y = 0)
{
(new WebDriverActions($this->driver))->dragAndDropBy(
$this->resolver->findOrFail($selector), $x, $y
)->perform();
return $this;
}
/**
* Accept a JavaScript dialog.
*
* @return $this
*/
public function acceptDialog()
{
$this->driver->switchTo()->alert()->accept();
return $this;
}
/**
* Type the given value in an open JavaScript prompt dialog.
*
* @param string $value
* @return $this
*/
public function typeInDialog($value)
{
$this->driver->switchTo()->alert()->sendKeys($value);
return $this;
}
/**
* Dismiss a JavaScript dialog.
*
* @return $this
*/
public function dismissDialog()
{
$this->driver->switchTo()->alert()->dismiss();
return $this;
}
}
@@ -0,0 +1,19 @@
<?php
namespace Laravel\Dusk\Concerns;
trait InteractsWithJavascript
{
/**
* Execute JavaScript within the browser.
*
* @param string|array $scripts
* @return array
*/
public function script($scripts)
{
return collect((array) $scripts)->map(function ($script) {
return $this->driver->executeScript($script);
})->all();
}
}
@@ -0,0 +1,42 @@
<?php
namespace Laravel\Dusk\Concerns;
use Facebook\WebDriver\WebDriverKeys;
use Illuminate\Support\Str;
use Laravel\Dusk\Keyboard;
trait InteractsWithKeyboard
{
/**
* Execute the given callback while interacting with the keyboard.
*
* @param callable(\Laravel\Dusk\Keyboard):void $callback
* @return $this
*/
public function withKeyboard(callable $callback)
{
return tap($this, fn () => $callback(new Keyboard($this)));
}
/**
* Parse the keys before sending to the keyboard.
*
* @param array $keys
* @return array
*/
protected function parseKeys($keys)
{
return collect($keys)->map(function ($key) {
if (is_string($key) && Str::startsWith($key, '{') && Str::endsWith($key, '}')) {
$key = constant(WebDriverKeys::class.'::'.strtoupper(trim($key, '{}')));
}
if (is_array($key) && Str::startsWith($key[0], '{')) {
$key[0] = constant(WebDriverKeys::class.'::'.strtoupper(trim($key[0], '{}')));
}
return $key;
})->all();
}
}
+187
View File
@@ -0,0 +1,187 @@
<?php
namespace Laravel\Dusk\Concerns;
use Facebook\WebDriver\Exception\ElementClickInterceptedException;
use Facebook\WebDriver\Exception\NoSuchElementException;
use Facebook\WebDriver\Interactions\WebDriverActions;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\WebDriverKeys;
use Laravel\Dusk\Keyboard;
use Laravel\Dusk\OperatingSystem;
trait InteractsWithMouse
{
/**
* Move the mouse by offset X and Y.
*
* @param int $xOffset
* @param int $yOffset
* @return $this
*/
public function moveMouse($xOffset, $yOffset)
{
(new WebDriverActions($this->driver))->moveByOffset(
$xOffset, $yOffset
)->perform();
return $this;
}
/**
* Move the mouse over the given selector.
*
* @param string $selector
* @return $this
*/
public function mouseover($selector)
{
$element = $this->resolver->findOrFail($selector);
$this->driver->getMouse()->mouseMove($element->getCoordinates());
return $this;
}
/**
* Click the element at the given selector.
*
* @param string|null $selector
* @return $this
*/
public function click($selector = null)
{
if (is_null($selector)) {
(new WebDriverActions($this->driver))->click()->perform();
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 $expression
* @return $this
*/
public function clickAtXPath($expression)
{
$this->driver
->findElement(WebDriverBy::xpath($expression))
->click();
return $this;
}
/**
* Perform a mouse click and hold the mouse button down at the given selector.
*
* @param string|null $selector
* @return $this
*/
public function clickAndHold($selector = null)
{
if (is_null($selector)) {
(new WebDriverActions($this->driver))->clickAndHold()->perform();
} else {
(new WebDriverActions($this->driver))->clickAndHold(
$this->resolver->findOrFail($selector)
)->perform();
}
return $this;
}
/**
* Double click the element at the given selector.
*
* @param string|null $selector
* @return $this
*/
public function doubleClick($selector = null)
{
if (is_null($selector)) {
(new WebDriverActions($this->driver))->doubleClick()->perform();
} else {
(new WebDriverActions($this->driver))->doubleClick(
$this->resolver->findOrFail($selector)
)->perform();
}
return $this;
}
/**
* Right click the element at the given selector.
*
* @param string|null $selector
* @return $this
*/
public function rightClick($selector = null)
{
if (is_null($selector)) {
(new WebDriverActions($this->driver))->contextClick()->perform();
} else {
(new WebDriverActions($this->driver))->contextClick(
$this->resolver->findOrFail($selector)
)->perform();
}
return $this;
}
/**
* Control click the element at the given selector.
*
* @param string|null $selector
* @return $this
*/
public function controlClick($selector = null)
{
return $this->withKeyboard(function (Keyboard $keyboard) use ($selector) {
$key = OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL;
$keyboard->press($key);
$this->click($selector);
$keyboard->release($key);
});
}
/**
* Release the currently clicked mouse button.
*
* @return $this
*/
public function releaseMouse()
{
(new WebDriverActions($this->driver))->release()->perform();
return $this;
}
}
File diff suppressed because it is too large Load Diff
+390
View File
@@ -0,0 +1,390 @@
<?php
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 (without the query string) matches the given string.
*
* @param string $url
* @return $this
*/
public function assertUrlIs($url)
{
$pattern = str_replace('\*', '.*', preg_quote($url, '/'));
$segments = parse_url($this->driver->getCurrentURL());
$currentUrl = sprintf(
'%s://%s%s%s',
$segments['scheme'],
$segments['host'],
Arr::get($segments, 'port', '') ? ':'.$segments['port'] : '',
Arr::get($segments, 'path', '')
);
PHPUnit::assertThat(
$currentUrl, new RegularExpression('/^'.$pattern.'$/u'),
"Actual URL [{$this->driver->getCurrentURL()}] does not equal expected URL [{$url}]."
);
return $this;
}
/**
* Assert that the current URL scheme matches the given scheme.
*
* @param string $scheme
* @return $this
*/
public function assertSchemeIs($scheme)
{
$pattern = str_replace('\*', '.*', preg_quote($scheme, '/'));
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_SCHEME) ?? '';
PHPUnit::assertThat(
$actual, new RegularExpression('/^'.$pattern.'$/u'),
"Actual scheme [{$actual}] does not equal expected scheme [{$pattern}]."
);
return $this;
}
/**
* Assert that the current URL scheme does not match the given scheme.
*
* @param string $scheme
* @return $this
*/
public function assertSchemeIsNot($scheme)
{
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_SCHEME) ?? '';
PHPUnit::assertNotEquals(
$scheme, $actual,
"Scheme [{$scheme}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL host matches the given host.
*
* @param string $host
* @return $this
*/
public function assertHostIs($host)
{
$pattern = str_replace('\*', '.*', preg_quote($host, '/'));
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_HOST) ?? '';
PHPUnit::assertThat(
$actual, new RegularExpression('/^'.$pattern.'$/u'),
"Actual host [{$actual}] does not equal expected host [{$pattern}]."
);
return $this;
}
/**
* Assert that the current URL host does not match the given host.
*
* @param string $host
* @return $this
*/
public function assertHostIsNot($host)
{
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_HOST) ?? '';
PHPUnit::assertNotEquals(
$host, $actual,
"Host [{$host}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL port matches the given port.
*
* @param string $port
* @return $this
*/
public function assertPortIs($port)
{
$pattern = str_replace('\*', '.*', preg_quote($port, '/'));
$actual = (string) parse_url($this->driver->getCurrentURL(), PHP_URL_PORT) ?? '';
PHPUnit::assertThat(
$actual, new RegularExpression('/^'.$pattern.'$/u'),
"Actual port [{$actual}] does not equal expected port [{$pattern}]."
);
return $this;
}
/**
* Assert that the current URL port does not match the given port.
*
* @param string $port
* @return $this
*/
public function assertPortIsNot($port)
{
$actual = parse_url($this->driver->getCurrentURL(), PHP_URL_PORT) ?? '';
PHPUnit::assertNotEquals(
$port, $actual,
"Port [{$port}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL path begins with the given path.
*
* @param string $path
* @return $this
*/
public function assertPathBeginsWith($path)
{
$actualPath = parse_url($this->driver->getCurrentURL(), PHP_URL_PATH) ?? '';
PHPUnit::assertStringStartsWith(
$path, $actualPath,
"Actual path [{$actualPath}] does not begin with expected path [{$path}]."
);
return $this;
}
/**
* Assert that the current URL path ends with the given path.
*
* @param string $path
* @return $this
*/
public function assertPathEndsWith($path)
{
$actualPath = parse_url($this->driver->getCurrentURL(), PHP_URL_PATH) ?? '';
PHPUnit::assertStringEndsWith(
$path, $actualPath,
"Actual path [{$actualPath}] does not end with expected path [{$path}]."
);
return $this;
}
/**
* Assert that the current URL path contains the given path.
*
* @param string $path
* @return $this
*/
public function assertPathContains($path)
{
$actualPath = parse_url($this->driver->getCurrentURL(), PHP_URL_PATH) ?? '';
PHPUnit::assertStringContainsString(
$path, $actualPath,
"Actual path [{$actualPath}] does not contain the expected string [{$path}]."
);
return $this;
}
/**
* 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
*/
public function assertPathIsNot($path)
{
$actualPath = parse_url($this->driver->getCurrentURL(), PHP_URL_PATH) ?? '';
PHPUnit::assertNotEquals(
$path, $actualPath,
"Path [{$path}] should not equal the actual value."
);
return $this;
}
/**
* Assert that the current URL matches the given named route's URL.
*
* @param string $route
* @param array $parameters
* @return $this
*/
public function assertRouteIs($route, $parameters = [])
{
return $this->assertPathIs(route($route, $parameters, false));
}
/**
* Assert that the given query string parameter is present and has a given value.
*
* @param string $name
* @param string|null $value
* @return $this
*/
public function assertQueryStringHas($name, $value = null)
{
$output = $this->assertHasQueryStringParameter($name);
if (is_null($value)) {
return $this;
}
$parsedOutputName = is_array($output[$name]) ? implode(',', $output[$name]) : $output[$name];
$parsedValue = is_array($value) ? implode(',', $value) : $value;
PHPUnit::assertEquals(
$value, $output[$name],
"Query string parameter [{$name}] had value [{$parsedOutputName}], but expected [{$parsedValue}]."
);
return $this;
}
/**
* Assert that the given query string parameter is missing.
*
* @param string $name
* @return $this
*/
public function assertQueryStringMissing($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
if (! array_key_exists('query', $parsedUrl)) {
PHPUnit::assertTrue(true);
return $this;
}
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayNotHasKey(
$name, $output,
"Found unexpected query string parameter [{$name}] in [".$this->driver->getCurrentURL().'].'
);
return $this;
}
/**
* Assert that the 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.
*
* @param string $name
* @return array
*/
protected function assertHasQueryStringParameter($name)
{
$parsedUrl = parse_url($this->driver->getCurrentURL());
PHPUnit::assertArrayHasKey(
'query', $parsedUrl,
'Did not see expected query string in ['.$this->driver->getCurrentURL().'].'
);
parse_str($parsedUrl['query'], $output);
PHPUnit::assertArrayHasKey(
$name, $output,
"Did not see expected query string parameter [{$name}] in [".$this->driver->getCurrentURL().'].'
);
return $output;
}
}
+250
View File
@@ -0,0 +1,250 @@
<?php
namespace Laravel\Dusk\Concerns;
use Closure;
use Exception;
use Illuminate\Support\Collection;
use Laravel\Dusk\Browser;
use PHPUnit\Framework\Attributes\AfterClass;
use PHPUnit\Runner\Version;
use ReflectionFunction;
use Throwable;
trait ProvidesBrowser
{
/**
* All of the active browser instances.
*
* @var array
*/
protected static $browsers = [];
/**
* The callbacks that should be run on class tear down.
*
* @var array
*/
protected static $afterClassCallbacks = [];
/**
* Tear down the Dusk test case class.
*
* @return void
*/
#[AfterClass]
public static function tearDownDuskClass()
{
static::closeAll();
foreach (static::$afterClassCallbacks as $callback) {
$callback();
}
}
/**
* Register an "after class" tear down callback.
*
* @param \Closure $callback
* @return void
*/
public static function afterClass(Closure $callback)
{
static::$afterClassCallbacks[] = $callback;
}
/**
* Create a new browser instance.
*
* @param \Closure $callback
* @return \Laravel\Dusk\Browser|void
*
* @throws \Exception
* @throws \Throwable
*/
public function browse(Closure $callback)
{
$browsers = $this->createBrowsersFor($callback);
try {
$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 {
$this->storeConsoleLogsFor($browsers);
static::$browsers = $this->closeAllButPrimary($browsers);
}
}
/**
* Create the browser instances needed for the given callback.
*
* @param \Closure $callback
* @return array
*
* @throws \ReflectionException
*/
protected function createBrowsersFor(Closure $callback)
{
if (count(static::$browsers) === 0) {
static::$browsers = collect([$this->newBrowser($this->createWebDriver())]);
}
$additional = $this->browsersNeededFor($callback) - 1;
for ($i = 0; $i < $additional; $i++) {
static::$browsers->push($this->newBrowser($this->createWebDriver()));
}
return static::$browsers;
}
/**
* Create a new Browser instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @return \Laravel\Dusk\Browser
*/
protected function newBrowser($driver)
{
return new Browser($driver);
}
/**
* Get the number of browsers needed for a given callback.
*
* @param \Closure $callback
* @return int
*
* @throws \ReflectionException
*/
protected function browsersNeededFor(Closure $callback)
{
return (new ReflectionFunction($callback))->getNumberOfParameters();
}
/**
* Capture failure screenshots for each browser.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function captureFailuresFor($browsers)
{
$browsers->each(function ($browser, $key) {
if (property_exists($browser, 'fitOnFailure') && $browser->fitOnFailure) {
$browser->fitContent();
}
$name = $this->getCallerName();
$browser->screenshot('failure-'.$name.'-'.$key);
});
}
/**
* Store the console output for the given browsers.
*
* @param \Illuminate\Support\Collection $browsers
* @return void
*/
protected function storeConsoleLogsFor($browsers)
{
$browsers->each(function ($browser, $key) {
$name = $this->getCallerName();
$browser->storeConsoleLog($name.'-'.$key);
});
}
/**
* 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.
*
* @param \Illuminate\Support\Collection $browsers
* @return \Illuminate\Support\Collection
*/
protected function closeAllButPrimary($browsers)
{
$browsers->slice(1)->each->quit();
return $browsers->take(1);
}
/**
* Close all of the active browsers.
*
* @return void
*/
public static function closeAll()
{
Collection::make(static::$browsers)->each->quit();
static::$browsers = collect();
}
/**
* Create the remote web driver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*
* @throws \Exception
*/
protected function createWebDriver()
{
return retry(5, function () {
return $this->driver();
}, 50);
}
/**
* Get the browser caller name.
*
* @return string
*/
protected function getCallerName()
{
$name = version_compare(Version::id(), '10', '>=')
? $this->name()
: $this->getName(false); // @phpstan-ignore-line
$parts = array_filter([
str_replace('\\', '_', get_class($this)),
$name,
str_replace(['\\', DIRECTORY_SEPARATOR, ' '], ['', '', '_'], $this->dataName()),
], fn ($part) => $part !== '');
return substr(implode('_', $parts), -140);
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
abstract protected function driver();
}
+434
View File
@@ -0,0 +1,434 @@
<?php
namespace Laravel\Dusk\Concerns;
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;
use Illuminate\Support\Str;
trait WaitsForElements
{
/**
* Execute the given callback in a scoped browser once the selector is available.
*
* @param string $selector
* @param \Closure $callback
* @param int|null $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function whenAvailable($selector, Closure $callback, $seconds = null)
{
return $this->waitFor($selector, $seconds)->with($selector, $callback);
}
/**
* Wait for the given selector to become visible.
*
* @param string $selector
* @param int|null $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitFor($selector, $seconds = null)
{
$message = $this->formatTimeOutMessage('Waited %s seconds for selector', $selector);
return $this->waitUsing($seconds, 100, function () use ($selector) {
return $this->resolver->findOrFail($selector)->isDisplayed();
}, $message);
}
/**
* Wait for the given selector to be removed.
*
* @param string $selector
* @param int|null $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitUntilMissing($selector, $seconds = null)
{
$message = $this->formatTimeOutMessage('Waited %s seconds for removal of selector', $selector);
return $this->waitUsing($seconds, 100, function () use ($selector) {
try {
$missing = ! $this->resolver->findOrFail($selector)->isDisplayed();
} catch (NoSuchElementException $e) {
$missing = true;
}
return $missing;
}, $message);
}
/**
* Wait for the given text to be removed.
*
* @param string $text
* @param int|null $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitUntilMissingText($text, $seconds = null)
{
$text = Arr::wrap($text);
$message = $this->formatTimeOutMessage('Waited %s seconds for removal of text', implode("', '", $text));
return $this->waitUsing($seconds, 100, function () use ($text) {
return ! Str::contains($this->resolver->findOrFail('')->getText(), $text);
}, $message);
}
/**
* Wait for the given text to become visible.
*
* @param array|string $text
* @param int|null $seconds
* @param bool $ignoreCase
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitForText($text, $seconds = null, $ignoreCase = false)
{
$text = Arr::wrap($text);
$message = $this->formatTimeOutMessage('Waited %s seconds for text', implode("', '", $text));
return $this->waitUsing($seconds, 100, function () use ($text, $ignoreCase) {
return Str::contains($this->resolver->findOrFail('')->getText(), $text, $ignoreCase);
}, $message);
}
/**
* Wait for the given text to become visible inside the given selector.
*
* @param string $selector
* @param array|string $text
* @param int|null $seconds
* @param bool $ignoreCase
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitForTextIn($selector, $text, $seconds = null, $ignoreCase = false)
{
$message = 'Waited %s seconds for text "'.$this->escapePercentCharacters($text).'" in selector '.$selector;
return $this->waitUsing($seconds, 100, function () use ($selector, $text, $ignoreCase) {
return $this->assertSeeIn($selector, $text, $ignoreCase);
}, $message);
}
/**
* Wait for the given link to become visible.
*
* @param string $link
* @param int|null $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitForLink($link, $seconds = null)
{
$message = $this->formatTimeOutMessage('Waited %s seconds for link', $link);
return $this->waitUsing($seconds, 100, function () use ($link) {
return $this->seeLink($link);
}, $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|null $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitForLocation($path, $seconds = null)
{
$message = $this->formatTimeOutMessage('Waited %s seconds for location', $path);
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);
}
/**
* Wait for the given location using a named route.
*
* @param string $route
* @param array $parameters
* @param int|null $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitForRoute($route, $parameters = [], $seconds = null)
{
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|null $seconds
* @param string|null $message
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitUntil($script, $seconds = null, $message = null)
{
if (! Str::startsWith($script, 'return ')) {
$script = 'return '.$script;
}
if (! Str::endsWith($script, ';')) {
$script = $script.';';
}
return $this->waitUsing($seconds, 100, function () use ($script) {
return $this->driver->executeScript($script);
}, $message);
}
/**
* Wait until the Vue component's attribute at the given key has the given value.
*
* @param string $key
* @param string $value
* @param string|null $componentSelector
* @param int|null $seconds
* @return $this
*/
public function waitUntilVue($key, $value, $componentSelector = null, $seconds = null)
{
$this->waitUsing($seconds, 100, function () use ($key, $value, $componentSelector) {
return $value == $this->vueAttribute($componentSelector, $key);
});
return $this;
}
/**
* Wait until the Vue component's attribute at the given key does not have the given value.
*
* @param string $key
* @param string $value
* @param string|null $componentSelector
* @param int|null $seconds
* @return $this
*/
public function waitUntilVueIsNot($key, $value, $componentSelector = null, $seconds = null)
{
$this->waitUsing($seconds, 100, function () use ($key, $value, $componentSelector) {
return $value != $this->vueAttribute($componentSelector, $key);
});
return $this;
}
/**
* Wait for a JavaScript dialog to open.
*
* @param int|null $seconds
* @return $this
*/
public function waitForDialog($seconds = null)
{
$seconds = is_null($seconds) ? static::$waitSeconds : $seconds;
$this->driver->wait($seconds, 100)->until(
WebDriverExpectedCondition::alertIsPresent(), "Waited {$seconds} seconds for dialog."
);
return $this;
}
/**
* Wait for the current page to reload.
*
* @param \Closure|null $callback
* @param int|null $seconds
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitForReload($callback = null, $seconds = null)
{
$token = Str::random();
$this->driver->executeScript("window['{$token}'] = {};");
if ($callback) {
$callback($this);
}
return $this->waitUsing($seconds, 100, function () use ($token) {
return $this->driver->executeScript("return typeof window['{$token}'] === 'undefined';");
}, 'Waited %s seconds for page reload.');
}
/**
* Click an element and wait for the page to reload.
*
* @param string|null $selector
* @param int|null $seconds
* @return $this
*/
public function clickAndWaitForReload($selector = null, $seconds = null)
{
return $this->waitForReload(function ($browser) use ($selector) {
$browser->click($selector);
}, $seconds);
}
/**
* 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|null $seconds
* @param int $interval
* @param \Closure $callback
* @param string|null $message
* @return $this
*
* @throws \Facebook\WebDriver\Exception\TimeoutException
*/
public function waitUsing($seconds, $interval, Closure $callback, $message = null)
{
$seconds = is_null($seconds) ? static::$waitSeconds : $seconds;
$this->pause($interval);
$this->driver->wait($seconds, $interval)->until(
function ($driver) use ($callback) {
try {
return $callback();
} catch (Exception $e) {
return false;
}
},
$message ? sprintf($message, $seconds) : "Waited {$seconds} seconds for callback."
);
return $this;
}
/**
* Prepare custom TimeoutException message for sprintf().
*
* @param string $message
* @param string $expected
* @return string
*/
protected function formatTimeOutMessage($message, $expected)
{
return $message.' ['.$this->escapePercentCharacters($expected).'].';
}
/**
* Escape percent characters in preparation for sending the given message to "sprintf".
*
* @param string $message
* @return string
*/
protected function escapePercentCharacters($message)
{
return str_replace('%', '%%', $message);
}
}
+346
View File
@@ -0,0 +1,346 @@
<?php
namespace Laravel\Dusk\Console;
use Exception;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Utils;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use Laravel\Dusk\OperatingSystem;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Process\Process;
use ZipArchive;
/**
* @copyright Originally created by Jonas Staudenmeir: https://github.com/staudenmeir/dusk-updater
*/
#[AsCommand(name: 'dusk:chrome-driver')]
class ChromeDriverCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk:chrome-driver {version?}
{--all : Install a ChromeDriver binary for every OS}
{--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}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Install the ChromeDriver binary';
/**
* The legacy versions for ChromeDriver.
*
* @var array
*/
protected $legacyVersions = [
43 => '2.20',
44 => '2.20',
45 => '2.20',
46 => '2.21',
47 => '2.21',
48 => '2.21',
49 => '2.22',
50 => '2.22',
51 => '2.23',
52 => '2.24',
53 => '2.26',
54 => '2.27',
55 => '2.28',
56 => '2.29',
57 => '2.29',
58 => '2.31',
59 => '2.32',
60 => '2.33',
61 => '2.34',
62 => '2.35',
63 => '2.36',
64 => '2.37',
65 => '2.38',
66 => '2.40',
67 => '2.41',
68 => '2.42',
69 => '2.44',
];
/**
* Path to the bin directory.
*
* @var string
*/
protected $directory = __DIR__.'/../../bin/';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$version = $this->version();
$all = $this->option('all');
$currentOS = OperatingSystem::id();
foreach (OperatingSystem::all() as $os) {
if ($all || ($os === $currentOS)) {
$archive = $this->download($version, $os);
$binary = $this->extract($archive);
$this->rename($binary, $os);
}
}
$message = 'ChromeDriver %s successfully installed for version %s.';
$this->components->info(sprintf($message, $all ? 'binaries' : 'binary', $version));
}
/**
* Get the desired ChromeDriver version.
*
* @return string
*/
protected function version()
{
$version = $this->argument('version');
if ($this->option('detect')) {
$version = $this->detectChromeVersion(OperatingSystem::id());
}
if (! $version) {
return $this->latestVersion();
}
if (! ctype_digit($version)) {
return $version;
}
$version = (int) $version;
if ($version < 70) {
return $this->legacyVersions[$version];
} elseif ($version < 115) {
return $this->fetchChromeVersionFromUrl($version);
}
$milestones = $this->resolveChromeVersionsPerMilestone();
return $milestones['milestones'][$version]['version']
?? throw new Exception('Could not determine the ChromeDriver version.');
}
/**
* Get the latest stable ChromeDriver version.
*
* @return string
*/
protected function latestVersion()
{
$versions = json_decode($this->getUrl('https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json'), true);
return $versions['channels']['Stable']['version']
?? throw new Exception('Could not get the latest ChromeDriver version.');
}
/**
* Detect the installed Chrome / Chromium major version.
*
* @param string $os
* @return int|bool
*/
protected function detectChromeVersion($os)
{
foreach (OperatingSystem::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->components->error('Chrome version could not be detected.');
return false;
}
/**
* Download the ChromeDriver archive.
*
* @param string $version
* @param string $os
* @return string
*/
protected function download($version, $os)
{
$url = $this->resolveChromeDriverDownloadUrl($version, $os);
$resource = Utils::tryFopen($archive = $this->directory.'chromedriver.zip', 'w');
$client = new Client();
$response = $client->get($url, array_merge([
'sink' => $resource,
'verify' => $this->option('ssl-no-verify') === false,
], array_filter([
'proxy' => $this->option('proxy'),
])));
if ($response->getStatusCode() < 200 || $response->getStatusCode() > 299) {
throw new Exception("Unable to download ChromeDriver from [{$url}].");
}
return $archive;
}
/**
* Extract the ChromeDriver binary from the archive and delete the archive.
*
* @param string $archive
* @return string
*
* @throws \Exception
*/
protected function extract($archive)
{
$zip = new ZipArchive;
$zip->open($archive);
$binary = null;
for ($fileIndex = 0; $fileIndex < $zip->numFiles; $fileIndex++) {
$filename = $zip->getNameIndex($fileIndex);
if (Str::startsWith(basename($filename), 'chromedriver')) {
$binary = $filename;
$zip->extractTo($this->directory, $binary);
break;
}
}
$zip->close();
unlink($archive);
if (! $binary) {
throw new Exception('Could not extract the ChromeDriver binary.');
}
return $binary;
}
/**
* Rename the ChromeDriver binary and make it executable.
*
* @param string $binary
* @param string $os
* @return void
*/
protected function rename($binary, $os)
{
$binary = str_replace(DIRECTORY_SEPARATOR, '/', $binary);
$newName = Str::contains($binary, '/')
? Str::after(str_replace('chromedriver', 'chromedriver-'.$os, $binary), '/')
: str_replace('chromedriver', 'chromedriver-'.$os, $binary);
rename($this->directory.$binary, $this->directory.$newName);
chmod($this->directory.$newName, 0755);
}
/**
* Get the Chrome version from URL.
*
* @return string
*/
protected function fetchChromeVersionFromUrl(int $version)
{
return trim((string) $this->getUrl(
sprintf('https://chromedriver.storage.googleapis.com/LATEST_RELEASE_%d', $version)
));
}
/**
* Get the Chrome versions per milestone.
*
* @return array
*/
protected function resolveChromeVersionsPerMilestone()
{
return json_decode(
$this->getUrl('https://googlechromelabs.github.io/chrome-for-testing/latest-versions-per-milestone-with-downloads.json'), true
);
}
/**
* Resolve the download URL.
*
* @return string
*
* @throws \Exception
*/
protected function resolveChromeDriverDownloadUrl(string $version, string $os)
{
$slug = OperatingSystem::chromeDriverSlug($os, $version);
if (version_compare($version, '115.0', '<')) {
return sprintf('https://chromedriver.storage.googleapis.com/%s/chromedriver_%s.zip', $version, $slug);
}
$milestone = (int) $version;
$versions = $this->resolveChromeVersionsPerMilestone();
/** @var array<string, mixed> $chromedrivers */
$chromedrivers = $versions['milestones'][$milestone]['downloads']['chromedriver']
?? throw new Exception('Could not get the ChromeDriver version.');
return collect($chromedrivers)->firstWhere('platform', $slug)['url']
?? throw new Exception('Could not get the ChromeDriver version.');
}
/**
* Get the contents of a URL using the 'proxy' and 'ssl-no-verify' command options.
*
* @return string
*
* @throws \Exception
*/
protected function getUrl(string $url)
{
$client = new Client();
$response = $client->get($url, array_merge([
'verify' => $this->option('ssl-no-verify') === false,
], array_filter([
'proxy' => $this->option('proxy'),
])));
if ($response->getStatusCode() < 200 || $response->getStatusCode() > 299) {
throw new Exception("Unable to fetch contents from [{$url}].");
}
return (string) $response->getBody();
}
}
+76
View File
@@ -0,0 +1,76 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'dusk:component')]
class ComponentCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $signature = 'dusk:component {name : The name of the class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Dusk component class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Component';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/component.stub';
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
$name = Str::replaceFirst($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Browser\Components';
}
/**
* Get the root namespace for the class.
*
* @return string
*/
protected function rootNamespace()
{
return 'Tests';
}
}
@@ -0,0 +1,16 @@
<?php
namespace Laravel\Dusk\Console\Concerns;
trait InteractsWithTestingFrameworks
{
/**
* Determine if Pest is being used by the application.
*
* @return bool
*/
protected function usingPest()
{
return function_exists('\Pest\\version') && file_exists(base_path('tests').'/Pest.php');
}
}
+390
View File
@@ -0,0 +1,390 @@
<?php
namespace Laravel\Dusk\Console;
use Dotenv\Dotenv;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use NunoMaduro\Collision\Adapters\Phpunit\Subscribers\EnsurePrinterIsRegisteredSubscriber;
use PHPUnit\Runner\Version;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Process;
#[AsCommand(name: 'dusk')]
class DuskCommand extends Command
{
use Concerns\InteractsWithTestingFrameworks;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk
{--browse : Open a browser instead of using headless mode}
{--without-tty : Disable output to TTY}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Run the Dusk tests for the application';
/**
* Indicates if the project has its own PHPUnit configuration.
*
* @var bool
*/
protected $hasPhpUnitConfiguration = false;
/**
* 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();
$options = collect($_SERVER['argv'])
->slice(2)
->diff([
'--browse', '--without-tty',
'--quiet', '-q',
'--verbose', '-v', '-vv', '-vvv',
'--no-interaction', '-n',
])
->values()
->all();
return $this->withDuskEnvironment(function () use ($options) {
$process = (new Process(array_merge(
$this->binary(), $this->phpunitArguments($options)
), null, $this->env()))->setTimeout(null);
try {
$process->setTty(! $this->option('without-tty'));
} catch (RuntimeException $e) {
$this->output->writeln('Warning: '.$e->getMessage());
}
try {
return $process->run(function ($type, $line) {
$this->output->write($line);
});
} catch (ProcessSignaledException $e) {
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
throw $e;
}
}
});
}
/**
* Get the PHP binary to execute.
*
* @return array
*/
protected function binary()
{
$binaryPath = 'vendor/phpunit/phpunit/phpunit';
if ($this->usingPest()) {
$binaryPath = 'vendor/pestphp/pest/bin/pest';
}
if ('phpdbg' === PHP_SAPI) {
return [PHP_BINARY, '-qrr', $binaryPath];
}
return [PHP_BINARY, $binaryPath];
}
/**
* Get the array of arguments for running PHPUnit.
*
* @param array $options
* @return array
*/
protected function phpunitArguments($options)
{
if ($this->shouldUseCollisionPrinter()) {
$options[] = '--no-output';
}
$options = array_values(array_filter($options, function ($option) {
return ! Str::startsWith($option, ['--env=', '--pest', '--ansi', '--no-ansi']);
}));
if (! file_exists($file = base_path('phpunit.dusk.xml'))) {
$file = base_path('phpunit.dusk.xml.dist');
}
if (version_compare(Version::id(), '10.0', '>=')) {
if ($this->option('ansi')) {
$options[] = '--colors=always';
}
if ($this->option('no-ansi')) {
$options[] = '--colors=never';
}
}
return array_merge(['-c', $file], $options);
}
/**
* Get the PHP binary environment variables.
*
* @return array|null
*/
protected function env()
{
$variables = [];
if ($this->option('browse') && ! isset($_ENV['CI']) && ! isset($_SERVER['CI'])) {
$variables['DUSK_HEADLESS_DISABLED'] = true;
}
if ($this->shouldUseCollisionPrinter()) {
$variables['COLLISION_PRINTER'] = 'DefaultPrinter';
}
return $variables;
}
/**
* Determine if Collision's printer should be used.
*
* @return bool
*/
protected function shouldUseCollisionPrinter()
{
return ! $this->usingPest()
&& class_exists(EnsurePrinterIsRegisteredSubscriber::class)
&& version_compare(Version::id(), '10.0', '>=');
}
/**
* Purge the failure screenshots.
*
* @return void
*/
protected function purgeScreenshots()
{
$this->purgeDebuggingFiles(
base_path('tests/Browser/screenshots'), 'failure-*'
);
}
/**
* Purge the console logs.
*
* @return void
*/
protected function purgeConsoleLogs()
{
$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($patterns);
foreach ($files as $file) {
@unlink($file->getRealPath());
}
}
/**
* Run the given callback with the Dusk configuration files.
*
* @param \Closure $callback
* @return mixed
*/
protected function withDuskEnvironment($callback)
{
$this->setupDuskEnvironment();
try {
return $callback();
} finally {
$this->teardownDuskEnvironment();
}
}
/**
* Setup the Dusk environment.
*
* @return void
*/
protected function setupDuskEnvironment()
{
if (file_exists(base_path($this->duskFile()))) {
if (file_exists(base_path('.env')) &&
file_get_contents(base_path('.env')) !== file_get_contents(base_path($this->duskFile()))) {
$this->backupEnvironment();
}
$this->refreshEnvironment();
}
$this->writeConfiguration();
$this->setupSignalHandler();
}
/**
* Backup the current environment file.
*
* @return void
*/
protected function backupEnvironment()
{
copy(base_path('.env'), base_path('.env.backup'));
copy(base_path($this->duskFile()), base_path('.env'));
}
/**
* Refresh the current environment variables.
*
* @return void
*/
protected function refreshEnvironment()
{
Dotenv::createMutable(base_path())->load();
}
/**
* Write the Dusk PHPUnit configuration.
*
* @return void
*/
protected function writeConfiguration()
{
if (! file_exists($file = base_path('phpunit.dusk.xml')) &&
! file_exists(base_path('phpunit.dusk.xml.dist'))) {
copy(realpath(__DIR__.'/../../stubs/phpunit.xml'), $file);
return;
}
$this->hasPhpUnitConfiguration = true;
}
/**
* Setup the SIGINT signal handler for CTRL+C exits.
*
* @return void
*/
protected function setupSignalHandler()
{
if (extension_loaded('pcntl')) {
pcntl_async_signals(true);
pcntl_signal(SIGINT, function () {
$this->teardownDuskEnvironment();
});
}
}
/**
* Restore the original environment.
*
* @return void
*/
protected function teardownDuskEnvironment()
{
$this->removeConfiguration();
if (file_exists(base_path($this->duskFile())) && file_exists(base_path('.env.backup'))) {
$this->restoreEnvironment();
}
}
/**
* Remove the Dusk PHPUnit configuration.
*
* @return void
*/
protected function removeConfiguration()
{
if (! $this->hasPhpUnitConfiguration && file_exists($file = base_path('phpunit.dusk.xml'))) {
unlink($file);
}
}
/**
* Restore the backed-up environment file.
*
* @return void
*/
protected function restoreEnvironment()
{
copy(base_path('.env.backup'), base_path('.env'));
unlink(base_path('.env.backup'));
}
/**
* Get the name of the Dusk file for the environment.
*
* @return string
*/
protected function duskFile()
{
if (file_exists(base_path($file = '.env.dusk.'.$this->laravel->environment()))) {
return $file;
}
return '.env.dusk';
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Laravel\Dusk\Console;
class DuskFailsCommand extends DuskCommand
{
/**
* The name and signature of the console command.
*
* @var string
*/
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.
*
* @var string
*/
protected $description = 'Run the failing Dusk tests from the last run and stop on failure';
/**
* Get the array of arguments for running PHPUnit.
*
* @param array $options
* @return array
*/
protected function phpunitArguments($options)
{
return array_unique(array_merge(parent::phpunitArguments($options), [
'--cache-result', '--order-by=defects', '--stop-on-failure',
]));
}
}
+169
View File
@@ -0,0 +1,169 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'dusk:install')]
class InstallCommand extends Command
{
use Concerns\InteractsWithTestingFrameworks;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'dusk:install
{--proxy= : The proxy to download the binary through (example: "tcp://127.0.0.1:9000")}
{--ssl-no-verify : Bypass SSL certificate verification when installing through a proxy}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Install Dusk into the application';
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
if (! is_dir(base_path('tests/Browser/Pages'))) {
mkdir(base_path('tests/Browser/Pages'), 0755, true);
}
if (! is_dir(base_path('tests/Browser/Components'))) {
mkdir(base_path('tests/Browser/Components'), 0755, true);
}
if (! is_dir(base_path('tests/Browser/screenshots'))) {
$this->createScreenshotsDirectory();
}
if (! is_dir(base_path('tests/Browser/console'))) {
$this->createConsoleDirectory();
}
if (! is_dir(base_path('tests/Browser/source'))) {
$this->createSourceDirectory();
}
$stubs = [
'HomePage.stub' => base_path('tests/Browser/Pages/HomePage.php'),
'DuskTestCase.stub' => base_path('tests/DuskTestCase.php'),
'Page.stub' => base_path('tests/Browser/Pages/Page.php'),
];
if ($this->usingPest()) {
$stubs['ExampleTest.pest.stub'] = base_path('tests/Browser/ExampleTest.php');
$contents = file_get_contents(base_path('tests/Pest.php'));
if (str_contains($contents, 'uses(')) {
$contents = str_replace('<?php', <<<EOT
<?php
uses(
Tests\DuskTestCase::class,
// Illuminate\Foundation\Testing\DatabaseMigrations::class,
)->in('Browser');
EOT, $contents);
} else {
$contents = str_replace('<?php', <<<EOT
<?php
pest()->extend(Tests\DuskTestCase::class)
// ->use(Illuminate\Foundation\Testing\DatabaseMigrations::class)
->in('Browser');
EOT, $contents);
}
file_put_contents(base_path('tests/Pest.php'), $contents);
} else {
$stubs['ExampleTest.stub'] = base_path('tests/Browser/ExampleTest.php');
}
foreach ($stubs as $stub => $file) {
if (! is_file($file)) {
copy(__DIR__.'/../../stubs/'.$stub, $file);
}
}
$baseTestCase = file_get_contents(base_path('tests/DuskTestCase.php'));
if (! trait_exists(\Tests\CreatesApplication::class)) {
file_put_contents(base_path('tests/DuskTestCase.php'), str_replace(<<<'EOT'
{
use CreatesApplication;
EOT, <<<'EOT'
{
EOT,
$baseTestCase,
));
}
$this->components->info('Dusk scaffolding installed successfully.');
$this->components->task('Downloading ChromeDriver binaries...', function () {
$driverCommandArgs = [];
if ($this->option('proxy')) {
$driverCommandArgs['--proxy'] = $this->option('proxy');
}
if ($this->option('ssl-no-verify')) {
$driverCommandArgs['--ssl-no-verify'] = true;
}
$this->call('dusk:chrome-driver', $driverCommandArgs);
});
}
/**
* Create the screenshots directory.
*
* @return void
*/
protected function createScreenshotsDirectory()
{
mkdir(base_path('tests/Browser/screenshots'), 0755, true);
file_put_contents(base_path('tests/Browser/screenshots/.gitignore'), '*
!.gitignore
');
}
/**
* Create the console directory.
*
* @return void
*/
protected function createConsoleDirectory()
{
mkdir(base_path('tests/Browser/console'), 0755, true);
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
');
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'dusk:make')]
class MakeCommand extends GeneratorCommand
{
use Concerns\InteractsWithTestingFrameworks;
/**
* The console command name.
*
* @var string
*/
protected $signature = 'dusk:make {name : The name of the class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Dusk test class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Test';
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return $this->usingPest()
? __DIR__.'/stubs/test.pest.stub'
: __DIR__.'/stubs/test.stub';
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
$name = Str::replaceFirst($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Browser';
}
/**
* Get the root namespace for the class.
*
* @return string
*/
protected function rootNamespace()
{
return 'Tests';
}
}
+111
View File
@@ -0,0 +1,111 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\GeneratorCommand;
use Illuminate\Support\Str;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand(name: 'dusk:page')]
class PageCommand extends GeneratorCommand
{
/**
* The console command name.
*
* @var string
*/
protected $signature = 'dusk:page {name : The name of the class}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new Dusk page class';
/**
* The type of class being generated.
*
* @var string
*/
protected $type = 'Page';
/**
* Build the class with the given name.
*
* @param string $name
* @return string
*/
protected function buildClass($name)
{
$result = parent::buildClass($name);
$pageName = $this->argument('name');
$baseClass = 'Tests\Browser\Pages\Page';
if (! Str::contains($pageName, '/') && class_exists($baseClass)) {
return $result;
} elseif (! class_exists($baseClass)) {
$baseClass = 'Laravel\Dusk\Page';
}
$lineEndingCount = [
"\r\n" => substr_count($result, "\r\n"),
"\r" => substr_count($result, "\r"),
"\n" => substr_count($result, "\n"),
];
$eol = array_keys($lineEndingCount, max($lineEndingCount))[0];
return str_replace(
'use Laravel\Dusk\Browser;'.$eol,
'use Laravel\Dusk\Browser;'.$eol."use {$baseClass};".$eol,
$result
);
}
/**
* Get the stub file for the generator.
*
* @return string
*/
protected function getStub()
{
return __DIR__.'/stubs/page.stub';
}
/**
* Get the destination class path.
*
* @param string $name
* @return string
*/
protected function getPath($name)
{
$name = Str::replaceFirst($this->rootNamespace(), '', $name);
return $this->laravel->basePath().'/tests'.str_replace('\\', '/', $name).'.php';
}
/**
* Get the default namespace for the class.
*
* @param string $rootNamespace
* @return string
*/
protected function getDefaultNamespace($rootNamespace)
{
return $rootNamespace.'\Browser\Pages';
}
/**
* Get the root namespace for the class.
*
* @return string
*/
protected function rootNamespace()
{
return 'Tests';
}
}
+116
View File
@@ -0,0 +1,116 @@
<?php
namespace Laravel\Dusk\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Finder\Finder;
#[AsCommand(name: 'dusk:purge')]
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->components->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->components->info("Purged \"{$patterns}\" from [{$relativePath}].");
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
namespace DummyNamespace;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Component as BaseComponent;
class DummyClass extends BaseComponent
{
/**
* Get the root selector for the component.
*/
public function selector(): string
{
return '#selector';
}
/**
* Assert that the browser page contains the component.
*/
public function assert(Browser $browser): void
{
$browser->assertVisible($this->selector());
}
/**
* Get the element shortcuts for the component.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@element' => '#selector',
];
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace DummyNamespace;
use Laravel\Dusk\Browser;
class DummyClass extends Page
{
/**
* Get the URL for the page.
*/
public function url(): string
{
return '/';
}
/**
* Assert that the browser is on the page.
*/
public function assert(Browser $browser): void
{
$browser->assertPathIs($this->url());
}
/**
* Get the element shortcuts for the page.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@element' => '#selector',
];
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
use Laravel\Dusk\Browser;
test('example', function () {
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertSee('Laravel');
});
});
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace DummyNamespace;
use Laravel\Dusk\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\DuskTestCase;
class DummyClass extends DuskTestCase
{
/**
* A Dusk test example.
*/
public function test_example(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertSee('Laravel');
});
}
}
+64
View File
@@ -0,0 +1,64 @@
<?php
namespace Laravel\Dusk;
use InvalidArgumentException;
class Dusk
{
/**
* The Dusk selector (@dusk) HTML attribute.
*
* @var string
*/
public static $selectorHtmlAttribute = 'dusk';
/**
* Register the Dusk service provider.
*
* @param array $options
* @return void
*/
public static function register(array $options = [])
{
if (static::duskEnvironment($options)) {
app()->register(DuskServiceProvider::class);
}
}
/**
* Determine if Dusk may run in this environment.
*
* @param array $options
* @return bool
*
* @throws \InvalidArgumentException
*/
protected static function duskEnvironment($options)
{
if (! isset($options['environments'])) {
return false;
}
if (is_string($options['environments'])) {
$options['environments'] = [$options['environments']];
}
if (! is_array($options['environments'])) {
throw new InvalidArgumentException('Dusk environments must be listed as an array.');
}
return app()->environment(...$options['environments']);
}
/**
* Set the Dusk selector (@dusk) HTML attribute.
*
* @param string $attribute
* @return void
*/
public static function selectorHtmlAttribute(string $attribute)
{
static::$selectorHtmlAttribute = $attribute;
}
}
+53
View File
@@ -0,0 +1,53 @@
<?php
namespace Laravel\Dusk;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class DuskServiceProvider extends ServiceProvider
{
/**
* Bootstrap any package services.
*
* @return void
*/
public function boot()
{
if (! $this->app->environment('production')) {
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('/logout/{guard?}', [
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@logout',
'as' => 'dusk.logout',
]);
Route::get('/user/{guard?}', [
'uses' => 'Laravel\Dusk\Http\Controllers\UserController@user',
'as' => 'dusk.user',
]);
});
}
if ($this->app->runningInConsole()) {
$this->commands([
Console\InstallCommand::class,
Console\DuskCommand::class,
Console\DuskFailsCommand::class,
Console\MakeCommand::class,
Console\PageCommand::class,
Console\PurgeCommand::class,
Console\ComponentCommand::class,
Console\ChromeDriverCommand::class,
]);
}
}
}
+426
View File
@@ -0,0 +1,426 @@
<?php
namespace Laravel\Dusk;
use Exception;
use Facebook\WebDriver\WebDriverBy;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use InvalidArgumentException;
class ElementResolver
{
use Macroable;
/**
* The remote web driver instance.
*
* @var \Facebook\WebDriver\Remote\RemoteWebDriver
*/
public $driver;
/**
* The selector prefix for the resolver.
*
* @var string
*/
public $prefix;
/**
* Set the elements the resolver should use as shortcuts.
*
* @var array<string, string>
*/
public $elements = [];
/**
* The button finding methods.
*
* @var array
*/
protected $buttonFinders = [
'findById',
'findButtonBySelector',
'findButtonByName',
'findButtonByValue',
'findButtonByText',
];
/**
* Create a new element resolver instance.
*
* @param \Facebook\WebDriver\Remote\RemoteWebDriver $driver
* @param string $prefix
* @return void
*/
public function __construct($driver, $prefix = 'body')
{
$this->driver = $driver;
$this->prefix = trim($prefix);
}
/**
* Set the page elements the resolver should use as shortcuts.
*
* @param array<string, string> $elements
* @return $this
*/
public function pageElements(array $elements)
{
$this->elements = $elements;
return $this;
}
/**
* Resolve the element for a given input "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForTyping($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
"input[name='{$field}']", "textarea[name='{$field}']", $field,
]);
}
/**
* Resolve the element for a given select "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForSelection($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
"select[name='{$field}']", $field,
]);
}
/**
* Resolve all the options with the given value on the select field.
*
* @param string $field
* @param array $values
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
*
* @throws \Exception
*/
public function resolveSelectOptions($field, array $values)
{
$options = $this->resolveForSelection($field)
->findElements(WebDriverBy::tagName('option'));
if (empty($options)) {
return [];
}
return array_filter($options, function ($option) use ($values) {
return in_array($option->getAttribute('value'), $values);
});
}
/**
* Resolve the element for a given radio "field" / value.
*
* @param string $field
* @param string|null $value
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
* @throws \InvalidArgumentException
*/
public function resolveForRadioSelection($field, $value = null)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
if (is_null($value)) {
throw new InvalidArgumentException(
"No value was provided for radio button [{$field}]."
);
}
return $this->firstOrFail([
"input[type=radio][name='{$field}'][value='{$value}']", $field,
]);
}
/**
* Resolve the element for a given checkbox "field".
*
* @param string|null $field
* @param string|null $value
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForChecking($field, $value = null)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
$selector = 'input[type=checkbox]';
if (! is_null($field)) {
$selector .= "[name='{$field}']";
}
if (! is_null($value)) {
$selector .= "[value='{$value}']";
}
return $this->firstOrFail([
$selector, $field,
]);
}
/**
* Resolve the element for a given file "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForAttachment($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
"input[type=file][name='{$field}']", $field,
]);
}
/**
* Resolve the element for a given "field".
*
* @param string $field
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function resolveForField($field)
{
if (! is_null($element = $this->findById($field))) {
return $element;
}
return $this->firstOrFail([
"input[name='{$field}']", "textarea[name='{$field}']",
"select[name='{$field}']", "button[name='{$field}']", $field,
]);
}
/**
* Resolve the element for a given button.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \InvalidArgumentException
*/
public function resolveForButtonPress($button)
{
foreach ($this->buttonFinders as $method) {
if (! is_null($element = $this->{$method}($button))) {
return $element;
}
}
throw new InvalidArgumentException(
"Unable to locate button [{$button}]."
);
}
/**
* Resolve the element for a given button by selector.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonBySelector($button)
{
if (! is_null($element = $this->find($button))) {
return $element;
}
}
/**
* Resolve the element for a given button by name.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonByName($button)
{
if (! is_null($element = $this->find("input[type=submit][name='{$button}']")) ||
! is_null($element = $this->find("input[type=button][value='{$button}']")) ||
! is_null($element = $this->find("button[name='{$button}']"))) {
return $element;
}
}
/**
* Resolve the element for a given button by value.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonByValue($button)
{
foreach ($this->all('input[type=submit]') as $element) {
if ($element->getAttribute('value') === $button) {
return $element;
}
}
}
/**
* Resolve the element for a given button by text.
*
* @param string $button
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findButtonByText($button)
{
// First, try to find a button with an exact text match...
foreach ($this->all('button') as $element) {
if (trim($element->getText()) === $button) {
return $element;
}
}
// If no exact match is found, fall back to a "contains" match...
foreach ($this->all('button') as $element) {
if (Str::contains($element->getText(), $button)) {
return $element;
}
}
}
/**
* Attempt to find the selector by ID.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
protected function findById($selector)
{
if (preg_match('/^#[\w\-:]+$/', $selector)) {
return $this->driver->findElement(WebDriverBy::id(substr($selector, 1)));
}
}
/**
* Find an element by the given selector or return null.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement|null
*/
public function find($selector)
{
try {
return $this->findOrFail($selector);
} catch (Exception $e) {
//
}
}
/**
* Get the first element matching the given selectors.
*
* @param array $selectors
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*
* @throws \Exception
*/
public function firstOrFail($selectors)
{
foreach ((array) $selectors as $selector) {
try {
return $this->findOrFail($selector);
} catch (Exception $e) {
//
}
}
throw $e;
}
/**
* Find an element by the given selector or throw an exception.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement
*/
public function findOrFail($selector)
{
if (! is_null($element = $this->findById($selector))) {
return $element;
}
return $this->driver->findElement(
WebDriverBy::cssSelector($this->format($selector))
);
}
/**
* Find the elements by the given selector or return an empty array.
*
* @param string $selector
* @return \Facebook\WebDriver\Remote\RemoteWebElement[]
*/
public function all($selector)
{
try {
return $this->driver->findElements(
WebDriverBy::cssSelector($this->format($selector))
);
} catch (Exception $e) {
//
}
return [];
}
/**
* Format the given selector with the current prefix.
*
* @param string $selector
* @return string
*/
public function format($selector)
{
$sortedElements = collect($this->elements)->sortByDesc(function ($element, $key) {
return strlen($key);
})->toArray();
$selector = str_replace(
array_keys($sortedElements), array_values($sortedElements), $originalSelector = $selector
);
if (Str::startsWith($selector, '@') && $selector === $originalSelector) {
$selector = preg_replace('/@([^\s\)]+)/', '['.Dusk::$selectorHtmlAttribute.'="$1"]', $selector);
}
return trim($this->prefix.' '.$selector);
}
}
@@ -0,0 +1,78 @@
<?php
namespace Laravel\Dusk\Http\Controllers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
class UserController
{
/**
* Retrieve the authenticated user identifier and class name.
*
* @param string|null $guard
* @return array
*/
public function user($guard = null)
{
$user = Auth::guard($guard)->user();
if (! $user) {
return [];
}
return [
'id' => $user->getAuthIdentifier(),
'className' => get_class($user),
];
}
/**
* Login using the given user ID / email.
*
* @param string $userId
* @param string|null $guard
* @return void
*/
public function login($userId, $guard = null)
{
$guard = $guard ?: config('auth.defaults.guard');
$provider = Auth::guard($guard)->getProvider();
$user = Str::contains($userId, '@')
? $provider->retrieveByCredentials(['email' => $userId])
: $provider->retrieveById($userId);
Auth::guard($guard)->login($user);
}
/**
* Log the user out of the application.
*
* @param string|null $guard
* @return void
*/
public function logout($guard = null)
{
$guard = $guard ?: config('auth.defaults.guard');
Auth::guard($guard)->logout();
Session::forget('password_hash_'.$guard);
}
/**
* Get the model for the given guard.
*
* @param string $guard
* @return string
*/
protected function modelForGuard($guard)
{
$provider = config("auth.guards.{$guard}.provider");
return config("auth.providers.{$provider}.model");
}
}
+114
View File
@@ -0,0 +1,114 @@
<?php
namespace Laravel\Dusk;
use BadMethodCallException;
use Illuminate\Support\Traits\Macroable;
/**
* @mixin \Facebook\WebDriver\Remote\RemoteKeyboard
*/
class Keyboard
{
use Macroable {
__call as macroCall;
}
/**
* The browser instance.
*
* @var \Laravel\Dusk\Browser
*/
public $browser;
/**
* Create a keyboard instance.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function __construct(Browser $browser)
{
$this->browser = $browser;
}
/**
* Press the key using keyboard.
*
* @return $this
*/
public function press($key)
{
$this->pressKey($key);
return $this;
}
/**
* Release the given pressed key.
*
* @return $this
*/
public function release($key)
{
$this->releaseKey($key);
return $this;
}
/**
* Type the given keys using keyboard.
*
* @param string|array<int, string> $keys
* @return $this
*/
public function type($keys)
{
$this->sendKeys($keys);
return $this;
}
/**
* Pause for the given amount of milliseconds.
*
* @param int $milliseconds
* @return $this
*/
public function pause($milliseconds)
{
$this->browser->pause($milliseconds);
return $this;
}
/**
* Dynamically call a method on the keyboard.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (static::hasMacro($method)) {
return $this->macroCall($method, $parameters);
}
$keyboard = $this->browser->driver->getKeyboard();
if (method_exists($keyboard, $method)) {
$response = $keyboard->{$method}(...$parameters);
if ($response === $keyboard) {
return $this;
} else {
return $response;
}
}
throw new BadMethodCallException("Call to undefined keyboard method [{$method}].");
}
}
+159
View File
@@ -0,0 +1,159 @@
<?php
namespace Laravel\Dusk;
use Illuminate\Support\Str;
use InvalidArgumentException;
class OperatingSystem
{
/**
* List of available operating system platforms.
*
* @var array<string, array{slug: string, commands: array<int, string>}>
*/
protected static $platforms = [
'linux' => [
'slug' => 'linux64',
'commands' => [
'/usr/bin/google-chrome --version',
'/usr/bin/chromium-browser --version',
'/usr/bin/chromium --version',
'/usr/bin/google-chrome-stable --version',
],
],
'mac' => [
'slug' => 'mac-x64',
'commands' => [
'/Applications/Google\ Chrome\ for\ Testing.app/Contents/MacOS/Google\ Chrome\ for\ Testing --version',
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
],
],
'mac-intel' => [
'slug' => 'mac-x64',
'commands' => [
'/Applications/Google\ Chrome\ for\ Testing.app/Contents/MacOS/Google\ Chrome\ for\ Testing --version',
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
],
],
'mac-arm' => [
'slug' => 'mac-arm64',
'commands' => [
'/Applications/Google\ Chrome\ for\ Testing.app/Contents/MacOS/Google\ Chrome\ for\ Testing --version',
'/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version',
],
],
'win' => [
'slug' => 'win32',
'commands' => [
'reg query "HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon" /v version',
],
],
];
/**
* Resolve the Chrome version commands for the given operating system.
*
* @param string $operatingSystem
* @return array<int, string>
*/
public static function chromeVersionCommands($operatingSystem)
{
$commands = static::$platforms[$operatingSystem]['commands'] ?? null;
if (is_null($commands)) {
throw new InvalidArgumentException("Unable to find commands for Operating System [{$operatingSystem}]");
}
return $commands;
}
/**
* Resolve the ChromeDriver slug for the given operating system.
*
* @param string $operatingSystem
* @param string|null $version
* @return string
*/
public static function chromeDriverSlug($operatingSystem, $version = null)
{
$slug = static::$platforms[$operatingSystem]['slug'] ?? null;
if (is_null($slug)) {
throw new InvalidArgumentException("Unable to find ChromeDriver slug for Operating System [{$operatingSystem}]");
}
if (! is_null($version) && version_compare($version, '115.0', '<')) {
if ($slug === 'mac-arm64') {
return version_compare($version, '106.0.5249', '<') ? 'mac64_m1' : 'mac_arm64';
} elseif ($slug === 'mac-x64') {
return 'mac64';
}
}
return $slug;
}
/**
* Get all supported operating systems.
*
* @return array<int, string>
*/
public static function all()
{
return array_keys(static::$platforms);
}
/**
* Get the current operating system identifier.
*
* @return string
*/
public static function id()
{
if (static::onWindows()) {
return 'win';
} elseif (static::onMac()) {
return static::macArchitectureId();
}
return 'linux';
}
/**
* Determine if the operating system is Windows or Windows Subsystem for Linux.
*
* @return bool
*/
public static function onWindows()
{
return PHP_OS === 'WINNT' || Str::contains(php_uname(), 'Microsoft');
}
/**
* Determine if the operating system is macOS.
*
* @return bool
*/
public static function onMac()
{
return PHP_OS === 'Darwin';
}
/**
* Get the current macOS 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';
}
}
}
+44
View File
@@ -0,0 +1,44 @@
<?php
namespace Laravel\Dusk;
abstract class Page
{
/**
* Get the URL for the page.
*
* @return string
*/
abstract public function url();
/**
* Assert that the browser is on the page.
*
* @param \Laravel\Dusk\Browser $browser
* @return void
*/
public function assert(Browser $browser)
{
//
}
/**
* Get the element shortcuts for the page.
*
* @return array
*/
public function elements()
{
return [];
}
/**
* Get the global element shortcuts for the site.
*
* @return array
*/
public static function siteElements()
{
return [];
}
}
+100
View File
@@ -0,0 +1,100 @@
<?php
namespace Laravel\Dusk;
use Exception;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Illuminate\Foundation\Testing\TestCase as FoundationTestCase;
use Laravel\Dusk\Chrome\SupportsChrome;
use Laravel\Dusk\Concerns\ProvidesBrowser;
abstract class TestCase extends FoundationTestCase
{
use ProvidesBrowser, SupportsChrome;
/**
* Register the base URL with Dusk.
*
* @return void
*/
protected function setUp(): void
{
parent::setUp();
Browser::$baseUrl = $this->baseUrl();
Browser::$storeScreenshotsAt = base_path('tests/Browser/screenshots');
Browser::$storeConsoleLogAt = base_path('tests/Browser/console');
Browser::$storeSourceAt = base_path('tests/Browser/source');
Browser::$userResolver = function () {
return $this->user();
};
}
/**
* Create the RemoteWebDriver instance.
*
* @return \Facebook\WebDriver\Remote\RemoteWebDriver
*/
protected function driver()
{
return RemoteWebDriver::create(
$_ENV['DUSK_DRIVER_URL'] ?? env('DUSK_DRIVER_URL') ?? 'http://localhost:9515',
DesiredCapabilities::chrome()
);
}
/**
* Determine the application's base URL.
*
* @return string
*/
protected function baseUrl()
{
return rtrim(config('app.url'), '/');
}
/**
* Return the default user to authenticate.
*
* @return \App\User|int|null
*
* @throws \Exception
*/
protected function user()
{
throw new Exception('User resolver has not been set.');
}
/**
* Determine whether the Dusk command has disabled headless mode.
*/
protected function hasHeadlessDisabled(): bool
{
return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
isset($_ENV['DUSK_HEADLESS_DISABLED']);
}
/**
* Determine if the browser window should start maximized.
*/
protected function shouldStartMaximized(): bool
{
return isset($_SERVER['DUSK_START_MAXIMIZED']) ||
isset($_ENV['DUSK_START_MAXIMIZED']);
}
/**
* Determine if the tests are running within Laravel Sail.
*
* @return bool
*/
protected static function runningInSail()
{
return isset($_ENV['LARAVEL_SAIL']) && $_ENV['LARAVEL_SAIL'] == '1';
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace Tests;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Illuminate\Support\Collection;
use Laravel\Dusk\TestCase as BaseTestCase;
use PHPUnit\Framework\Attributes\BeforeClass;
abstract class DuskTestCase extends BaseTestCase
{
use CreatesApplication;
/**
* Prepare for Dusk test execution.
*/
#[BeforeClass]
public static function prepare(): void
{
if (! static::runningInSail()) {
static::startChromeDriver(['--port=9515']);
}
}
/**
* Create the RemoteWebDriver instance.
*/
protected function driver(): RemoteWebDriver
{
$options = (new ChromeOptions)->addArguments(collect([
$this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1920,1080',
'--disable-search-engine-choice-screen',
'--disable-smooth-scrolling',
])->unless($this->hasHeadlessDisabled(), function (Collection $items) {
return $items->merge([
'--disable-gpu',
'--headless=new',
]);
})->all());
return RemoteWebDriver::create(
$_ENV['DUSK_DRIVER_URL'] ?? env('DUSK_DRIVER_URL') ?? 'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
);
}
}
+10
View File
@@ -0,0 +1,10 @@
<?php
use Laravel\Dusk\Browser;
test('basic example', function () {
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertSee('Laravel');
});
});
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
/**
* A basic browser test example.
*/
public function test_basic_example(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertSee('Laravel');
});
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
class HomePage extends Page
{
/**
* Get the URL for the page.
*/
public function url(): string
{
return '/';
}
/**
* Assert that the browser is on the page.
*/
public function assert(Browser $browser): void
{
//
}
/**
* Get the element shortcuts for the page.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@element' => '#selector',
];
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Page as BasePage;
abstract class Page extends BasePage
{
/**
* Get the global element shortcuts for the site.
*
* @return array<string, string>
*/
public static function siteElements(): array
{
return [
'@element' => '#selector',
];
}
}
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
beStrictAboutTestsThatDoNotTestAnything="false"
colors="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
cacheDirectory=".phpunit.cache"
backupStaticProperties="false">
<testsuites>
<testsuite name="Browser Test Suite">
<directory suffix="Test.php">./tests/Browser</directory>
</testsuite>
</testsuites>
</phpunit>
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<script type="text/javascript">
function copy(e) {
e.innerHTML = 'Copied!'
setTimeout(() => {
e.innerHTML = 'Copy'
}, 2000);
}
</script>
</head>
<body>
<button dusk="copy-button" onclick="copy(this)">Copy</button>
</body>
</html>
+17
View File
@@ -0,0 +1,17 @@
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "web" middleware group. Make something great!
|
*/
Route::view('/', 'welcome');
Route::view('tests/wait-for-text-in', 'workbench::wait-for-text-in');
+278
View File
@@ -0,0 +1,278 @@
# Changelog
This project versioning adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
## 1.16.0 - 2025-12-29
### Fixed
- Fix file upload endpoint for Selenium protocol (W3C WebDriver extension).
- Docs: Fix incorrect link to driver capabilities docs.
### Changed
- Exceptions thrown by the library are now always extends `\Throwable`, to allow better work with exception methods when catching `\PhpWebDriverExceptionInterface`.
- Support Symfony 8.
- Timeouts value types now properly supports and allow only `null|int|float` for W3C Webdriver (while keeping only `int|float` allowed for JsonWire protocol).
- Tests: Update SauceLabs Connect and use new platform versions.
- Tests: Remove `new` flag from headless chrome, as it is already default since Chromedriver 132.
## 1.15.2 - 2024-11-21
### Fixed
- PHP 8.4 deprecation notices, especially in nullable type-hints.
- Docs: Fix static return types in RemoteWebElement phpDoc.
- Tests: Disable chrome 127+ search engine pop-up in tests
- Tests: Enable Shadow DOM tests in Geckodriver
### Added
- Tests: Allow running tests in headfull (not headless) mode using `DISABLE_HEADLESS` environment variable.
### Changed
- Docs: Update selenium server host URL in example.
## 1.15.1 - 2023-10-20
- Update `symfony/process` dependency to support upcoming Symfony 7.
## 1.15.0 - 2023-08-29
### Changed
- Capability key `ChromeOptions::CAPABILITY_W3C` used to set ChromeOptions is now deprecated in favor of `ChromeOptions::CAPABILITY`, which now also contains the W3C compatible value (`goog:chromeOptions`).
- ChromeOptions are now passed to the driver always as a W3C compatible key `goog:chromeOptions`, even in the deprecated OSS JsonWire payload (as ChromeDriver [supports](https://bugs.chromium.org/p/chromedriver/issues/detail?id=1786) this since 2017).
- Improve Safari compatibility for `<select multilpe>` element.
- Remove no longer needed compatibility layer with old Symfony.
- Docs: Document exception throwing in findElement.
### Fixed
- Handle unexpected response when getting element(s) by throwing an exception, not triggering fatal error.
## 1.14.0 - 2023-02-09
### Added
- `PhpWebDriverExceptionInterface` as a common interface to identify all exceptions thrown in php-webdriver.
### Changed
- Require PHP ^7.3.
- Capabilities must be either explicitly provided or retrievable from Selenium Grid when resuing session with `createBySessionID()`.
- Throw `UnexpectedResponseException` instead of `UnknownErrorException` in `findElement()` and `findElements()` methods.
- Throw custom php-webdriver exceptions instead of native PHP SPL exceptions.
- Do not mix internal non-W3C WebDriver exceptions, separate them into own namespaces.
## 1.13.1 - 2022-10-11
### Fixed
- Do not fail when using `isDisplayed()` and capabilities are missing in WebDriver instance. (Happens when driver instance was created using `RemoteWebDriver::createBySessionID()`.)
## 1.13.0 - 2022-10-03
### Added
- Support for current Firefox XPI extension format. Extensions could now be loaded into `FirefoxProfile` using `addExtension()` method.
- `setProfile()` method to `FirefoxOptions`, which is now a preferred way to set Firefox Profile.
- Element `isDisplayed()` can now be used even for browsers not supporting native API endpoint (like Safari), thanks to javascript atom workaround.
### Changed
- Handle errors when taking screenshots. `WebDriverException` is thrown if WebDriver returns empty or invalid screenshot data.
- Deprecate `FirefoxDriver::PROFILE` constant. Instead, use `setProfile()` method of `FirefoxOptions` to set Firefox Profile.
- Deprecate `getAllSessions()` method of `RemoteWebDriver` (which is not part of W3C WebDriver).
- Increase default request timeout to 3 minutes (instead of 30 seconds).
### Fixed
- Throw `UnknownErrorException` instead of fatal error if remote end returns invalid response for `findElement()`/`findElements()` commands.
## 1.12.1 - 2022-05-03
### Fixed
- Improper PHP documentation for `getAttribute()` and `getDomProperty()`.
- Unsafe use of `static::` when accessing private property in `DesiredCapabilities`.
- PHP 8.1 deprecations in the `Cookie` class.
### Changed
- Docs: Extend `findElement()`/`findElements()` method documentation to better explain XPath behavior.
- Add `@return` and `@param` type annotations to Cookie class to avoid deprecations in PHP 8.1.
## 1.12.0 - 2021-10-14
### Added
- `RemoteWebElement::getDomProperty()` method to read JavaScript properties of an element (like the value of `innerHTML` etc.) in W3C mode.
- `WebDriverCommand::newSession()` constructor to create new session command without violating typehints.
### Changed
- Allow installation of Symfony 6 components.
### Fixed
- PHP 8.1 compatibility.
## 1.11.1 - 2021-05-21
### Fixed
- `RemoteWebElement::getLocationOnScreenOnceScrolledIntoView()` was missing polyfill implementation for W3C mode and not working in eg. Safari.
## 1.11.0 - 2021-05-03
### Added
- `FirefoxOptions` class to simplify passing Firefox capabilities. Usage is covered [in our wiki](https://github.com/php-webdriver/php-webdriver/wiki/Firefox#firefoxoptions).
- `FirefoxDriver` to easy local start of Firefox instance without a need to start the `geckodriver` process manually. [See wiki](https://github.com/php-webdriver/php-webdriver/wiki/Firefox#start-directly-using-firefoxdriver-class) for usage examples.
- Method `ChromeDriver::startUsingDriverService()` to be used for creating ChromeDriver instance with custom service.
### Fixed
- Driver capabilities received from the browser when creating now session were not set to the instance of ChromeDriver (when ChromeDriver::start() was used).
### Changed
- Deprecate `ChromeDriver::startSession`. However, the method was supposed to be used only internally.
- KeyDown and KeyUp actions will throw an exception when not used with modifier keys.
## 1.10.0 - 2021-02-25
### Added
- Support for sending Chrome DevTools Protocol commands (see details in [wiki](https://github.com/php-webdriver/php-webdriver/wiki/Chrome#chrome-devtools-protocol-cdp)).
- Option to specify type of new window (window or tab) when using `$driver->switchTo()->newWindow()`.
### Fixed
- Actually start ChromeDriver in W3C mode if it is supported by the browser driver. Until now, when it was initialized using `ChromeDriver::start()`, it has always been unintentionally started in OSS mode.
- ChromeOptions were ignored when passed to DesiredCapabilities as `ChromeOptions::CAPABILITY_W3C`.
- Clicking a block element inside `<a>` element in Firefox (workaround for GeckoDriver bug [1374283](https://bugzilla.mozilla.org/show_bug.cgi?id=1374283)).
### Changed
- Throw `DriverServerDiedException` on local driver process terminating unexpectedly and provide full details of original exception to improve debugging.
- Do not require `WEBDRIVER_CHROME_DRIVER` environment variable to be set if `chromedriver` binary is already available via system PATH.
- Mark PhantomJS deprecated, as it is no longer developed and maintained.
- Deprecate `RemoteWebDriver::newWindow()` in favor of `$driver->switchTo()->newWindow()`.
- Don't escape slashes in CURL exception message to improve readability.
## 1.9.0 - 2020-11-19
### Added
- Support of SameSite cookie property.
- Command `RemoteWebDriver::newWindow()` for W3C mode to open new top-level browsing context (aka window).
- PHP 8.0 support.
## 1.8.3 - 2020-10-06
### Fixed
- Make `alertIsPresent()` condition working in W3C mode.
- `RemoteWebDriver::create()` cannot be used without providing the second parameter (which is in fact optional).
- `ChromeDriver::start()` starts in inconsistent state mixing W3C/OSS mode.
- Modifier keys are not released when sending NULL key in GeckoDriver (workaround for GeckoDriver bug [1494661](https://bugzilla.mozilla.org/show_bug.cgi?id=1494661)).
- Do not set unnecessary `binary` value of `goog:chromeOptions` while keep the object in proper data type required by ChromeDriver.
## 1.8.2 - 2020-03-04
### Changed
- Reimplement element `equals()` method to be working in W3C mode.
- New instance of `RemoteWebDriver` created via `createBySessionID()` by default expects W3C mode. This could be disabled using fifth parameter of `createBySessionID()`.
- Disable JSON viewer in Firefox to let JSON response be rendered as-is.
### Fixed
- Properly read fifth parameter whether W3C compliant instance should be created when using `createBySessionID()`.
- Creating of Firefox profile with libzip 1.6+ (eg. on Mac OS Catalina).
## 1.8.1 - 2020-02-17
### Fixed
- Accept array as possible input to `sendKeys()` method. (Unintentional BC break in 1.8.0.)
- Use relative offset when moving mouse pointer in W3C WebDriver mode.
## 1.8.0 - 2020-02-10
### Added
- Experimental W3C WebDriver protocol support. The protocol will be used automatically when remote end (like Geckodriver, newer Chromedriver etc.) supports it.
- `getStatus()` method of `RemoteWebDriver` to get information about remote-end readiness to create new sessions.
- `takeElementScreenshot()` method of `RemoteWebElement` to do the obvious - take screenshot of the particular element.
- Support for sending custom commands via `executeCustomCommand()`. See [wiki](https://github.com/php-webdriver/php-webdriver/wiki/Custom-commands) for more information.
### Changed
- The repository was migrated to [`php-webdriver/php-webdriver`](https://github.com/php-webdriver/php-webdriver/).
- The Packagist package was renamed to [`php-webdriver/webdriver`](https://packagist.org/packages/php-webdriver/webdriver) and the original [`facebook/webdriver`](https://packagist.org/packages/facebook/webdriver) was marked as abandoned.
- Revert no longer needed workaround for Chromedriver bug [2943](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2943).
- Allow installation of Symfony 5 components.
- Rename environment variable used to pass path to ChromeDriver executable from `webdriver.chrome.driver` to `WEBDRIVER_CHROME_DRIVER`. However the old one also still works to keep backward compatibility
- If subdirectories in a path to screenshot destination does not exists (using `takeScreenshot()` or `takeElementScreenshot()` methods), they are automatically created.
- When zip archive cannot be created during file upload, throw an exception instead of silently returning false.
- `WebDriverNavigation` and `EventFiringWebDriverNavigation` now both implement new `WebDriverNavigationInterface`.
### Fixed
- `WebDriverExpectedCondition::presenceOfElementLocated()` works correctly when used within `WebDriverExpectedCondition::not()`.
- Improper behavior of Microsoft Edge when retrieving all cookies via `getCookies()` (it was causing fatal error when there were no cookies).
- Avoid "path is not canonical" error when uploading file to Chromedriver.
## 1.7.1 - 2019-06-13
### Fixed
- Error `Call to a member function toArray()` if capabilities were already converted to an array.
- Temporarily do not send capabilities to disable W3C WebDriver protocol when BrowserStack hub is used.
## 1.7.0 - 2019-06-10
### Added
- `WebDriverCheckboxes` and `WebDriverRadios` helper classes to simplify interaction with checkboxes and radio buttons.
### Fixed
- Stop sending null values in Cookie object, which is against the protocol and may cause request to remote ends to fail.
### Changed
- Force Chrome to not use W3C WebDriver protocol.
- Add workaround for Chromedriver bug [2943](https://bugs.chromium.org/p/chromedriver/issues/detail?id=2943) which breaks the protocol in Chromedriver 75.
## 1.6.0 - 2018-05-16
### Added
- Connection and request timeouts could be specified also when creating RemoteWebDriver from existing session ID.
- Update PHPDoc for functions that return static instances of a class.
### Changed
- Disable sending 'Expect: 100-Continue' header with POST requests, as they may more easily fail when sending via eg. squid proxy.
## 1.5.0 - 2017-11-15
### Changed
- Drop PHP 5.5 support, the minimal required version of PHP is now PHP 5.6.
- Allow installation of Symfony 4 components.
### Added
- Add a `visibilityOfAnyElementsLocated()` method to `WebDriverExpectedCondition`.
## 1.4.1 - 2017-04-28
### Fixed
- Do not throw notice `Constant CURLOPT_CONNECTTIMEOUT_MS already defined`.
## 1.4.0 - 2017-03-22
### Changed
- Cookies should now be set using `Cookie` value object instead of an array when passed to to `addCookie()` method of `WebDriverOptions`.
- Cookies retrieved using `getCookieNamed()` and `getCookies()` methods of `WebDriverOptions` are now encapsulated in `Cookie` object instead of an plain array. The object implements `ArrayAccess` interface to provide backward compatibility.
- `ext-zip` is now specified as required dependency in composer.json (but the extension was already required by the code, though).
- Deprecate `WebDriverCapabilities::isJavascriptEnabled()` method.
- Deprecate `textToBePresentInElementValue` expected condition in favor of `elementValueContains`.
### Fixed
- Do not throw fatal error when `null` is passed to `sendKeys()`.
## 1.3.0 - 2017-01-13
### Added
- Added `getCapabilities()` method of `RemoteWebDriver`, to retrieve actual capabilities acknowledged by the remote driver on startup.
- Added option to pass required capabilities when creating `RemoteWebDriver`. (So far only desired capabilities were supported.)
- Added new expected conditions:
- `urlIs` - current URL exactly equals given value
- `urlContains` - current URL contains given text
- `urlMatches` - current URL matches regular expression
- `titleMatches` - current page title matches regular expression
- `elementTextIs` - text in element exactly equals given text
- `elementTextContains` (as an alias for `textToBePresentInElement`) - text in element contains given text
- `elementTextMatches` - text in element matches regular expression
- `numberOfWindowsToBe` - number of opened windows equals given number
- Possibility to select option of `<select>` by its partial text (using `selectByVisiblePartialText()`).
- `XPathEscaper` helper class to quote XPaths containing both single and double quotes.
- `WebDriverSelectInterface`, to allow implementation of custom select-like components, eg. those not built around and actual select tag.
### Changed
- `Symfony\Process` is used to start local WebDriver processes (when browsers are run directly, without Selenium server) to workaround some PHP bugs and improve portability.
- Clarified meaning of selenium server URL variable in methods of `RemoteWebDriver` class.
- Deprecated `setSessionID()` and `setCommandExecutor()` methods of `RemoteWebDriver` class; these values should be immutable and thus passed only via constructor.
- Deprecated `WebDriverExpectedCondition::textToBePresentInElement()` in favor of `elementTextContains()`.
- Throw an exception when attempting to deselect options of non-multiselect (it already didn't have any effect, but was silently ignored).
- Optimize performance of `(de)selectByIndex()` and `getAllSelectedOptions()` methods of `WebDriverSelect` when used with non-multiple select element.
### Fixed
- XPath escaping in `select*()` and `deselect*()` methods of `WebDriverSelect`.
## 1.2.0 - 2016-10-14
- Added initial support of remote Microsoft Edge browser (but starting local EdgeDriver is still not supported).
- Utilize late static binding to make eg. `WebDriverBy` and `DesiredCapabilities` classes easily extensible.
- PHP version at least 5.5 is required.
- Fixed incompatibility with Appium, caused by redundant params present in requests to Selenium server.
## 1.1.3 - 2016-08-10
- Fixed FirefoxProfile to support installation of extensions with custom namespace prefix in their manifest file.
- Comply codestyle with [PSR-2](http://www.php-fig.org/psr/psr-2/).
## 1.1.2 - 2016-06-04
- Added ext-curl to composer.json.
- Added CHANGELOG.md.
- Added CONTRIBUTING.md with information and rules for contributors.
## 1.1.1 - 2015-12-31
- Fixed strict standards error in `ChromeDriver`.
- Added unit tests for `WebDriverCommand` and `DesiredCapabilities`.
- Fixed retrieving temporary path name in `FirefoxDriver` when `open_basedir` restriction is in effect.
## 1.1.0 - 2015-12-08
- FirefoxProfile improved - added possibility to set RDF file and to add datas for extensions.
- Fixed setting 0 second timeout of `WebDriverWait`.
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2004-2020 Facebook
Copyright (c) 2020-present [open-source contributors](https://github.com/php-webdriver/php-webdriver/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+228
View File
@@ -0,0 +1,228 @@
# php-webdriver Selenium WebDriver bindings for PHP
[![Latest stable version](https://img.shields.io/packagist/v/php-webdriver/webdriver.svg?style=flat-square&label=Packagist)](https://packagist.org/packages/php-webdriver/webdriver)
[![GitHub Actions build status](https://img.shields.io/github/actions/workflow/status/php-webdriver/php-webdriver/tests.yaml?style=flat-square&label=GitHub%20Actions)](https://github.com/php-webdriver/php-webdriver/actions)
[![SauceLabs test status](https://img.shields.io/github/actions/workflow/status/php-webdriver/php-webdriver/sauce-labs.yaml?style=flat-square&label=SauceLabs)](https://saucelabs.com/u/php-webdriver)
[![Total downloads](https://img.shields.io/packagist/dd/php-webdriver/webdriver.svg?style=flat-square&label=Downloads)](https://packagist.org/packages/php-webdriver/webdriver)
## Description
Php-webdriver library is PHP language binding for Selenium WebDriver, which allows you to control web browsers from PHP.
This library is compatible with Selenium server version 2.x, 3.x and 4.x.
The library supports modern [W3C WebDriver](https://w3c.github.io/webdriver/) protocol, as well
as legacy [JsonWireProtocol](https://www.selenium.dev/documentation/legacy/json_wire_protocol/).
The concepts of this library are very similar to the "official" Java, JavaScript, .NET, Python and Ruby libraries
which are developed as part of the [Selenium project](https://github.com/SeleniumHQ/selenium/).
## Installation
Installation is possible using [Composer](https://getcomposer.org/).
If you don't already use Composer, you can download the `composer.phar` binary:
curl -sS https://getcomposer.org/installer | php
Then install the library:
php composer.phar require php-webdriver/webdriver
## Upgrade from version <1.8.0
Starting from version 1.8.0, the project has been renamed from `facebook/php-webdriver` to `php-webdriver/webdriver`.
In order to receive the new version and future updates, **you need to rename it in your composer.json**:
```diff
"require": {
- "facebook/webdriver": "(version you use)",
+ "php-webdriver/webdriver": "(version you use)",
}
```
and run `composer update`.
## Getting started
### 1. Start server (aka. remote end)
To control a browser, you need to start a *remote end* (server), which will listen to the commands sent
from this library and will execute them in the respective browser.
This could be Selenium standalone server, but for local development, you can send them directly to so-called "browser driver" like Chromedriver or Geckodriver.
#### a) Chromedriver
📙 Below you will find a simple example. Make sure to read our wiki for [more information on Chrome/Chromedriver](https://github.com/php-webdriver/php-webdriver/wiki/Chrome).
Install the latest Chrome and [Chromedriver](https://sites.google.com/chromium.org/driver/downloads).
Make sure to have a compatible version of Chromedriver and Chrome!
Run `chromedriver` binary, you can pass `port` argument, so that it listens on port 4444:
```sh
chromedriver --port=4444
```
#### b) Geckodriver
📙 Below you will find a simple example. Make sure to read our wiki for [more information on Firefox/Geckodriver](https://github.com/php-webdriver/php-webdriver/wiki/Firefox).
Install the latest Firefox and [Geckodriver](https://github.com/mozilla/geckodriver/releases).
Make sure to have a compatible version of Geckodriver and Firefox!
Run `geckodriver` binary (it start to listen on port 4444 by default):
```sh
geckodriver
```
#### c) Selenium standalone server
Selenium server can be useful when you need to execute multiple tests at once,
when you run tests in several different browsers (like on your CI server), or when you need to distribute tests amongst
several machines in grid mode (where one Selenium server acts as a hub, and others connect to it as nodes).
Selenium server then act like a proxy and takes care of distributing commands to the respective nodes.
The latest version can be found on the [Selenium download page](https://www.selenium.dev/downloads/).
📙 You can find [further Selenium server information](https://github.com/php-webdriver/php-webdriver/wiki/Selenium-server)
in our wiki.
#### d) Docker
Selenium server could also be started inside Docker container - see [docker-selenium project](https://github.com/SeleniumHQ/docker-selenium).
### 2. Create a Browser Session
When creating a browser session, be sure to pass the url of your running server.
For example:
```php
// Chromedriver (if started using --port=4444 as above)
$serverUrl = 'http://localhost:4444';
// Geckodriver
$serverUrl = 'http://localhost:4444';
// selenium-server-standalone-#.jar (version 2.x or 3.x)
$serverUrl = 'http://localhost:4444/wd/hub';
// selenium-server-standalone-#.jar (version 4.x)
$serverUrl = 'http://localhost:4444';
```
Now you can start browser of your choice:
```php
use Facebook\WebDriver\Remote\RemoteWebDriver;
// Chrome
$driver = RemoteWebDriver::create($serverUrl, DesiredCapabilities::chrome());
// Firefox
$driver = RemoteWebDriver::create($serverUrl, DesiredCapabilities::firefox());
// Microsoft Edge
$driver = RemoteWebDriver::create($serverUrl, DesiredCapabilities::microsoftEdge());
```
### 3. Customize Desired Capabilities
Desired capabilities define properties of the browser you are about to start.
They can be customized:
```php
use Facebook\WebDriver\Firefox\FirefoxOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
$desiredCapabilities = DesiredCapabilities::firefox();
// Disable accepting SSL certificates
$desiredCapabilities->setCapability('acceptSslCerts', false);
// Add arguments via FirefoxOptions to start headless firefox
$firefoxOptions = new FirefoxOptions();
$firefoxOptions->addArguments(['-headless']);
$desiredCapabilities->setCapability(FirefoxOptions::CAPABILITY, $firefoxOptions);
$driver = RemoteWebDriver::create($serverUrl, $desiredCapabilities);
```
Capabilities can also be used to [📙 configure a proxy server](https://github.com/php-webdriver/php-webdriver/wiki/HowTo-Work-with-proxy) which the browser should use.
To configure browser-specific capabilities, you may use [📙 ChromeOptions](https://github.com/php-webdriver/php-webdriver/wiki/Chrome#chromeoptions)
or [📙 FirefoxOptions](https://github.com/php-webdriver/php-webdriver/wiki/Firefox#firefoxoptions).
* See [legacy JsonWire protocol](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities) documentation or [W3C WebDriver specification](https://w3c.github.io/webdriver/#capabilities) for more details.
### 4. Control your browser
```php
// Go to URL
$driver->get('https://en.wikipedia.org/wiki/Selenium_(software)');
// Find search element by its id, write 'PHP' inside and submit
$driver->findElement(WebDriverBy::id('searchInput')) // find search input element
->sendKeys('PHP') // fill the search box
->submit(); // submit the whole form
// Find element of 'History' item in menu by its css selector
$historyButton = $driver->findElement(
WebDriverBy::cssSelector('#ca-history a')
);
// Read text of the element and print it to output
echo 'About to click to a button with text: ' . $historyButton->getText();
// Click the element to navigate to revision history page
$historyButton->click();
// Make sure to always call quit() at the end to terminate the browser session
$driver->quit();
```
See [example.php](example.php) for full example scenario.
Visit our GitHub wiki for [📙 php-webdriver command reference](https://github.com/php-webdriver/php-webdriver/wiki/Example-command-reference) and further examples.
**NOTE:** Above snippets are not intended to be a working example by simply copy-pasting. See [example.php](example.php) for a working example.
## Changelog
For latest changes see [CHANGELOG.md](CHANGELOG.md) file.
## More information
Some basic usage example is provided in [example.php](example.php) file.
How-tos are provided right here in [📙 our GitHub wiki](https://github.com/php-webdriver/php-webdriver/wiki).
If you don't use IDE, you may use [API documentation of php-webdriver](https://php-webdriver.github.io/php-webdriver/latest/).
You may also want to check out the Selenium project [docs](https://selenium.dev/documentation/en/) and [wiki](https://github.com/SeleniumHQ/selenium/wiki).
## Testing framework integration
To take advantage of automatized testing you may want to integrate php-webdriver to your testing framework.
There are some projects already providing this:
- [Symfony Panther](https://github.com/symfony/panther) uses php-webdriver and integrates with PHPUnit using `PantherTestCase`
- [Laravel Dusk](https://laravel.com/docs/dusk) is another project using php-webdriver, could be used for testing via `DuskTestCase`
- [Steward](https://github.com/lmc-eu/steward) integrates php-webdriver directly to [PHPUnit](https://phpunit.de/), and provides parallelization
- [Codeception](https://codeception.com/) testing framework provides BDD-layer on top of php-webdriver in its [WebDriver module](https://codeception.com/docs/modules/WebDriver)
- You can also check out this [blogpost](https://codeception.com/11-12-2013/working-with-phpunit-and-selenium-webdriver.html) + [demo project](https://github.com/DavertMik/php-webdriver-demo), describing simple [PHPUnit](https://phpunit.de/) integration
## Support
We have a great community willing to help you!
❓ Do you have a **question, idea or some general feedback**? Visit our [Discussions](https://github.com/php-webdriver/php-webdriver/discussions) page.
(Alternatively, you can [look for many answered questions also on StackOverflow](https://stackoverflow.com/questions/tagged/php+selenium-webdriver)).
🐛 Something isn't working, and you want to **report a bug**? [Submit it here](https://github.com/php-webdriver/php-webdriver/issues/new) as a new issue.
📙 Looking for a **how-to** or **reference documentation**? See [our wiki](https://github.com/php-webdriver/php-webdriver/wiki).
## Contributing ❤️
We love to have your help to make php-webdriver better. See [CONTRIBUTING.md](.github/CONTRIBUTING.md) for more information about contributing and developing php-webdriver.
Php-webdriver is community project - if you want to join the effort with maintaining and developing this library, the best is to look on [issues marked with "help wanted"](https://github.com/php-webdriver/php-webdriver/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22)
label. Let us know in the issue comments if you want to contribute and if you want any guidance, and we will be delighted to help you to prepare your pull request.
+98
View File
@@ -0,0 +1,98 @@
{
"name": "php-webdriver/webdriver",
"description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
"license": "MIT",
"type": "library",
"keywords": [
"webdriver",
"selenium",
"php",
"geckodriver",
"chromedriver"
],
"homepage": "https://github.com/php-webdriver/php-webdriver",
"require": {
"php": "^7.3 || ^8.0",
"ext-curl": "*",
"ext-json": "*",
"ext-zip": "*",
"symfony/polyfill-mbstring": "^1.12",
"symfony/process": "^5.0 || ^6.0 || ^7.0 || ^8.0"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.20.0",
"ondram/ci-detector": "^4.0",
"php-coveralls/php-coveralls": "^2.4",
"php-mock/php-mock-phpunit": "^2.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpunit/phpunit": "^9.3",
"squizlabs/php_codesniffer": "^3.5",
"symfony/var-dumper": "^5.0 || ^6.0 || ^7.0 || ^8.0"
},
"replace": {
"facebook/webdriver": "*"
},
"suggest": {
"ext-simplexml": "For Firefox profile creation"
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"Facebook\\WebDriver\\": "lib/"
},
"files": [
"lib/Exception/TimeoutException.php"
]
},
"autoload-dev": {
"psr-4": {
"Facebook\\WebDriver\\": [
"tests/unit",
"tests/functional"
]
},
"classmap": [
"tests/functional/"
]
},
"config": {
"allow-plugins": {
"ergebnis/composer-normalize": true
},
"sort-packages": true
},
"scripts": {
"post-install-cmd": [
"@composer install --working-dir=tools/php-cs-fixer --no-progress --no-interaction",
"@composer install --working-dir=tools/phpstan --no-progress --no-interaction"
],
"post-update-cmd": [
"@composer update --working-dir=tools/php-cs-fixer --no-progress --no-interaction",
"@composer update --working-dir=tools/phpstan --no-progress --no-interaction"
],
"all": [
"@lint",
"@analyze",
"@test"
],
"analyze": [
"@php tools/phpstan/vendor/bin/phpstan analyze -c phpstan.neon --ansi",
"@php tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff --dry-run -vvv --ansi",
"@php vendor/bin/phpcs --standard=PSR2 --ignore=*.js ./lib/ ./tests/"
],
"fix": [
"@composer normalize",
"@php tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff -vvv || exit 0",
"@php vendor/bin/phpcbf --standard=PSR2 --ignore=*.js ./lib/ ./tests/"
],
"lint": [
"@php vendor/bin/parallel-lint -j 10 ./lib ./tests example.php",
"@composer validate",
"@composer normalize --dry-run"
],
"test": [
"@php vendor/bin/phpunit --colors=always"
]
}
}
@@ -0,0 +1,240 @@
<?php
namespace Facebook\WebDriver;
use Facebook\WebDriver\Exception\InvalidElementStateException;
use Facebook\WebDriver\Exception\NoSuchElementException;
use Facebook\WebDriver\Exception\UnexpectedTagNameException;
use Facebook\WebDriver\Support\XPathEscaper;
/**
* Provides helper methods for checkboxes and radio buttons.
*/
abstract class AbstractWebDriverCheckboxOrRadio implements WebDriverSelectInterface
{
/** @var WebDriverElement */
protected $element;
/** @var string */
protected $type;
/** @var string */
protected $name;
public function __construct(WebDriverElement $element)
{
$tagName = $element->getTagName();
if ($tagName !== 'input') {
throw new UnexpectedTagNameException('input', $tagName);
}
$this->name = $element->getAttribute('name');
if ($this->name === null) {
throw new InvalidElementStateException('The input does not have a "name" attribute.');
}
$this->element = $element;
}
public function getOptions()
{
return $this->getRelatedElements();
}
public function getAllSelectedOptions()
{
$selectedElement = [];
foreach ($this->getRelatedElements() as $element) {
if ($element->isSelected()) {
$selectedElement[] = $element;
if (!$this->isMultiple()) {
return $selectedElement;
}
}
}
return $selectedElement;
}
public function getFirstSelectedOption()
{
foreach ($this->getRelatedElements() as $element) {
if ($element->isSelected()) {
return $element;
}
}
throw new NoSuchElementException(
sprintf('No %s are selected', $this->type === 'radio' ? 'radio buttons' : 'checkboxes')
);
}
public function selectByIndex($index)
{
$this->byIndex($index);
}
public function selectByValue($value)
{
$this->byValue($value);
}
public function selectByVisibleText($text)
{
$this->byVisibleText($text);
}
public function selectByVisiblePartialText($text)
{
$this->byVisibleText($text, true);
}
/**
* Selects or deselects a checkbox or a radio button by its value.
*
* @param string $value
* @param bool $select
* @throws NoSuchElementException
*/
protected function byValue($value, $select = true)
{
$matched = false;
foreach ($this->getRelatedElements($value) as $element) {
$select ? $this->selectOption($element) : $this->deselectOption($element);
if (!$this->isMultiple()) {
return;
}
$matched = true;
}
if (!$matched) {
throw new NoSuchElementException(
sprintf('Cannot locate %s with value: %s', $this->type, $value)
);
}
}
/**
* Selects or deselects a checkbox or a radio button by its index.
*
* @param int $index
* @param bool $select
* @throws NoSuchElementException
*/
protected function byIndex($index, $select = true)
{
$elements = $this->getRelatedElements();
if (!isset($elements[$index])) {
throw new NoSuchElementException(sprintf('Cannot locate %s with index: %d', $this->type, $index));
}
$select ? $this->selectOption($elements[$index]) : $this->deselectOption($elements[$index]);
}
/**
* Selects or deselects a checkbox or a radio button by its visible text.
*
* @param string $text
* @param bool $partial
* @param bool $select
*/
protected function byVisibleText($text, $partial = false, $select = true)
{
foreach ($this->getRelatedElements() as $element) {
$normalizeFilter = sprintf(
$partial ? 'contains(normalize-space(.), %s)' : 'normalize-space(.) = %s',
XPathEscaper::escapeQuotes($text)
);
$xpath = 'ancestor::label';
$xpathNormalize = sprintf('%s[%s]', $xpath, $normalizeFilter);
$id = $element->getAttribute('id');
if ($id !== null) {
$idFilter = sprintf('@for = %s', XPathEscaper::escapeQuotes($id));
$xpath .= sprintf(' | //label[%s]', $idFilter);
$xpathNormalize .= sprintf(' | //label[%s and %s]', $idFilter, $normalizeFilter);
}
try {
$element->findElement(WebDriverBy::xpath($xpathNormalize));
} catch (NoSuchElementException $e) {
if ($partial) {
continue;
}
try {
// Since the mechanism of getting the text in xpath is not the same as
// webdriver, use the expensive getText() to check if nothing is matched.
if ($text !== $element->findElement(WebDriverBy::xpath($xpath))->getText()) {
continue;
}
} catch (NoSuchElementException $e) {
continue;
}
}
$select ? $this->selectOption($element) : $this->deselectOption($element);
if (!$this->isMultiple()) {
return;
}
}
}
/**
* Gets checkboxes or radio buttons with the same name.
*
* @param string|null $value
* @return WebDriverElement[]
*/
protected function getRelatedElements($value = null)
{
$valueSelector = $value ? sprintf(' and @value = %s', XPathEscaper::escapeQuotes($value)) : '';
$formId = $this->element->getAttribute('form');
if ($formId === null) {
$form = $this->element->findElement(WebDriverBy::xpath('ancestor::form'));
$formId = $form->getAttribute('id');
if ($formId === '' || $formId === null) {
return $form->findElements(WebDriverBy::xpath(
sprintf('.//input[@name = %s%s]', XPathEscaper::escapeQuotes($this->name), $valueSelector)
));
}
}
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#form
return $this->element->findElements(
WebDriverBy::xpath(sprintf(
'//form[@id = %1$s]//input[@name = %2$s%3$s'
. ' and ((boolean(@form) = true() and @form = %1$s) or boolean(@form) = false())]'
. ' | //input[@form = %1$s and @name = %2$s%3$s]',
XPathEscaper::escapeQuotes($formId),
XPathEscaper::escapeQuotes($this->name),
$valueSelector
))
);
}
/**
* Selects a checkbox or a radio button.
*/
protected function selectOption(WebDriverElement $element)
{
if (!$element->isSelected()) {
$element->click();
}
}
/**
* Deselects a checkbox or a radio button.
*/
protected function deselectOption(WebDriverElement $element)
{
if ($element->isSelected()) {
$element->click();
}
}
}
@@ -0,0 +1,46 @@
<?php
namespace Facebook\WebDriver\Chrome;
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* Provide access to Chrome DevTools Protocol (CDP) commands via HTTP endpoint of Chromedriver.
*
* @see https://chromedevtools.github.io/devtools-protocol/
*/
class ChromeDevToolsDriver
{
public const SEND_COMMAND = [
'method' => 'POST',
'url' => '/session/:sessionId/goog/cdp/execute',
];
/**
* @var RemoteWebDriver
*/
private $driver;
public function __construct(RemoteWebDriver $driver)
{
$this->driver = $driver;
}
/**
* Executes a Chrome DevTools command
*
* @param string $command The DevTools command to execute
* @param array $parameters Optional parameters to the command
* @return array The result of the command
*/
public function execute($command, array $parameters = [])
{
$params = ['cmd' => $command, 'params' => (object) $parameters];
return $this->driver->executeCustomCommand(
self::SEND_COMMAND['url'],
self::SEND_COMMAND['method'],
$params
);
}
}
@@ -0,0 +1,107 @@
<?php
namespace Facebook\WebDriver\Chrome;
use Facebook\WebDriver\Local\LocalWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\Service\DriverCommandExecutor;
use Facebook\WebDriver\Remote\WebDriverCommand;
class ChromeDriver extends LocalWebDriver
{
/** @var ChromeDevToolsDriver */
private $devTools;
/**
* Creates a new ChromeDriver using default configuration.
* This includes starting a new chromedriver process each time this method is called. However this may be
* unnecessary overhead - instead, you can start the process once using ChromeDriverService and pass
* this instance to startUsingDriverService() method.
*
* @todo Remove $service parameter. Use `ChromeDriver::startUsingDriverService` to pass custom $service instance.
* @return static
*/
public static function start(
?DesiredCapabilities $desired_capabilities = null,
?ChromeDriverService $service = null
) {
if ($service === null) { // TODO: Remove the condition (always create default service)
$service = ChromeDriverService::createDefaultService();
}
return static::startUsingDriverService($service, $desired_capabilities);
}
/**
* Creates a new ChromeDriver using given ChromeDriverService.
* This is usable when you for example don't want to start new chromedriver process for each individual test
* and want to reuse the already started chromedriver, which will lower the overhead associated with spinning up
* a new process.
* @return static
*/
public static function startUsingDriverService(
ChromeDriverService $service,
?DesiredCapabilities $capabilities = null
) {
if ($capabilities === null) {
$capabilities = DesiredCapabilities::chrome();
}
$executor = new DriverCommandExecutor($service);
$newSessionCommand = WebDriverCommand::newSession(
[
'capabilities' => [
'firstMatch' => [(object) $capabilities->toW3cCompatibleArray()],
],
'desiredCapabilities' => (object) $capabilities->toArray(),
]
);
$response = $executor->execute($newSessionCommand);
/*
* TODO: in next major version we may not need to use this method, because without OSS compatibility the
* driver creation is straightforward.
*/
return static::createFromResponse($response, $executor);
}
/**
* @todo Remove in next major version. The class is internally no longer used and is kept only to keep BC.
* @deprecated Use start or startUsingDriverService method instead.
* @codeCoverageIgnore
* @internal
*/
public function startSession(DesiredCapabilities $desired_capabilities)
{
$command = WebDriverCommand::newSession(
[
'capabilities' => [
'firstMatch' => [(object) $desired_capabilities->toW3cCompatibleArray()],
],
'desiredCapabilities' => (object) $desired_capabilities->toArray(),
]
);
$response = $this->executor->execute($command);
$value = $response->getValue();
if (!$this->isW3cCompliant = isset($value['capabilities'])) {
$this->executor->disableW3cCompliance();
}
$this->sessionID = $response->getSessionID();
}
/**
* @return ChromeDevToolsDriver
*/
public function getDevTools()
{
if ($this->devTools === null) {
$this->devTools = new ChromeDevToolsDriver($this);
}
return $this->devTools;
}
}
@@ -0,0 +1,37 @@
<?php
namespace Facebook\WebDriver\Chrome;
use Facebook\WebDriver\Remote\Service\DriverService;
class ChromeDriverService extends DriverService
{
/**
* The environment variable storing the path to the chrome driver executable.
* @deprecated Use ChromeDriverService::CHROME_DRIVER_EXECUTABLE
*/
public const CHROME_DRIVER_EXE_PROPERTY = 'webdriver.chrome.driver';
/** @var string The environment variable storing the path to the chrome driver executable */
public const CHROME_DRIVER_EXECUTABLE = 'WEBDRIVER_CHROME_DRIVER';
/**
* @var string Default executable used when no other is provided
* @internal
*/
public const DEFAULT_EXECUTABLE = 'chromedriver';
/**
* @return static
*/
public static function createDefaultService()
{
$pathToExecutable = getenv(self::CHROME_DRIVER_EXECUTABLE) ?: getenv(self::CHROME_DRIVER_EXE_PROPERTY);
if ($pathToExecutable === false || $pathToExecutable === '') {
$pathToExecutable = static::DEFAULT_EXECUTABLE;
}
$port = 9515; // TODO: Get another port if the default port is used.
$args = ['--port=' . $port];
return new static($pathToExecutable, $port, $args);
}
}
@@ -0,0 +1,182 @@
<?php
namespace Facebook\WebDriver\Chrome;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use JsonSerializable;
use ReturnTypeWillChange;
/**
* The class manages the capabilities in ChromeDriver.
*
* @see https://sites.google.com/chromium.org/driver/capabilities
*/
class ChromeOptions implements JsonSerializable
{
/**
* The key of chromeOptions in desired capabilities
*/
public const CAPABILITY = 'goog:chromeOptions';
/**
* @deprecated Use CAPABILITY instead
*/
public const CAPABILITY_W3C = self::CAPABILITY;
/**
* @var array
*/
private $arguments = [];
/**
* @var string
*/
private $binary = '';
/**
* @var array
*/
private $extensions = [];
/**
* @var array
*/
private $experimentalOptions = [];
/**
* Return a version of the class which can JSON serialized.
*
* @return array
*/
#[ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->toArray();
}
/**
* Sets the path of the Chrome executable. The path should be either absolute
* or relative to the location running ChromeDriver server.
*
* @param string $path
* @return ChromeOptions
*/
public function setBinary($path)
{
$this->binary = $path;
return $this;
}
/**
* @return ChromeOptions
*/
public function addArguments(array $arguments)
{
$this->arguments = array_merge($this->arguments, $arguments);
return $this;
}
/**
* Add a Chrome extension to install on browser startup. Each path should be
* a packed Chrome extension.
*
* @return ChromeOptions
*/
public function addExtensions(array $paths)
{
foreach ($paths as $path) {
$this->addExtension($path);
}
return $this;
}
/**
* @param array $encoded_extensions An array of base64 encoded of the extensions.
* @return ChromeOptions
*/
public function addEncodedExtensions(array $encoded_extensions)
{
foreach ($encoded_extensions as $encoded_extension) {
$this->addEncodedExtension($encoded_extension);
}
return $this;
}
/**
* Sets an experimental option which has not exposed officially.
*
* When using "prefs" to set Chrome preferences, please be aware they are so far not supported by
* Chrome running in headless mode, see https://bugs.chromium.org/p/chromium/issues/detail?id=775911
*
* @param string $name
* @param mixed $value
* @return ChromeOptions
*/
public function setExperimentalOption($name, $value)
{
$this->experimentalOptions[$name] = $value;
return $this;
}
/**
* @return DesiredCapabilities The DesiredCapabilities for Chrome with this options.
*/
public function toCapabilities()
{
$capabilities = DesiredCapabilities::chrome();
$capabilities->setCapability(self::CAPABILITY, $this);
return $capabilities;
}
/**
* @return \ArrayObject|array
*/
public function toArray()
{
// The selenium server expects a 'dictionary' instead of a 'list' when
// reading the chrome option. However, an empty array in PHP will be
// converted to a 'list' instead of a 'dictionary'. To fix it, we work
// with `ArrayObject`
$options = new \ArrayObject($this->experimentalOptions);
if (!empty($this->binary)) {
$options['binary'] = $this->binary;
}
if (!empty($this->arguments)) {
$options['args'] = $this->arguments;
}
if (!empty($this->extensions)) {
$options['extensions'] = $this->extensions;
}
return $options;
}
/**
* Add a Chrome extension to install on browser startup. Each path should be a
* packed Chrome extension.
*
* @param string $path
* @return ChromeOptions
*/
private function addExtension($path)
{
$this->addEncodedExtension(base64_encode(file_get_contents($path)));
return $this;
}
/**
* @param string $encoded_extension Base64 encoded of the extension.
* @return ChromeOptions
*/
private function addEncodedExtension($encoded_extension)
{
$this->extensions[] = $encoded_extension;
return $this;
}
}
+278
View File
@@ -0,0 +1,278 @@
<?php
namespace Facebook\WebDriver;
use Facebook\WebDriver\Exception\Internal\LogicException;
/**
* Set values of an cookie.
*
* Implements ArrayAccess for backwards compatibility.
*
* @see https://w3c.github.io/webdriver/#cookies
*/
class Cookie implements \ArrayAccess
{
/** @var array */
protected $cookie = [];
/**
* @param string $name The name of the cookie; may not be null or an empty string.
* @param string $value The cookie value; may not be null.
*/
public function __construct($name, $value)
{
$this->validateCookieName($name);
$this->validateCookieValue($value);
$this->cookie['name'] = $name;
$this->cookie['value'] = $value;
}
/**
* @param array $cookieArray The cookie fields; must contain name and value.
* @return Cookie
*/
public static function createFromArray(array $cookieArray)
{
if (!isset($cookieArray['name'])) {
throw LogicException::forError('Cookie name should be set');
}
if (!isset($cookieArray['value'])) {
throw LogicException::forError('Cookie value should be set');
}
$cookie = new self($cookieArray['name'], $cookieArray['value']);
if (isset($cookieArray['path'])) {
$cookie->setPath($cookieArray['path']);
}
if (isset($cookieArray['domain'])) {
$cookie->setDomain($cookieArray['domain']);
}
if (isset($cookieArray['expiry'])) {
$cookie->setExpiry($cookieArray['expiry']);
}
if (isset($cookieArray['secure'])) {
$cookie->setSecure($cookieArray['secure']);
}
if (isset($cookieArray['httpOnly'])) {
$cookie->setHttpOnly($cookieArray['httpOnly']);
}
if (isset($cookieArray['sameSite'])) {
$cookie->setSameSite($cookieArray['sameSite']);
}
return $cookie;
}
/**
* @return string
*/
public function getName()
{
return $this->offsetGet('name');
}
/**
* @return string
*/
public function getValue()
{
return $this->offsetGet('value');
}
/**
* The path the cookie is visible to. Defaults to "/" if omitted.
*
* @param string $path
*/
public function setPath($path)
{
$this->offsetSet('path', $path);
}
/**
* @return string|null
*/
public function getPath()
{
return $this->offsetGet('path');
}
/**
* The domain the cookie is visible to. Defaults to the current browsing context's document's URL domain if omitted.
*
* @param string $domain
*/
public function setDomain($domain)
{
if (mb_strpos($domain, ':') !== false) {
throw LogicException::forError(sprintf('Cookie domain "%s" should not contain a port', $domain));
}
$this->offsetSet('domain', $domain);
}
/**
* @return string|null
*/
public function getDomain()
{
return $this->offsetGet('domain');
}
/**
* The cookie's expiration date, specified in seconds since Unix Epoch.
*
* @param int $expiry
*/
public function setExpiry($expiry)
{
$this->offsetSet('expiry', (int) $expiry);
}
/**
* @return int|null
*/
public function getExpiry()
{
return $this->offsetGet('expiry');
}
/**
* Whether this cookie requires a secure connection (https). Defaults to false if omitted.
*
* @param bool $secure
*/
public function setSecure($secure)
{
$this->offsetSet('secure', $secure);
}
/**
* @return bool|null
*/
public function isSecure()
{
return $this->offsetGet('secure');
}
/**
* Whether the cookie is an HTTP only cookie. Defaults to false if omitted.
*
* @param bool $httpOnly
*/
public function setHttpOnly($httpOnly)
{
$this->offsetSet('httpOnly', $httpOnly);
}
/**
* @return bool|null
*/
public function isHttpOnly()
{
return $this->offsetGet('httpOnly');
}
/**
* The cookie's same-site value.
*
* @param string $sameSite
*/
public function setSameSite($sameSite)
{
$this->offsetSet('sameSite', $sameSite);
}
/**
* @return string|null
*/
public function getSameSite()
{
return $this->offsetGet('sameSite');
}
/**
* @return array
*/
public function toArray()
{
$cookie = $this->cookie;
if (!isset($cookie['secure'])) {
// Passing a boolean value for the "secure" flag is mandatory when using geckodriver
$cookie['secure'] = false;
}
return $cookie;
}
/**
* @param mixed $offset
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return isset($this->cookie[$offset]);
}
/**
* @param mixed $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->offsetExists($offset) ? $this->cookie[$offset] : null;
}
/**
* @param mixed $offset
* @param mixed $value
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value)
{
if ($value === null) {
unset($this->cookie[$offset]);
} else {
$this->cookie[$offset] = $value;
}
}
/**
* @param mixed $offset
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
unset($this->cookie[$offset]);
}
/**
* @param string $name
*/
protected function validateCookieName($name)
{
if ($name === null || $name === '') {
throw LogicException::forError('Cookie name should be non-empty');
}
if (mb_strpos($name, ';') !== false) {
throw LogicException::forError('Cookie name should not contain a ";"');
}
}
/**
* @param string $value
*/
protected function validateCookieValue($value)
{
if ($value === null) {
throw LogicException::forError('Cookie value is required when setting a cookie');
}
}
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* A command failed because the referenced shadow root is no longer attached to the DOM.
*/
class DetachedShadowRootException extends WebDriverException
{
}
@@ -0,0 +1,11 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* The Element Click command could not be completed because the element receiving the events is obscuring the element
* that was requested clicked.
*/
class ElementClickInterceptedException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* A command could not be completed because the element is not pointer- or keyboard interactable.
*/
class ElementNotInteractableException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* @deprecated Use Facebook\WebDriver\Exception\ElementNotInteractableException
*/
class ElementNotSelectableException extends ElementNotInteractableException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
*/
class ElementNotVisibleException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
*/
class ExpectedException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
*/
class IMEEngineActivationFailedException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
*/
class IMENotAvailableException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
*/
class IndexOutOfBoundsException extends WebDriverException
{
}
@@ -0,0 +1,11 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* Navigation caused the user agent to hit a certificate warning, which is usually the result of an expired
* or invalid TLS certificate.
*/
class InsecureCertificateException extends WebDriverException
{
}
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
namespace Facebook\WebDriver\Exception\Internal;
use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface;
/**
* The driver server process is unexpectedly no longer available.
*/
class DriverServerDiedException extends \RuntimeException implements PhpWebDriverExceptionInterface
{
public function __construct(?\Exception $previous = null)
{
parent::__construct('The driver server has died.', 0, $previous);
}
}
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);
namespace Facebook\WebDriver\Exception\Internal;
use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface;
/**
* Exception class thrown when a filesystem related operation failure happens.
*/
class IOException extends \LogicException implements PhpWebDriverExceptionInterface
{
public static function forFileError(string $message, string $path): self
{
return new self(sprintf($message . ' ("%s")', $path));
}
}
@@ -0,0 +1,29 @@
<?php declare(strict_types=1);
namespace Facebook\WebDriver\Exception\Internal;
use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface;
/**
* Exception thrown when error in program logic occurs. This includes invalid domain data and unexpected data states.
*/
class LogicException extends \LogicException implements PhpWebDriverExceptionInterface
{
public static function forError(string $message): self
{
return new self($message);
}
public static function forInvalidHttpMethod(string $url, string $httpMethod, array $params): self
{
return new self(
sprintf(
'The http method called for "%s" is "%s", but it has to be POST' .
' if you want to pass the JSON params %s',
$url,
$httpMethod,
json_encode($params)
)
);
}
}
@@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace Facebook\WebDriver\Exception\Internal;
use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface;
use Symfony\Component\Process\Process;
/**
* Exception thrown if an error which can only be found on runtime occurs.
*/
class RuntimeException extends \RuntimeException implements PhpWebDriverExceptionInterface
{
public static function forError(string $message): self
{
return new self($message);
}
public static function forDriverError(Process $process): self
{
return new self(
sprintf(
'Error starting driver executable "%s": %s',
$process->getCommandLine(),
$process->getErrorOutput()
)
);
}
}
@@ -0,0 +1,51 @@
<?php declare(strict_types=1);
namespace Facebook\WebDriver\Exception\Internal;
use Facebook\WebDriver\Exception\PhpWebDriverExceptionInterface;
/**
* Exception thrown on invalid or unexpected server response.
*/
class UnexpectedResponseException extends \RuntimeException implements PhpWebDriverExceptionInterface
{
public static function forError(string $message): self
{
return new self($message);
}
public static function forElementNotArray($response): self
{
return new self(
sprintf(
"Unexpected server response for getting an element. Expected array, but the response was: '%s'\n",
print_r($response, true)
)
);
}
public static function forJsonDecodingError(int $jsonLastError, string $rawResults): self
{
return new self(
sprintf(
"JSON decoding of remote response failed.\n" .
"Error code: %d\n" .
"The response: '%s'\n",
$jsonLastError,
$rawResults
)
);
}
public static function forCapabilitiesRetrievalError(\Exception $previousException): self
{
return new self(
sprintf(
'Existing Capabilities were not provided, and they also cannot be read from Selenium Grid'
. ' (error: "%s"). You are probably not using Selenium Grid, so to reuse the previous session,'
. ' Capabilities must be explicitly provided to createBySessionID() method.',
$previousException->getMessage()
)
);
}
}
@@ -0,0 +1,22 @@
<?php
namespace Facebook\WebDriver\Exception\Internal;
/**
* @deprecated To be replaced with UnexpectedResponseException in 2.0
*/
class WebDriverCurlException extends UnexpectedResponseException
{
public static function forCurlError(string $httpMethod, string $url, string $curlError, ?array $params): self
{
$message = sprintf('Curl error thrown for http %s to %s', $httpMethod, $url);
if (!empty($params)) {
$message .= sprintf(' with params: %s', json_encode($params, JSON_UNESCAPED_SLASHES));
}
$message .= "\n\n" . $curlError;
return new self($message);
}
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* The arguments passed to a command are either invalid or malformed.
*/
class InvalidArgumentException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* An illegal attempt was made to set a cookie under a different domain than the current page.
*/
class InvalidCookieDomainException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* @deprecated Removed in W3C WebDriver, see https://github.com/php-webdriver/php-webdriver/pull/686
*/
class InvalidCoordinatesException extends WebDriverException
{
}
@@ -0,0 +1,11 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* A command could not be completed because the element is in an invalid state, e.g. attempting to clear an element
* that isnt both editable and resettable.
*/
class InvalidElementStateException extends WebDriverException
{
}
@@ -0,0 +1,10 @@
<?php
namespace Facebook\WebDriver\Exception;
/**
* Argument was an invalid selector.
*/
class InvalidSelectorException extends WebDriverException
{
}

Some files were not shown because too many files have changed in this diff Show More