Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти
Інше
Події Про нас

Laravel Dusk

Вступ

Pest 4 тепер містить автоматизоване тестування у браузері, яке суттєво виграє в продуктивності та зручності порівняно з Laravel Dusk. Для нових проєктів ми рекомендуємо тестувати у браузері саме через Pest.

Laravel Dusk надає виразний і простий у користуванні API для автоматизації браузера й тестування. За замовчуванням Dusk не вимагає встановлювати JDK чи Selenium на ваш комп'ютер. Натомість Dusk використовує окремо встановлений ChromeDriver. Проте ви вільні скористатися будь-яким іншим сумісним із Selenium драйвером.

Встановлення

Для початку встановіть Google Chrome і додайте до свого проєкту Composer-залежність laravel/dusk:

composer require laravel/dusk --dev

Якщо ви реєструєте сервіс-провайдер Dusk вручну, ніколи не реєструйте його у продакшн-середовищі: це може призвести до того, що будь-хто зможе автентифікуватися у вашому застосунку.

Після встановлення пакета Dusk виконайте артизан-команду dusk:install. Команда dusk:install створить каталог tests/Browser, приклад тесту Dusk і встановить бінарник Chrome Driver для вашої операційної системи:

php artisan dusk:install

Далі задайте змінну оточення APP_URL у файлі .env вашого застосунку. Це значення має збігатися з URL, за яким ви звертаєтеся до застосунку у браузері.

Якщо ви користуєтеся Laravel Sail для керування локальним середовищем розробки, зверніться також до документації Sail щодо налаштування й запуску тестів Dusk.

Керування встановленням ChromeDriver

Якщо ви хочете встановити версію ChromeDriver, відмінну від тієї, яку встановлює Laravel Dusk командою dusk:install, скористайтеся командою dusk:chrome-driver:

# Install the latest version of ChromeDriver for your OS...
php artisan dusk:chrome-driver

# Install a given version of ChromeDriver for your OS...
php artisan dusk:chrome-driver 86

# Install a given version of ChromeDriver for all supported OSs...
php artisan dusk:chrome-driver --all

# Install the version of ChromeDriver that matches the detected version of Chrome / Chromium for your OS...
php artisan dusk:chrome-driver --detect

Dusk вимагає, щоб бінарники chromedriver були виконуваними. Якщо у вас виникають проблеми із запуском Dusk, переконайтеся, що бінарники виконувані, такою командою: chmod -R 0755 vendor/laravel/dusk/bin/.

Використання інших браузерів

За замовчуванням Dusk запускає ваші браузерні тести в Google Chrome через окремо встановлений ChromeDriver. Проте ви можете запустити власний сервер Selenium і виконувати тести в будь-якому браузері.

Для початку відкрийте файл tests/DuskTestCase.php - це базовий тест-кейс Dusk для вашого застосунку. У ньому ви можете прибрати виклик методу startChromeDriver. Це зупинить автоматичний запуск ChromeDriver у Dusk:

/**
 * Prepare for Dusk test execution.
 *
 * @beforeClass
 */
public static function prepare(): void
{
    // static::startChromeDriver();
}

Далі ви можете змінити метод driver, щоб підключитися до потрібних вам URL і порту. Крім того, ви можете змінити «бажані можливості» (desired capabilities), які передаються до WebDriver:

use Facebook\WebDriver\Remote\RemoteWebDriver;

/**
 * Create the RemoteWebDriver instance.
 */
protected function driver(): RemoteWebDriver
{
    return RemoteWebDriver::create(
        'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
    );
}

Перші кроки

Генерування тестів

Щоб згенерувати тест Dusk, скористайтеся артизан-командою dusk:make. Згенерований тест потрапить до каталогу tests/Browser:

php artisan dusk:make LoginTest

Скидання бази даних після кожного тесту

Більшість ваших тестів працюватимуть зі сторінками, які отримують дані з бази даних застосунку; проте ваші тести Dusk ніколи не повинні використовувати трейт RefreshDatabase. Трейт RefreshDatabase спирається на транзакції бази даних, які не застосовні й недоступні між HTTP-запитами. Натомість у вас є два варіанти: трейт DatabaseMigrations і трейт DatabaseTruncation.

Використання міграцій бази даних

Трейт DatabaseMigrations запускатиме ваші міграції перед кожним тестом. Проте видалення й повторне створення таблиць для кожного тесту зазвичай повільніше за їх очищення:

<?php

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;

pest()->use(DatabaseMigrations::class);

//
<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;

    //
}

Бази даних SQLite в пам'яті не можна використовувати під час прогону тестів Dusk. Оскільки браузер виконується у власному процесі, він не матиме доступу до баз даних у пам'яті інших процесів.

Використання очищення бази даних

Трейт DatabaseTruncation мігрує вашу базу даних на першому тесті, щоб переконатися, що таблиці створено належним чином. Проте в наступних тестах таблиці бази просто очищатимуться - це швидше, ніж щоразу проганяти всі міграції:

<?php

use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;

pest()->use(DatabaseTruncation::class);

//
<?php

namespace Tests\Browser;

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseTruncation;

    //
}

За замовчуванням цей трейт очищає всі таблиці, окрім migrations. Якщо ви хочете вказати, які саме таблиці слід очищати, визначте у своєму тестовому класі властивість $tablesToTruncate:

Якщо ви користуєтеся Pest, визначайте властивості чи методи в базовому класі DuskTestCase або в будь-якому класі, який успадковує ваш тестовий файл.

/**
 * Indicates which tables should be truncated.
 *
 * @var array
 */
protected $tablesToTruncate = ['users'];

Або ж ви можете визначити у своєму тестовому класі властивість $exceptTables, щоб указати, які таблиці слід виключити з очищення:

/**
 * Indicates which tables should be excluded from truncation.
 *
 * @var array
 */
protected $exceptTables = ['users'];

Щоб указати підключення до баз даних, чиї таблиці слід очищати, визначте у своєму тестовому класі властивість $connectionsToTruncate:

/**
 * Indicates which connections should have their tables truncated.
 *
 * @var array
 */
protected $connectionsToTruncate = ['mysql'];

Якщо ви хочете виконати код до або після очищення бази даних, визначте у своєму тестовому класі методи beforeTruncatingDatabase чи afterTruncatingDatabase:

/**
 * Perform any work that should take place before the database has started truncating.
 */
protected function beforeTruncatingDatabase(): void
{
    //
}

/**
 * Perform any work that should take place after the database has finished truncating.
 */
protected function afterTruncatingDatabase(): void
{
    //
}

Запуск тестів

Щоб запустити ваші браузерні тести, виконайте артизан-команду dusk:

php artisan dusk

Якщо під час останнього запуску команди dusk у вас були провалені тести, ви можете заощадити час, спершу перезапустивши саме їх командою dusk:fails:

php artisan dusk:fails

Команда dusk приймає будь-який аргумент, який зазвичай приймає раннер тестів Pest / PHPUnit, - наприклад, щоб запустити лише тести певної групи:

php artisan dusk --group=foo

Якщо ви користуєтеся Laravel Sail для керування локальним середовищем розробки, зверніться до документації Sail щодо налаштування й запуску тестів Dusk.

Ручний запуск ChromeDriver

За замовчуванням Dusk автоматично намагається запустити ChromeDriver. Якщо у вашій системі це не працює, ви можете запустити ChromeDriver вручну перед виконанням команди dusk. Якщо ви вирішите запускати ChromeDriver вручну, закоментуйте такий рядок у файлі tests/DuskTestCase.php:

/**
 * Prepare for Dusk test execution.
 *
 * @beforeClass
 */
public static function prepare(): void
{
    // static::startChromeDriver();
}

Крім того, якщо ви запускаєте ChromeDriver на порту, відмінному від 9515, змініть метод driver того самого класу, щоб указати правильний порт:

use Facebook\WebDriver\Remote\RemoteWebDriver;

/**
 * Create the RemoteWebDriver instance.
 */
protected function driver(): RemoteWebDriver
{
    return RemoteWebDriver::create(
        'http://localhost:9515', DesiredCapabilities::chrome()
    );
}

Робота із середовищем

Щоб змусити Dusk використовувати власний файл оточення під час прогону тестів, створіть у корені проєкту файл .env.dusk.{environment}. Наприклад, якщо ви запускатимете команду dusk із середовища local, створіть файл .env.dusk.local.

Під час прогону тестів Dusk створить резервну копію вашого файлу .env і перейменує ваше оточення Dusk на .env. Коли тести завершаться, ваш файл .env буде відновлено.

Основи роботи з браузером

Створення браузерів

Для початку напишімо тест, який перевіряє, що ми можемо увійти до нашого застосунку. Згенерувавши тест, ми можемо змінити його так, щоб перейти на сторінку входу, ввести облікові дані й натиснути кнопку «Login». Щоб створити екземпляр браузера, викличте метод browse усередині вашого тесту Dusk:

<?php

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;

pest()->use(DatabaseMigrations::class);

test('basic example', function () {
    $user = User::factory()->create([
        'email' => 'taylor@laravel.com',
    ]);

    $this->browse(function (Browser $browser) use ($user) {
        $browser->visit('/login')
            ->type('email', $user->email)
            ->type('password', 'password')
            ->press('Login')
            ->assertPathIs('/home');
    });
});
<?php

namespace Tests\Browser;

use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;

    /**
     * A basic browser test example.
     */
    public function test_basic_example(): void
    {
        $user = User::factory()->create([
            'email' => 'taylor@laravel.com',
        ]);

        $this->browse(function (Browser $browser) use ($user) {
            $browser->visit('/login')
                ->type('email', $user->email)
                ->type('password', 'password')
                ->press('Login')
                ->assertPathIs('/home');
        });
    }
}

Як видно з прикладу вище, метод browse приймає замикання. Dusk автоматично передасть до нього екземпляр браузера - це головний об'єкт для взаємодії з вашим застосунком і тверджень щодо нього.

Створення кількох браузерів

Іноді для належного виконання тесту вам може знадобитися кілька браузерів. Наприклад, вони можуть знадобитися, щоб протестувати екран чату, який працює через вебсокети. Щоб створити кілька браузерів, просто додайте більше аргументів-браузерів до сигнатури замикання, переданого методу browse:

$this->browse(function (Browser $first, Browser $second) {
    $first->loginAs(User::find(1))
        ->visit('/home')
        ->waitForText('Message');

    $second->loginAs(User::find(2))
        ->visit('/home')
        ->waitForText('Message')
        ->type('message', 'Hey Taylor')
        ->press('Send');

    $first->waitForText('Hey Taylor')
        ->assertSee('Jeffrey Way');
});

Метод visit дозволяє перейти до заданого URI у вашому застосунку:

$browser->visit('/login');

Метод visitRoute дозволяє перейти до іменованого маршруту:

$browser->visitRoute($routeName, $parameters);

Ви можете переходити «назад» і «вперед» методами back та forward:

$browser->back();

$browser->forward();

Метод refresh дозволяє оновити сторінку:

$browser->refresh();

Зміна розміру вікна браузера

Метод resize дозволяє змінити розмір вікна браузера:

$browser->resize(1920, 1080);

Метод maximize дозволяє розгорнути вікно браузера на весь екран:

$browser->maximize();

Метод fitContent змінить розмір вікна браузера так, щоб він відповідав розміру вмісту:

$browser->fitContent();

Коли тест провалюється, Dusk автоматично змінює розмір браузера під вміст перед знімком екрана. Ви можете вимкнути цю поведінку, викликавши у своєму тесті метод disableFitOnFailure:

$browser->disableFitOnFailure();

Метод move дозволяє перемістити вікно браузера в інше місце екрана:

$browser->move($x = 100, $y = 100);

Макроси браузера

Якщо ви хочете визначити власний метод браузера, який можна перевикористовувати в різних тестах, скористайтеся методом macro класу Browser. Зазвичай цей метод викликають у методі boot сервіс-провайдера:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Browser;

class DuskServiceProvider extends ServiceProvider
{
    /**
     * Register Dusk's browser macros.
     */
    public function boot(): void
    {
        Browser::macro('scrollToElement', function (string $element = null) {
            $this->script("$('html, body').animate({ scrollTop: $('$element').offset().top }, 0);");

            return $this;
        });
    }
}

Функція macro приймає першим аргументом ім'я, а другим - замикання. Замикання макроса виконається, коли ви викличете макрос як метод на екземплярі Browser:

$this->browse(function (Browser $browser) use ($user) {
    $browser->visit('/pay')
        ->scrollToElement('#credit-card-details')
        ->assertSee('Enter Credit Card Details');
});

Автентифікація

Часто ви тестуватимете сторінки, які вимагають автентифікації. Щоб не взаємодіяти з екраном входу вашого застосунку в кожному тесті, скористайтеся методом Dusk loginAs. Метод loginAs приймає первинний ключ вашої моделі, придатної до автентифікації, або сам її екземпляр:

use App\Models\User;
use Laravel\Dusk\Browser;

$this->browse(function (Browser $browser) {
    $browser->loginAs(User::find(1))
        ->visit('/home');
});

Після використання методу loginAs сесія користувача зберігатиметься для всіх тестів у межах файлу.

Cookie

Метод cookie дозволяє отримати чи задати значення зашифрованого cookie. За замовчуванням усі створені Laravel cookie зашифровані:

$browser->cookie('name');

$browser->cookie('name', 'Taylor');

Метод plainCookie дозволяє отримати чи задати значення незашифрованого cookie:

$browser->plainCookie('name');

$browser->plainCookie('name', 'Taylor');

Метод deleteCookie дозволяє видалити заданий cookie:

$browser->deleteCookie('name');

Виконання JavaScript

Метод script дозволяє виконати у браузері довільні інструкції JavaScript:

$browser->script('document.documentElement.scrollTop = 0');

$browser->script([
    'document.body.scrollTop = 0',
    'document.documentElement.scrollTop = 0',
]);

$output = $browser->script('return window.location.pathname');

Знімок екрана

Метод screenshot дозволяє зробити знімок екрана й зберегти його із заданим іменем файлу. Усі знімки зберігаються в каталозі tests/Browser/screenshots:

$browser->screenshot('filename');

Метод responsiveScreenshots дозволяє зробити серію знімків на різних брейкпоїнтах:

$browser->responsiveScreenshots('filename');

Метод screenshotElement дозволяє зробити знімок конкретного елемента на сторінці:

$browser->screenshotElement('#selector', 'filename');

Збереження виводу консолі на диск

Метод storeConsoleLog дозволяє записати вивід консолі поточного браузера на диск під заданим іменем файлу. Вивід консолі зберігається в каталозі tests/Browser/console:

$browser->storeConsoleLog('filename');

Збереження коду сторінки на диск

Метод storeSource дозволяє записати код поточної сторінки на диск під заданим іменем файлу. Код сторінки зберігається в каталозі tests/Browser/source:

$browser->storeSource('filename');

Взаємодія з елементами

Селектори Dusk

Вибір хороших CSS-селекторів для взаємодії з елементами - одна з найскладніших частин написання тестів Dusk. З часом зміни у фронтенді можуть зламати ваші тести через CSS-селектори на кшталт такого:

// HTML...

<button>Login</button>
// Test...

$browser->click('.login-page .container div > button');

Селектори Dusk дозволяють зосередитися на написанні ефективних тестів, а не на запам'ятовуванні CSS-селекторів. Щоб визначити селектор, додайте до свого HTML-елемента атрибут dusk. Далі, взаємодіючи з браузером Dusk, додайте до селектора префікс @, щоб працювати з цим елементом у тесті:

// HTML...

<button dusk="login-button">Login</button>
// Test...

$browser->click('@login-button');

За бажання ви можете змінити HTML-атрибут, який використовує селектор Dusk, методом selectorHtmlAttribute. Зазвичай цей метод викликають у методі boot вашого AppServiceProvider:

use Laravel\Dusk\Dusk;

Dusk::selectorHtmlAttribute('data-dusk');

Текст, значення та атрибути

Отримання та задавання значень

Dusk надає кілька методів для роботи з поточним значенням, видимим текстом та атрибутами елементів на сторінці. Наприклад, щоб отримати «значення» елемента, який відповідає заданому CSS- чи Dusk-селектору, скористайтеся методом value:

// Retrieve the value...
$value = $browser->value('selector');

// Set the value...
$browser->value('selector', 'value');

Метод inputValue дозволяє отримати «значення» елемента input із заданим іменем поля:

$value = $browser->inputValue('field');

Отримання тексту

Метод text дозволяє отримати видимий текст елемента, який відповідає заданому селектору:

$text = $browser->text('selector');

Отримання атрибутів

Нарешті, метод attribute дозволяє отримати значення атрибута елемента, який відповідає заданому селектору:

$attribute = $browser->attribute('selector', 'value');

Робота з формами

Введення значень

Dusk надає різні методи для роботи з формами й полями введення. Спершу погляньмо на приклад введення тексту в поле:

$browser->type('email', 'taylor@laravel.com');

Зверніть увагу: хоча метод за потреби приймає CSS-селектор, передавати його до методу type не обов'язково. Якщо CSS-селектор не задано, Dusk шукатиме поле input чи textarea із заданим атрибутом name.

Щоб додати текст до поля, не очищаючи його вмісту, скористайтеся методом append:

$browser->type('tags', 'foo')
    ->append('tags', ', bar, baz');

Очистити значення поля можна методом clear:

$browser->clear('email');

Ви можете вказати Dusk вводити текст повільно методом typeSlowly. За замовчуванням Dusk робить паузу 100 мілісекунд між натисканнями клавіш. Щоб змінити цей час, передайте потрібну кількість мілісекунд третім аргументом методу:

$browser->typeSlowly('mobile', '+1 (202) 555-5555');

$browser->typeSlowly('mobile', '+1 (202) 555-5555', 300);

Метод appendSlowly дозволяє повільно додавати текст:

$browser->type('tags', 'foo')
    ->appendSlowly('tags', ', bar, baz');

Щоб обрати значення в елементі select, скористайтеся методом select. Як і методу type, методу select не потрібен повний CSS-селектор. Передаючи значення методу select, передавайте саме значення опції, а не видимий текст:

$browser->select('size', 'Large');

Ви можете обрати випадкову опцію, не передаючи другого аргументу:

$browser->select('size');

Передавши масив другим аргументом до методу select, ви можете вказати методу обрати кілька опцій:

$browser->select('categories', ['Art', 'Music']);

Чекбокси

Щоб «поставити галочку» в чекбоксі, скористайтеся методом check. Як і багатьом іншим методам для полів введення, повний CSS-селектор тут не потрібен. Якщо збігу за CSS-селектором не знайдено, Dusk шукатиме чекбокс із відповідним атрибутом name:

$browser->check('terms');

Метод uncheck дозволяє «зняти галочку» з чекбокса:

$browser->uncheck('terms');

Радіокнопки

Щоб «обрати» опцію radio, скористайтеся методом radio. Як і багатьом іншим методам для полів введення, повний CSS-селектор тут не потрібен. Якщо збігу за CSS-селектором не знайдено, Dusk шукатиме поле radio з відповідними атрибутами name та value:

$browser->radio('size', 'large');

Прикріплення файлів

Метод attach дозволяє прикріпити файл до елемента file. Як і багатьом іншим методам для полів введення, повний CSS-селектор тут не потрібен. Якщо збігу за CSS-селектором не знайдено, Dusk шукатиме поле file з відповідним атрибутом name:

$browser->attach('photo', __DIR__.'/photos/mountains.png');

Функція attach вимагає, щоб на вашому сервері було встановлено й увімкнено розширення PHP Zip.

Натискання кнопок

Метод press дозволяє клацнути по кнопці на сторінці. Аргументом методу press може бути або видимий текст кнопки, або CSS- чи Dusk-селектор:

$browser->press('Login');

Надсилаючи форми, багато застосунків вимикають кнопку надсилання після натискання й вмикають її знову, коли HTTP-запит завершується. Щоб натиснути кнопку й дочекатися, доки її знову ввімкнуть, скористайтеся методом pressAndWaitFor:

// Press the button and wait a maximum of 5 seconds for it to be enabled...
$browser->pressAndWaitFor('Save');

// Press the button and wait a maximum of 1 second for it to be enabled...
$browser->pressAndWaitFor('Save', 1);

Щоб клацнути по посиланню, скористайтеся методом clickLink на екземплярі браузера. Метод clickLink клацне по посиланню із заданим видимим текстом:

$browser->clickLink($linkText);

Метод seeLink дозволяє визначити, чи видиме на сторінці посилання із заданим видимим текстом:

if ($browser->seeLink($linkText)) {
    // ...
}

Ці методи працюють через jQuery. Якщо jQuery на сторінці немає, Dusk автоматично впровадить його, щоб він був доступний на час тесту.

Використання клавіатури

Метод keys дозволяє передавати елементу складніші послідовності введення, ніж дозволяє метод type. Наприклад, ви можете вказати Dusk утримувати клавіші-модифікатори під час введення значень. У цьому прикладі клавішу shift буде утримувано, доки в елемент, що відповідає заданому селектору, вводиться taylor. Після taylor буде введено swift уже без модифікаторів:

$browser->keys('selector', ['{shift}', 'taylor'], 'swift');

Ще один корисний випадок для методу keys - надсилання комбінації «гарячих клавіш» до основного CSS-селектора вашого застосунку:

$browser->keys('.app', ['{command}', 'j']);

Усі клавіші-модифікатори на кшталт {command} беруться у фігурні дужки {} і відповідають константам, визначеним у класі Facebook\WebDriver\WebDriverKeys, який можна знайти на GitHub.

Плавна робота з клавіатурою

Dusk також надає метод withKeyboard, який дозволяє плавно виконувати складні взаємодії з клавіатурою через клас Laravel\Dusk\Keyboard. Клас Keyboard надає методи press, release, type та pause:

use Laravel\Dusk\Keyboard;

$browser->withKeyboard(function (Keyboard $keyboard) {
    $keyboard->press('c')
        ->pause(1000)
        ->release('c')
        ->type(['c', 'e', 'o']);
});

Макроси клавіатури

Якщо ви хочете визначити власні взаємодії з клавіатурою, які легко перевикористовувати в усьому наборі тестів, скористайтеся методом macro, який надає клас Keyboard. Зазвичай цей метод викликають у методі boot сервіс-провайдера:

<?php

namespace App\Providers;

use Facebook\WebDriver\WebDriverKeys;
use Illuminate\Support\ServiceProvider;
use Laravel\Dusk\Keyboard;
use Laravel\Dusk\OperatingSystem;

class DuskServiceProvider extends ServiceProvider
{
    /**
     * Register Dusk's browser macros.
     */
    public function boot(): void
    {
        Keyboard::macro('copy', function (string $element = null) {
            $this->type([
                OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'c',
            ]);

            return $this;
        });

        Keyboard::macro('paste', function (string $element = null) {
            $this->type([
                OperatingSystem::onMac() ? WebDriverKeys::META : WebDriverKeys::CONTROL, 'v',
            ]);

            return $this;
        });
    }
}

Функція macro приймає першим аргументом ім'я, а другим - замикання. Замикання макроса виконається, коли ви викличете макрос як метод на екземплярі Keyboard:

$browser->click('@textarea')
    ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->copy())
    ->click('@another-textarea')
    ->withKeyboard(fn (Keyboard $keyboard) => $keyboard->paste());

Використання миші

Клацання по елементах

Метод click дозволяє клацнути по елементу, який відповідає заданому CSS- чи Dusk-селектору:

$browser->click('.selector');

Метод clickAtXPath дозволяє клацнути по елементу, який відповідає заданому виразу XPath:

$browser->clickAtXPath('//div[@class = "selector"]');

Метод clickAtPoint дозволяє клацнути по найвищому елементу за заданою парою координат відносно видимої області браузера:

$browser->clickAtPoint($x = 0, $y = 0);

Метод doubleClick дозволяє симулювати подвійне клацання мишею:

$browser->doubleClick();

$browser->doubleClick('.selector');

Метод rightClick дозволяє симулювати клацання правою кнопкою миші:

$browser->rightClick();

$browser->rightClick('.selector');

Метод clickAndHold дозволяє симулювати натискання й утримування кнопки миші. Наступний виклик методу releaseMouse скасує цю поведінку й відпустить кнопку:

$browser->clickAndHold('.selector');

$browser->clickAndHold()
    ->pause(1000)
    ->releaseMouse();

Метод controlClick дозволяє симулювати у браузері подію ctrl+click:

$browser->controlClick();

$browser->controlClick('.selector');

Методи clickWhenVisible чи clickWhenEnabled дозволяють дочекатися готовності елемента, перш ніж клацнути по ньому рівно один раз:

$browser->clickWhenVisible('@save-button');
$browser->clickWhenEnabled('@submit-button');

Наведення миші

Метод mouseover стане в пригоді, коли вам треба навести мишу на елемент, що відповідає заданому CSS- чи Dusk-селектору:

$browser->mouseover('.selector');

Перетягування

Метод drag дозволяє перетягнути елемент, що відповідає заданому селектору, до іншого елемента:

$browser->drag('.from-selector', '.to-selector');

Або ж ви можете перетягнути елемент в одному напрямку:

$browser->dragLeft('.selector', $pixels = 10);
$browser->dragRight('.selector', $pixels = 10);
$browser->dragUp('.selector', $pixels = 10);
$browser->dragDown('.selector', $pixels = 10);

Нарешті, ви можете перетягнути елемент на заданий зсув:

$browser->dragOffset('.selector', $x = 10, $y = 10);

Діалоги JavaScript

Dusk надає різні методи для роботи з діалогами JavaScript. Наприклад, метод waitForDialog дозволяє дочекатися появи діалогу JavaScript. Цей метод приймає необов'язковий аргумент - скільки секунд чекати на появу діалогу:

$browser->waitForDialog($seconds = null);

Метод assertDialogOpened дозволяє перевірити, що діалог було показано і що він містить задане повідомлення:

$browser->assertDialogOpened('Dialog message');

Якщо діалог JavaScript містить поле введення, ви можете скористатися методом typeInDialog, щоб ввести туди значення:

$browser->typeInDialog('Hello World');

Щоб закрити відкритий діалог JavaScript натисканням кнопки «OK», викличте метод acceptDialog:

$browser->acceptDialog();

Щоб закрити відкритий діалог JavaScript натисканням кнопки «Cancel», викличте метод dismissDialog:

$browser->dismissDialog();

Робота з вбудованими фреймами

Якщо вам потрібно взаємодіяти з елементами всередині iframe, скористайтеся методом withinFrame. Усі взаємодії з елементами всередині замикання, переданого методу withinFrame, будуть обмежені контекстом указаного iframe:

$browser->withinFrame('#credit-card-details', function ($browser) {
    $browser->type('input[name="cardnumber"]', '4242424242424242')
        ->type('input[name="exp-date"]', '1224')
        ->type('input[name="cvc"]', '123')
        ->press('Pay');
});

Обмеження селекторів

Іноді вам може знадобитися виконати кілька операцій, обмеживши їх усі заданим селектором. Наприклад, ви можете захотіти перевірити, що певний текст є лише в таблиці, а потім клацнути по кнопці всередині цієї таблиці. Для цього скористайтеся методом with. Усі операції всередині замикання, переданого методу with, будуть обмежені початковим селектором:

$browser->with('.table', function (Browser $table) {
    $table->assertSee('Hello World')
        ->clickLink('Delete');
});

Іноді вам може знадобитися виконати твердження поза межами поточного обмеження. Для цього скористайтеся методами elsewhere та elsewhereWhenAvailable:

$browser->with('.table', function (Browser $table) {
    // Current scope is `body .table`...

    $browser->elsewhere('.page-title', function (Browser $title) {
        // Current scope is `body .page-title`...
        $title->assertSee('Hello World');
    });

    $browser->elsewhereWhenAvailable('.page-title', function (Browser $title) {
        // Current scope is `body .page-title`...
        $title->assertSee('Hello World');
    });
});

Очікування елементів

Тестуючи застосунки, які активно використовують JavaScript, часто доводиться «чекати» на доступність певних елементів чи даних, перш ніж рухатися далі. Dusk робить це дуже просто. За допомогою різних методів ви можете дочекатися, доки елементи стануть видимими на сторінці, або навіть доки заданий вираз JavaScript не набуде значення true.

Очікування

Якщо вам просто треба призупинити тест на задану кількість мілісекунд, скористайтеся методом pause:

$browser->pause(1000);

Якщо тест треба призупинити лише за умови, що задана умова істинна (true), скористайтеся методом pauseIf:

$browser->pauseIf(App::environment('production'), 1000);

Так само, якщо тест треба призупинити, доки задана умова не стане істинною (true), скористайтеся методом pauseUnless:

$browser->pauseUnless(App::environment('testing'), 1000);

Очікування селекторів

Метод waitFor дозволяє призупинити виконання тесту, доки на сторінці не з'явиться елемент, що відповідає заданому CSS- чи Dusk-селектору. За замовчуванням тест буде призупинено максимум на п'ять секунд, після чого буде кинуто виняток. За потреби ви можете передати власний поріг очікування другим аргументом методу:

// Wait a maximum of five seconds for the selector...
$browser->waitFor('.selector');

// Wait a maximum of one second for the selector...
$browser->waitFor('.selector', 1);

Ви також можете дочекатися, доки елемент, що відповідає заданому селектору, не міститиме заданий текст:

// Wait a maximum of five seconds for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World');

// Wait a maximum of one second for the selector to contain the given text...
$browser->waitForTextIn('.selector', 'Hello World', 1);

Ви також можете дочекатися, доки елемент, що відповідає заданому селектору, зникне зі сторінки:

// Wait a maximum of five seconds until the selector is missing...
$browser->waitUntilMissing('.selector');

// Wait a maximum of one second until the selector is missing...
$browser->waitUntilMissing('.selector', 1);

Або ж ви можете дочекатися, доки елемент, що відповідає заданому селектору, стане увімкненим чи вимкненим:

// Wait a maximum of five seconds until the selector is enabled...
$browser->waitUntilEnabled('.selector');

// Wait a maximum of one second until the selector is enabled...
$browser->waitUntilEnabled('.selector', 1);

// Wait a maximum of five seconds until the selector is disabled...
$browser->waitUntilDisabled('.selector');

// Wait a maximum of one second until the selector is disabled...
$browser->waitUntilDisabled('.selector', 1);

Обмеження селекторів за доступністю

Іноді вам може знадобитися дочекатися появи елемента, що відповідає заданому селектору, а потім із ним попрацювати. Наприклад, дочекатися появи модального вікна й натиснути в ньому кнопку «OK». Для цього скористайтеся методом whenAvailable. Усі операції з елементами всередині заданого замикання будуть обмежені початковим селектором:

$browser->whenAvailable('.modal', function (Browser $modal) {
    $modal->assertSee('Hello World')
        ->press('OK');
});

Очікування тексту

Метод waitForText дозволяє дочекатися, доки на сторінці не з'явиться заданий текст:

// Wait a maximum of five seconds for the text...
$browser->waitForText('Hello World');

// Wait a maximum of one second for the text...
$browser->waitForText('Hello World', 1);

Метод waitUntilMissingText дозволяє дочекатися, доки показаний текст не зникне зі сторінки:

// Wait a maximum of five seconds for the text to be removed...
$browser->waitUntilMissingText('Hello World');

// Wait a maximum of one second for the text to be removed...
$browser->waitUntilMissingText('Hello World', 1);

Метод waitForLink дозволяє дочекатися, доки на сторінці не з'явиться заданий текст посилання:

// Wait a maximum of five seconds for the link...
$browser->waitForLink('Create');

// Wait a maximum of one second for the link...
$browser->waitForLink('Create', 1);

Очікування полів введення

Метод waitForInput дозволяє дочекатися, доки задане поле введення стане видимим на сторінці:

// Wait a maximum of five seconds for the input...
$browser->waitForInput($field);

// Wait a maximum of one second for the input...
$browser->waitForInput($field, 1);

Очікування розташування сторінки

Коли ви робите твердження щодо шляху на кшталт $browser->assertPathIs('/home'), воно може провалитися, якщо window.location.pathname оновлюється асинхронно. Скористайтеся методом waitForLocation, щоб дочекатися, доки розташування набуде заданого значення:

$browser->waitForLocation('/secret');

Метод waitForLocation можна також використати, щоб дочекатися, доки поточне розташування вікна стане повним URL:

$browser->waitForLocation('https://example.com/path');

Ви також можете дочекатися розташування іменованого маршруту:

$browser->waitForRoute($routeName, $parameters);

Очікування перезавантаження сторінки

Якщо після виконання дії вам треба дочекатися перезавантаження сторінки, скористайтеся методом waitForReload:

use Laravel\Dusk\Browser;

$browser->waitForReload(function (Browser $browser) {
    $browser->press('Submit');
})
->assertSee('Success!');

Оскільки потреба дочекатися перезавантаження зазвичай виникає після натискання кнопки, для зручності ви можете скористатися методом clickAndWaitForReload:

$browser->clickAndWaitForReload('.selector')
    ->assertSee('something');

Очікування виразів JavaScript

Іноді вам може знадобитися призупинити виконання тесту, доки заданий вираз JavaScript не набуде значення true. Легко зробити це можна методом waitUntil. Передаючи цьому методу вираз, вам не треба додавати ключове слово return чи крапку з комою в кінці:

// Wait a maximum of five seconds for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0');

// Wait a maximum of one second for the expression to be true...
$browser->waitUntil('App.data.servers.length > 0', 1);

Очікування виразів Vue

Методи waitUntilVue та waitUntilVueIsNot дозволяють дочекатися, доки атрибут компонента Vue набуде заданого значення:

// Wait until the component attribute contains the given value...
$browser->waitUntilVue('user.name', 'Taylor', '@user');

// Wait until the component attribute doesn't contain the given value...
$browser->waitUntilVueIsNot('user.name', null, '@user');

Очікування подій JavaScript

Метод waitForEvent дозволяє призупинити виконання тесту, доки не станеться подія JavaScript:

$browser->waitForEvent('load');

Слухач події прикріплюється до поточного обмеження, яким за замовчуванням є елемент body. Якщо ви користуєтеся обмеженим селектором, слухач буде прикріплено до відповідного елемента:

$browser->with('iframe', function (Browser $iframe) {
    // Wait for the iframe's load event...
    $iframe->waitForEvent('load');
});

Ви також можете передати селектор другим аргументом до методу waitForEvent, щоб прикріпити слухач до конкретного елемента:

$browser->waitForEvent('load', '.selector');

Ви також можете чекати на події об'єктів document та window:

// Wait until the document is scrolled...
$browser->waitForEvent('scroll', 'document');

// Wait a maximum of five seconds until the window is resized...
$browser->waitForEvent('resize', 'window', 5);

Очікування з колбеком

Багато методів очікування в Dusk спираються на метод waitUsing. Ви можете скористатися ним напряму, щоб дочекатися, доки задане замикання поверне true. Метод waitUsing приймає максимальну кількість секунд очікування, інтервал перевірки замикання, саме замикання та необов'язкове повідомлення про помилку:

$browser->waitUsing(10, 1, function () use ($something) {
    return $something->isReady();
}, "Something wasn't ready in time.");

Прокручування до елемента

Іноді ви не зможете клацнути по елементу, бо він поза видимою областю браузера. Метод scrollIntoView прокрутить вікно браузера, доки елемент за заданим селектором не потрапить у поле зору:

$browser->scrollIntoView('.selector')
    ->click('.selector');

Доступні твердження

Dusk надає різні твердження, які ви можете робити щодо свого застосунку. Усі доступні твердження задокументовані в списку нижче:

assertTitle

Перевіряє, що заголовок сторінки збігається із заданим текстом:

$browser->assertTitle($title);

assertTitleContains

Перевіряє, що заголовок сторінки містить заданий текст:

$browser->assertTitleContains($title);

assertUrlIs

Перевіряє, що поточний URL (без рядка запиту) збігається із заданим рядком:

$browser->assertUrlIs($url);

assertSchemeIs

Перевіряє, що схема поточного URL збігається із заданою:

$browser->assertSchemeIs($scheme);

assertSchemeIsNot

Перевіряє, що схема поточного URL не збігається із заданою:

$browser->assertSchemeIsNot($scheme);

assertHostIs

Перевіряє, що хост поточного URL збігається із заданим:

$browser->assertHostIs($host);

assertHostIsNot

Перевіряє, що хост поточного URL не збігається із заданим:

$browser->assertHostIsNot($host);

assertPortIs

Перевіряє, що порт поточного URL збігається із заданим:

$browser->assertPortIs($port);

assertPortIsNot

Перевіряє, що порт поточного URL не збігається із заданим:

$browser->assertPortIsNot($port);

assertPathBeginsWith

Перевіряє, що шлях поточного URL починається із заданого:

$browser->assertPathBeginsWith('/home');

assertPathEndsWith

Перевіряє, що шлях поточного URL закінчується заданим:

$browser->assertPathEndsWith('/home');

assertPathContains

Перевіряє, що шлях поточного URL містить заданий:

$browser->assertPathContains('/home');

assertPathIs

Перевіряє, що поточний шлях збігається із заданим:

$browser->assertPathIs('/home');

assertPathIsNot

Перевіряє, що поточний шлях не збігається із заданим:

$browser->assertPathIsNot('/home');

assertRouteIs

Перевіряє, що поточний URL збігається з URL заданого іменованого маршруту:

$browser->assertRouteIs($name, $parameters);

assertQueryStringHas

Перевіряє, що заданий параметр рядка запиту присутній:

$browser->assertQueryStringHas($name);

Перевіряє, що заданий параметр рядка запиту присутній і має задане значення:

$browser->assertQueryStringHas($name, $value);

assertQueryStringMissing

Перевіряє, що заданого параметра рядка запиту немає:

$browser->assertQueryStringMissing($name);

assertFragmentIs

Перевіряє, що поточний хеш-фрагмент URL збігається із заданим:

$browser->assertFragmentIs('anchor');

assertFragmentBeginsWith

Перевіряє, що поточний хеш-фрагмент URL починається із заданого:

$browser->assertFragmentBeginsWith('anchor');

assertFragmentIsNot

Перевіряє, що поточний хеш-фрагмент URL не збігається із заданим:

$browser->assertFragmentIsNot('anchor');

Перевіряє, що заданий зашифрований cookie присутній:

$browser->assertHasCookie($name);

Перевіряє, що заданий незашифрований cookie присутній:

$browser->assertHasPlainCookie($name);

Перевіряє, що заданого зашифрованого cookie немає:

$browser->assertCookieMissing($name);

Перевіряє, що заданого незашифрованого cookie немає:

$browser->assertPlainCookieMissing($name);

Перевіряє, що зашифрований cookie має задане значення:

$browser->assertCookieValue($name, $value);

Перевіряє, що незашифрований cookie має задане значення:

$browser->assertPlainCookieValue($name, $value);

assertSee

Перевіряє, що заданий текст присутній на сторінці:

$browser->assertSee($text);

assertDontSee

Перевіряє, що заданого тексту немає на сторінці:

$browser->assertDontSee($text);

assertSeeIn

Перевіряє, що заданий текст присутній усередині селектора:

$browser->assertSeeIn($selector, $text);

assertDontSeeIn

Перевіряє, що заданого тексту немає всередині селектора:

$browser->assertDontSeeIn($selector, $text);

assertSeeAnythingIn

Перевіряє, що всередині селектора є будь-який текст:

$browser->assertSeeAnythingIn($selector);

assertSeeNothingIn

Перевіряє, що всередині селектора немає жодного тексту:

$browser->assertSeeNothingIn($selector);

assertCount

Перевіряє, що елементи, які відповідають заданому селектору, трапляються вказану кількість разів:

$browser->assertCount($selector, $count);

assertScript

Перевіряє, що заданий вираз JavaScript обчислюється в задане значення:

$browser->assertScript('window.isLoaded')
    ->assertScript('document.readyState', 'complete');

assertSourceHas

Перевіряє, що заданий код присутній на сторінці:

$browser->assertSourceHas($code);

assertSourceMissing

Перевіряє, що заданого коду немає на сторінці:

$browser->assertSourceMissing($code);

Перевіряє, що задане посилання присутнє на сторінці:

$browser->assertSeeLink($linkText);

Перевіряє, що заданого посилання немає на сторінці:

$browser->assertDontSeeLink($linkText);

assertInputValue

Перевіряє, що задане поле введення має задане значення:

$browser->assertInputValue($field, $value);

assertInputValueIsNot

Перевіряє, що задане поле введення не має заданого значення:

$browser->assertInputValueIsNot($field, $value);

assertChecked

Перевіряє, що заданий чекбокс позначено:

$browser->assertChecked($field);

assertNotChecked

Перевіряє, що заданий чекбокс не позначено:

$browser->assertNotChecked($field);

assertIndeterminate

Перевіряє, що заданий чекбокс перебуває в невизначеному стані:

$browser->assertIndeterminate($field);

assertRadioSelected

Перевіряє, що задану радіокнопку обрано:

$browser->assertRadioSelected($field, $value);

assertRadioNotSelected

Перевіряє, що задану радіокнопку не обрано:

$browser->assertRadioNotSelected($field, $value);

assertSelected

Перевіряє, що в заданому випадному списку обрано задане значення:

$browser->assertSelected($field, $value);

assertNotSelected

Перевіряє, що в заданому випадному списку не обрано заданого значення:

$browser->assertNotSelected($field, $value);

assertSelectHasOptions

Перевіряє, що заданий масив значень доступний для вибору:

$browser->assertSelectHasOptions($field, $values);

assertSelectMissingOptions

Перевіряє, що заданий масив значень недоступний для вибору:

$browser->assertSelectMissingOptions($field, $values);

assertSelectHasOption

Перевіряє, що задане значення доступне для вибору в заданому полі:

$browser->assertSelectHasOption($field, $value);

assertSelectMissingOption

Перевіряє, що задане значення недоступне для вибору:

$browser->assertSelectMissingOption($field, $value);

assertValue

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення:

$browser->assertValue($selector, $value);

assertValueIsNot

Перевіряє, що елемент, який відповідає заданому селектору, не має заданого значення:

$browser->assertValueIsNot($selector, $value);

assertAttribute

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення у вказаному атрибуті:

$browser->assertAttribute($selector, $attribute, $value);

assertAttributeMissing

Перевіряє, що в елемента, який відповідає заданому селектору, немає вказаного атрибута:

$browser->assertAttributeMissing($selector, $attribute);

assertAttributeContains

Перевіряє, що елемент, який відповідає заданому селектору, містить задане значення у вказаному атрибуті:

$browser->assertAttributeContains($selector, $attribute, $value);

assertAttributeDoesntContain

Перевіряє, що елемент, який відповідає заданому селектору, не містить заданого значення у вказаному атрибуті:

$browser->assertAttributeDoesntContain($selector, $attribute, $value);

assertAriaAttribute

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення у вказаному атрибуті aria:

$browser->assertAriaAttribute($selector, $attribute, $value);

Наприклад, для розмітки <button aria-label="Add"></button> ви можете зробити твердження щодо атрибута aria-label ось так:

$browser->assertAriaAttribute('button', 'label', 'Add')

assertDataAttribute

Перевіряє, що елемент, який відповідає заданому селектору, має задане значення у вказаному атрибуті data:

$browser->assertDataAttribute($selector, $attribute, $value);

Наприклад, для розмітки <tr id="row-1" data-content="attendees"></tr> ви можете зробити твердження щодо атрибута data-content ось так:

$browser->assertDataAttribute('#row-1', 'content', 'attendees')

assertVisible

Перевіряє, що елемент, який відповідає заданому селектору, видимий:

$browser->assertVisible($selector);

assertPresent

Перевіряє, що елемент, який відповідає заданому селектору, присутній у коді сторінки:

$browser->assertPresent($selector);

assertNotPresent

Перевіряє, що елемента, який відповідає заданому селектору, немає в коді сторінки:

$browser->assertNotPresent($selector);

assertMissing

Перевіряє, що елемент, який відповідає заданому селектору, не видимий:

$browser->assertMissing($selector);

assertInputPresent

Перевіряє, що поле введення із заданим іменем присутнє:

$browser->assertInputPresent($name);

assertInputMissing

Перевіряє, що поля введення із заданим іменем немає в коді сторінки:

$browser->assertInputMissing($name);

assertDialogOpened

Перевіряє, що діалог JavaScript із заданим повідомленням було відкрито:

$browser->assertDialogOpened($message);

assertEnabled

Перевіряє, що задане поле увімкнене:

$browser->assertEnabled($field);

assertDisabled

Перевіряє, що задане поле вимкнене:

$browser->assertDisabled($field);

assertButtonEnabled

Перевіряє, що задана кнопка увімкнена:

$browser->assertButtonEnabled($button);

assertButtonDisabled

Перевіряє, що задана кнопка вимкнена:

$browser->assertButtonDisabled($button);

assertFocused

Перевіряє, що задане поле у фокусі:

$browser->assertFocused($field);

assertNotFocused

Перевіряє, що задане поле не у фокусі:

$browser->assertNotFocused($field);

assertAuthenticated

Перевіряє, що користувач автентифікований:

$browser->assertAuthenticated();

assertGuest

Перевіряє, що користувач не автентифікований:

$browser->assertGuest();

assertAuthenticatedAs

Перевіряє, що користувач автентифікований як заданий користувач:

$browser->assertAuthenticatedAs($user);

assertVue

Dusk дозволяє навіть робити твердження щодо стану даних компонента Vue. Уявіть, наприклад, що ваш застосунок містить такий компонент Vue:

// HTML...

<profile dusk="profile-component"></profile>

// Component Definition...

Vue.component('profile', {
    template: '<div>{{ user.name }}</div>',

    data: function () {
        return {
            user: {
                name: 'Taylor'
            }
        };
    }
});

Ви можете зробити твердження щодо стану компонента Vue ось так:

test('vue', function () {
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
            ->assertVue('user.name', 'Taylor', '@profile-component');
    });
});
/**
 * A basic Vue test example.
 */
public function test_vue(): void
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
            ->assertVue('user.name', 'Taylor', '@profile-component');
    });
}

assertVueIsNot

Перевіряє, що задана властивість даних компонента Vue не збігається із заданим значенням:

$browser->assertVueIsNot($property, $value, $componentSelector = null);

assertVueContains

Перевіряє, що задана властивість даних компонента Vue є масивом і містить задане значення:

$browser->assertVueContains($property, $value, $componentSelector = null);

assertVueDoesntContain

Перевіряє, що задана властивість даних компонента Vue є масивом і не містить заданого значення:

$browser->assertVueDoesntContain($property, $value, $componentSelector = null);

Сторінки

Іноді тести вимагають виконати послідовно кілька складних дій. Через це тести стає важче читати й розуміти. Сторінки Dusk дозволяють описати виразні дії, які потім можна виконати на заданій сторінці одним методом. Сторінки також дозволяють описати скорочення для поширених селекторів вашого застосунку або окремої сторінки.

Генерування сторінок

Щоб згенерувати об'єкт сторінки, виконайте артизан-команду dusk:page. Усі об'єкти сторінок потраплять до каталогу tests/Browser/Pages вашого застосунку:

php artisan dusk:page Login

Налаштування сторінок

За замовчуванням сторінки мають три методи: url, assert та elements. Методи url та assert ми розглянемо зараз. Метод elements буде докладніше розглянуто нижче.

Метод url

Метод url має повертати шлях URL, що представляє сторінку. Dusk використовуватиме цей URL, переходячи до сторінки у браузері:

/**
 * Get the URL for the page.
 */
public function url(): string
{
    return '/login';
}

Метод assert

Метод assert може містити будь-які твердження, потрібні, щоб переконатися, що браузер справді на заданій сторінці. Розміщувати щось у цьому методі не обов'язково; проте ви вільні робити такі твердження за бажання. Ці твердження виконуватимуться автоматично при переході до сторінки:

/**
 * Assert that the browser is on the page.
 */
public function assert(Browser $browser): void
{
    $browser->assertPathIs($this->url());
}

Коли сторінку визначено, ви можете перейти до неї методом visit:

use Tests\Browser\Pages\Login;

$browser->visit(new Login);

Іноді ви вже перебуваєте на заданій сторінці, і вам треба «завантажити» її селектори й методи до поточного контексту тесту. Так буває, коли ви натискаєте кнопку й вас перенаправляє на сторінку без явного переходу до неї. У такому разі скористайтеся методом on, щоб завантажити сторінку:

use Tests\Browser\Pages\CreatePlaylist;

$browser->visit('/dashboard')
    ->clickLink('Create Playlist')
    ->on(new CreatePlaylist)
    ->assertSee('@create');

Скорочені селектори

Метод elements у класах сторінок дозволяє описати швидкі й легкі для запам'ятовування скорочення для будь-якого CSS-селектора на вашій сторінці. Наприклад, визначмо скорочення для поля «email» на сторінці входу застосунку:

/**
 * Get the element shortcuts for the page.
 *
 * @return array<string, string>
 */
public function elements(): array
{
    return [
        '@email' => 'input[name=email]',
    ];
}

Коли скорочення визначено, ви можете використовувати скорочений селектор усюди, де зазвичай використовували б повний CSS-селектор:

$browser->type('@email', 'taylor@laravel.com');

Глобальні скорочені селектори

Після встановлення Dusk базовий клас Page потрапить до вашого каталогу tests/Browser/Pages. Цей клас містить метод siteElements, який дозволяє описати глобальні скорочені селектори, доступні на кожній сторінці вашого застосунку:

/**
 * Get the global element shortcuts for the site.
 *
 * @return array<string, string>
 */
public static function siteElements(): array
{
    return [
        '@element' => '#selector',
    ];
}

Методи сторінок

Окрім стандартних методів сторінок, ви можете визначити додаткові методи, якими користуватиметеся у своїх тестах. Уявімо, наприклад, що ми будуємо застосунок для керування музикою. Поширеною дією на одній зі сторінок може бути створення плейлиста. Замість того щоб переписувати логіку створення плейлиста в кожному тесті, ви можете визначити метод createPlaylist у класі сторінки:

<?php

namespace Tests\Browser\Pages;

use Laravel\Dusk\Browser;
use Laravel\Dusk\Page;

class Dashboard extends Page
{
    // Other page methods...

    /**
     * Create a new playlist.
     */
    public function createPlaylist(Browser $browser, string $name): void
    {
        $browser->type('name', $name)
            ->check('share')
            ->press('Create Playlist');
    }
}

Коли метод визначено, ви можете скористатися ним у будь-якому тесті, що використовує цю сторінку. Екземпляр браузера автоматично передається першим аргументом до власних методів сторінки:

use Tests\Browser\Pages\Dashboard;

$browser->visit(new Dashboard)
    ->createPlaylist('My Playlist')
    ->assertSee('My Playlist');

Компоненти

Компоненти схожі на «об'єкти сторінок» Dusk, але призначені для частин UI та функціональності, які перевикористовуються в усьому застосунку, - наприклад, панель навігації чи вікно сповіщень. Тому компоненти не прив'язані до конкретних URL.

Генерування компонентів

Щоб згенерувати компонент, виконайте артизан-команду dusk:component. Нові компоненти потрапляють до каталогу tests/Browser/Components:

php artisan dusk:component DatePicker

Як показано вище, «вибір дати» - приклад компонента, який може траплятися в усьому застосунку на різних сторінках. Писати вручну логіку автоматизації браузера для вибору дати в десятках тестів стає обтяжливо. Натомість ми можемо описати компонент Dusk, який представлятиме вибір дати, і інкапсулювати цю логіку в ньому:

<?php

namespace Tests\Browser\Components;

use Laravel\Dusk\Browser;
use Laravel\Dusk\Component as BaseComponent;

class DatePicker extends BaseComponent
{
    /**
     * Get the root selector for the component.
     */
    public function selector(): string
    {
        return '.date-picker';
    }

    /**
     * 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 [
            '@date-field' => 'input.datepicker-input',
            '@year-list' => 'div > div.datepicker-years',
            '@month-list' => 'div > div.datepicker-months',
            '@day-list' => 'div > div.datepicker-days',
        ];
    }

    /**
     * Select the given date.
     */
    public function selectDate(Browser $browser, int $year, int $month, int $day): void
    {
        $browser->click('@date-field')
            ->within('@year-list', function (Browser $browser) use ($year) {
                $browser->click($year);
            })
            ->within('@month-list', function (Browser $browser) use ($month) {
                $browser->click($month);
            })
            ->within('@day-list', function (Browser $browser) use ($day) {
                $browser->click($day);
            });
    }
}

Використання компонентів

Коли компонент визначено, ми можемо легко обрати дату у виборі дати з будь-якого тесту. І якщо логіка вибору дати зміниться, нам треба буде оновити лише компонент:

<?php

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;

pest()->use(DatabaseMigrations::class);

test('basic example', function () {
    $this->browse(function (Browser $browser) {
        $browser->visit('/')
            ->within(new DatePicker, function (Browser $browser) {
                $browser->selectDate(2019, 1, 30);
            })
            ->assertSee('January');
    });
});
<?php

namespace Tests\Browser;

use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\Browser\Components\DatePicker;
use Tests\DuskTestCase;

class ExampleTest extends DuskTestCase
{
    /**
     * A basic component test example.
     */
    public function test_basic_example(): void
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/')
                ->within(new DatePicker, function (Browser $browser) {
                    $browser->selectDate(2019, 1, 30);
                })
                ->assertSee('January');
        });
    }
}

Метод component дозволяє отримати екземпляр браузера, обмежений заданим компонентом:

$datePicker = $browser->component(new DatePickerComponent);

$datePicker->selectDate(2019, 1, 30);

$datePicker->assertSee('January');

Неперервна інтеграція

Більшість конфігурацій неперервної інтеграції для Dusk очікують, що ваш застосунок Laravel віддається вбудованим сервером розробки PHP на порту 8000. Тому, перш ніж рухатися далі, переконайтеся, що у вашому середовищі неперервної інтеграції змінна оточення APP_URL має значення http://127.0.0.1:8000.

Heroku CI

Щоб запускати тести Dusk на Heroku CI, додайте до свого файлу app.json для Heroku такий buildpack для Google Chrome і скрипти:

{
  "environments": {
    "test": {
      "buildpacks": [
        { "url": "heroku/php" },
        { "url": "https://github.com/heroku/heroku-buildpack-chrome-for-testing" }
      ],
      "scripts": {
        "test-setup": "cp .env.testing .env",
        "test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux --port=9515 > /dev/null 2>&1 &' && nohup bash -c 'php artisan serve --no-reload > /dev/null 2>&1 &' && php artisan dusk"
      }
    }
  }
}

Travis CI

Щоб запускати ваші тести Dusk на Travis CI, скористайтеся такою конфігурацією .travis.yml. Оскільки Travis CI не є графічним середовищем, нам знадобиться кілька додаткових кроків, щоб запустити браузер Chrome. Крім того, ми скористаємося php artisan serve, щоб запустити вбудований вебсервер PHP:

language: php

php:
  - 8.2

addons:
  chrome: stable

install:
  - cp .env.testing .env
  - travis_retry composer install --no-interaction --prefer-dist
  - php artisan key:generate
  - php artisan dusk:chrome-driver

before_script:
  - google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
  - php artisan serve --no-reload &

script:
  - php artisan dusk

GitHub Actions

Якщо ви запускаєте свої тести Dusk через GitHub Actions, можете взяти за відправну точку такий конфігураційний файл. Як і у випадку з TravisCI, ми скористаємося командою php artisan serve, щоб запустити вбудований вебсервер PHP:

name: CI
on: [push]
jobs:

  dusk-php:
    runs-on: ubuntu-latest
    env:
      APP_URL: "http://127.0.0.1:8000"
      DB_USERNAME: root
      DB_PASSWORD: root
      MAIL_MAILER: log
    steps:
      - uses: actions/checkout@v5
      - name: Prepare The Environment
        run: cp .env.example .env
      - name: Create Database
        run: |
          sudo systemctl start mysql
          mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;"
      - name: Install Composer Dependencies
        run: composer install --no-progress --prefer-dist --optimize-autoloader
      - name: Generate Application Key
        run: php artisan key:generate
      - name: Upgrade Chrome Driver
        run: php artisan dusk:chrome-driver --detect
      - name: Start Chrome Driver
        run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=9515 &
      - name: Run Laravel Server
        run: php artisan serve --no-reload &
      - name: Run Dusk Tests
        run: php artisan dusk
      - name: Upload Screenshots
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: screenshots
          path: tests/Browser/screenshots
      - name: Upload Console Logs
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: console
          path: tests/Browser/console

Chipper CI

Якщо ви запускаєте свої тести Dusk через Chipper CI, можете взяти за відправну точку такий конфігураційний файл. Ми скористаємося вбудованим сервером PHP, щоб запустити Laravel і слухати запити:

# file .chipperci.yml
version: 1

environment:
  php: 8.2
  node: 16

# Include Chrome in the build environment
services:
  - dusk

# Build all commits
on:
   push:
      branches: .*

pipeline:
  - name: Setup
    cmd: |
      cp -v .env.example .env
      composer install --no-interaction --prefer-dist --optimize-autoloader
      php artisan key:generate

      # Create a dusk env file, ensuring APP_URL uses BUILD_HOST
      cp -v .env .env.dusk.ci
      sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci

  - name: Compile Assets
    cmd: |
      npm ci --no-audit
      npm run build

  - name: Browser Tests
    cmd: |
      php -S [::0]:8000 -t public 2>server.log &
      sleep 2
      php artisan dusk:chrome-driver $CHROME_DRIVER
      php artisan dusk --env=ci

Щоб дізнатися більше про запуск тестів Dusk на Chipper CI, зокрема про роботу з базами даних, зверніться до офіційної документації Chipper CI.