- Введение
- Установка
- Начало работы
- Основы работы с браузером
- Взаимодействие с элементами
- Селекторы Dusk
- Текст, значения и атрибуты
- Взаимодействие с формами
- Прикрепление файлов
- Нажатие кнопок
- Клики по ссылкам
- Использование клавиатуры
- Использование мыши
- JavaScript диалоги
- Взаимодействие с iframe
- Ограничение области действия селекторов
- Ожидание элементов
- Прокрутка элемента в область видимости
- Доступные утверждения
- Страницы
- Компоненты
- Непрерывная интеграция
#Введение
Laravel Dusk предоставляет выразительный и простой в использовании API для автоматизации браузера и тестирования. По умолчанию Dusk не требует установки JDK или Selenium на вашем локальном компьютере. Вместо этого Dusk использует автономную установку ChromeDriver. Однако вы можете использовать любой другой драйвер, совместимый с Selenium, по вашему выбору.
#Установка
Для начала установите Google Chrome и добавьте зависимость laravel/dusk через Composer в ваш проект:
composer require laravel/dusk --dev
Если вы регистрируете сервис-провайдер Dusk вручную, никогда не регистрируйте его в рабочем окружении, так как это может позволить произвольным пользователям аутентифицироваться в вашем приложении.
После установки пакета Dusk выполните Artisan-команду 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:
# Установить последнюю версию ChromeDriver для вашей ОС...
php artisan dusk:chrome-driver
# Установить указанную версию ChromeDriver для вашей ОС...
php artisan dusk:chrome-driver 86
# Установить указанную версию ChromeDriver для всех поддерживаемых ОС...
php artisan dusk:chrome-driver --all
# Установить версию ChromeDriver, соответствующую обнаруженной версии Chrome / Chromium для вашей ОС...
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:
/**
* Подготовка к выполнению тестов Dusk.
*
* @beforeClass
*/
public static function prepare(): void
{
// static::startChromeDriver();
}
Далее вы можете изменить метод driver, чтобы подключаться к нужному URL и порту. Также можно изменить "желаемые возможности" (desired capabilities), которые передаются WebDriver:
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* Создать экземпляр RemoteWebDriver.
*/
protected function driver(): RemoteWebDriver
{
return RemoteWebDriver::create(
'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
);
}
#Начало работы
#Генерация тестов
Чтобы сгенерировать тест Dusk, используйте Artisan-команду dusk:make. Сгенерированный тест будет помещён в директорию tests/Browser:
php artisan dusk:make LoginTest
#Сброс базы данных после каждого теста
Большинство тестов, которые вы пишете, будут взаимодействовать со страницами, получающими данные из базы данных вашего приложения; однако Dusk-тесты никогда не должны использовать трейт RefreshDatabase. Трейт RefreshDatabase опирается на транзакции базы данных, которые не применимы и недоступны между HTTP-запросами. Вместо этого есть два варианта: трейт DatabaseMigrations или трейт DatabaseTruncation.
#Использование миграций базы данных
Трейд DatabaseMigrations будет запускать миграции базы данных перед каждым тестом. Однако удаление и повторное создание таблиц базы данных для каждого теста обычно медленнее, чем их очистка:
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Chrome;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
}
В памяти SQLite базы данных использовать нельзя при выполнении тестов Dusk. Поскольку браузер работает в отдельном процессе, он не сможет получить доступ к базам данных в памяти других процессов.
#Использование очистки базы данных
Перед использованием трейда DatabaseTruncation необходимо установить пакет doctrine/dbal через Composer:
composer require --dev doctrine/dbal
Трейд DatabaseTruncation выполнит миграцию базы данных при первом тесте, чтобы убедиться, что таблицы созданы корректно. Однако в последующих тестах таблицы будут просто очищаться — это ускорит процесс по сравнению с повторным выполнением всех миграций:
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseTruncation;
use Laravel\Dusk\Chrome;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseTruncation;
}
По умолчанию этот трейд очищает все таблицы, кроме таблицы migrations. Если вы хотите настроить список таблиц для очистки, определите свойство $tablesToTruncate в вашем тестовом классе:
/**
* Указывает, какие таблицы должны быть очищены.
*
* @var array
*/
protected $tablesToTruncate = ['users'];
В качестве альтернативы вы можете определить свойство $exceptTables в тестовом классе, чтобы указать таблицы, которые не должны очищаться:
/**
* Указывает, какие таблицы должны быть исключены из очистки.
*
* @var array
*/
protected $exceptTables = ['users'];
Чтобы указать подключения к базе данных, для которых следует очищать таблицы, определите свойство $connectionsToTruncate в тестовом классе:
/**
* Указывает, для каких подключений следует очищать таблицы.
*
* @var array
*/
protected $connectionsToTruncate = ['mysql'];
Если вы хотите выполнить код до или после очистки базы данных, определите методы beforeTruncatingDatabase или afterTruncatingDatabase в вашем тестовом классе:
/**
* Выполнить действия перед началом очистки базы данных.
*/
protected function beforeTruncatingDatabase(): void
{
//
}
/**
* Выполнить действия после завершения очистки базы данных.
*/
protected function afterTruncatingDatabase(): void
{
//
}
#Запуск тестов
Для запуска тестов браузера выполните Artisan-команду dusk:
php artisan dusk
Если при последнем запуске команды dusk были ошибки, вы можете сэкономить время, повторно запустив сначала только упавшие тесты с помощью команды dusk:fails:
php artisan dusk:fails
Команда dusk принимает любые аргументы, которые обычно принимает тестовый раннер PHPUnit, например, для запуска тестов только из определённой группы:
php artisan dusk --group=foo
Если вы используете Laravel Sail для управления локальной средой разработки, ознакомьтесь с документацией Sail по настройке и запуску тестов Dusk.
#Ручной запуск ChromeDriver
По умолчанию Dusk автоматически пытается запустить ChromeDriver. Если это не работает на вашей системе, вы можете запустить ChromeDriver вручную перед выполнением команды dusk. В этом случае следует закомментировать следующую строку в файле tests/DuskTestCase.php:
/**
* Подготовка к выполнению тестов Dusk.
*
* @beforeClass
*/
public static function prepare(): void
{
// static::startChromeDriver();
}
Кроме того, если вы запускаете ChromeDriver на порту, отличном от 9515, измените метод driver в том же классе, указав правильный порт:
use Facebook\WebDriver\Remote\RemoteWebDriver;
/**
* Создать экземпляр RemoteWebDriver.
*/
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
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Chrome;
use Tests\DuskTestCase;
class ExampleTest extends DuskTestCase
{
use DatabaseMigrations;
/**
* Пример базового теста браузера.
*/
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 принимает замыкание. Экземпляр браузера автоматически передаётся в это замыкание и является основным объектом для взаимодействия и проверки вашего приложения.
#Создание нескольких браузеров
Иногда для корректного выполнения теста требуется несколько браузеров. Например, для тестирования чата с использованием websocket. Чтобы создать несколько браузеров, добавьте дополнительные аргументы браузера в сигнатуру замыкания, передаваемого в метод 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('login');
Вы можете перемещаться "назад" и "вперёд" с помощью методов 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
{
/**
* Регистрация макросов браузера Dusk.
*/
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');
});
#Аутентификация
Часто вы будете тестировать страницы, требующие аутентификации. Вы можете использовать метод loginAs из Dusk, чтобы не взаимодействовать со страницей входа вашего приложения во время каждого теста. Метод loginAs принимает первичный ключ, связанный с вашей аутентифицируемой моделью, или экземпляр этой модели:
use App\Models\User;
use Laravel\Dusk\Browser;
$this->browse(function (Browser $browser) {
$browser->loginAs(User::find(1))
->visit('/home');
});
После использования метода loginAs сессия пользователя будет сохраняться для всех тестов в файле.
#Куки
Метод cookie позволяет получить или установить значение зашифрованной куки. По умолчанию все куки, создаваемые Laravel, шифруются:
$browser->cookie('name');
$browser->cookie('name', 'Taylor');
Метод plainCookie позволяет получить или установить значение незашифрованной куки:
$browser->plainCookie('name');
$browser->plainCookie('name', 'Taylor');
Метод deleteCookie удаляет указанную куку:
$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');
#Сохранение вывода консоли на диск
Метод storeConsoleLog записывает текущий вывод консоли браузера на диск с указанным именем файла. Логи консоли сохраняются в директории tests/Browser/console:
$browser->storeConsoleLog('filename');
#Сохранение исходного кода страницы на диск
Метод storeSource записывает исходный код текущей страницы на диск с указанным именем файла. Исходный код страницы сохраняется в директории tests/Browser/source:
$browser->storeSource('filename');
#Взаимодействие с элементами
#Селекторы Dusk
Выбор хороших CSS-селекторов для взаимодействия с элементами — одна из самых сложных частей написания тестов Dusk. Со временем изменения во frontend могут привести к тому, что селекторы, подобные следующим, сломают ваши тесты:
// HTML...
<button>Login</button>
// Тест...
$browser->click('.login-page .container div > button');
Селекторы Dusk позволяют сосредоточиться на написании эффективных тестов, а не на запоминании CSS-селекторов. Чтобы определить селектор, добавьте атрибут dusk к вашему HTML-элементу. Затем при взаимодействии с браузером Dusk используйте префикс @ перед селектором, чтобы управлять этим элементом в тесте:
// HTML...
<button dusk="login-button">Login</button>
// Тест...
$browser->click('@login-button');
При необходимости вы можете изменить HTML-атрибут, используемый селектором Dusk, с помощью метода selectorHtmlAttribute. Обычно этот метод вызывается из метода boot вашего AppServiceProvider:
use Laravel\Dusk\Dusk;
Dusk::selectorHtmlAttribute('data-dusk');
#Текст, значения и атрибуты
#Получение и установка значений
Dusk предоставляет несколько методов для взаимодействия с текущим значением, отображаемым текстом и атрибутами элементов на странице. Например, чтобы получить "значение" элемента, соответствующего CSS- или Dusk-селектору, используйте метод value:
// Получить значение...
$value = $browser->value('selector');
// Установить значение...
$browser->value('selector', 'value');
Вы можете использовать метод inputValue, чтобы получить "значение" поля ввода с заданным именем:
$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');
Метод 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');
Передав array в качестве второго аргумента методу select, вы можете указать методу выбрать несколько вариантов:
$browser->select('categories', ['Art', 'Music']);
#Флажки (Checkboxes)
Чтобы отметить флажок, используйте метод check. Как и в других методах для ввода, полный CSS-селектор не обязателен. Если селектор не найден, Dusk ищет флажок с соответствующим атрибутом name:
$browser->check('terms');
Метод uncheck снимает отметку с флажка:
$browser->uncheck('terms');
#Радиокнопки
Чтобы выбрать опцию radio, используйте метод radio. Как и в других методах, полный CSS-селектор не обязателен. Если селектор не найден, Dusk ищет radio с соответствующими атрибутами name и value:
$browser->radio('size', 'large');
#Прикрепление файлов
Метод attach позволяет прикрепить файл к элементу ввода типа file. Как и в других методах, полный CSS-селектор не обязателен. Если селектор не найден, Dusk ищет элемент file с соответствующим атрибутом name:
$browser->attach('photo', __DIR__.'/photos/mountains.png');
Для работы функции attach необходимо, чтобы на сервере была установлена и включена PHP-расширение Zip.
#Нажатие кнопок
Метод press используется для нажатия на элемент кнопки на странице. Аргумент, передаваемый методу press, может быть либо текстом, отображаемым на кнопке, либо CSS- или Dusk-селектором:
$browser->press('Login');
При отправке форм многие приложения отключают кнопку отправки после нажатия и повторно включают её, когда HTTP-запрос формы завершён. Чтобы нажать кнопку и дождаться её повторного включения, можно использовать метод pressAndWaitFor:
// Нажать кнопку и ждать максимум 5 секунд, пока она не станет активной...
$browser->pressAndWaitFor('Save');
// Нажать кнопку и ждать максимум 1 секунду, пока она не станет активной...
$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
{
/**
* Регистрация макросов браузера Dusk.
*/
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');
#Наведение мыши
Метод 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 позволяет ждать появления диалога. Он принимает необязательный аргумент — количество секунд ожидания:
$browser->waitForDialog($seconds = null);
Метод assertDialogOpened проверяет, что диалог отображён и содержит указанное сообщение:
$browser->assertDialogOpened('Dialog message');
Если диалог содержит prompt, метод typeInDialog позволяет ввести значение в prompt:
$browser->typeInDialog('Hello World');
Чтобы закрыть открытый диалог нажатием кнопки "OK", вызовите метод acceptDialog:
$browser->acceptDialog();
Чтобы закрыть открытый диалог нажатием кнопки "Cancel", вызовите метод dismissDialog:
$browser->dismissDialog();
#Взаимодействие с inline-фреймами
Если нужно взаимодействовать с элементами внутри iframe, можно использовать метод withinFrame. Все взаимодействия с элементами, которые происходят внутри замыкания, переданного в метод withinFrame, будут выполняться в контексте указанного iframe:
$browser->withinFrame('#credit-card-details', function ($browser) {
$browser->type('input[name="cardnumber"]', '4242424242424242')
->type('input[name="exp-date"]', '12/24')
->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) {
// Текущая область — `body .table`...
$browser->elsewhere('.page-title', function (Browser $title) {
// Текущая область — `body .page-title`...
$title->assertSee('Hello World');
});
$browser->elsewhereWhenAvailable('.page-title', function (Browser $title) {
// Текущая область — `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-селектору, не появится на странице. По умолчанию ждёт максимум пять секунд, затем выбрасывает исключение. При необходимости можно передать кастомное время ожидания вторым аргументом:
// Ждать максимум пять секунд появления селектора...
$browser->waitFor('.selector');
// Ждать максимум одну секунду появления селектора...
$browser->waitFor('.selector', 1);
Также можно ждать, пока элемент с данным селектором не будет содержать указанный текст:
// Ждать максимум пять секунд, пока селектор не содержит текст...
$browser->waitForTextIn('.selector', 'Hello World');
// Ждать максимум одну секунду, пока селектор не содержит текст...
$browser->waitForTextIn('.selector', 'Hello World', 1);
Можно ждать, пока элемент с данным селектором исчезнет со страницы:
// Ждать максимум пять секунд, пока селектор не исчезнет...
$browser->waitUntilMissing('.selector');
// Ждать максимум одну секунду, пока селектор не исчезнет...
$browser->waitUntilMissing('.selector', 1);
Или ждать, пока элемент с данным селектором не станет активным или неактивным:
// Ждать максимум пять секунд, пока селектор не станет активным...
$browser->waitUntilEnabled('.selector');
// Ждать максимум одну секунду, пока селектор не станет активным...
$browser->waitUntilEnabled('.selector', 1);
// Ждать максимум пять секунд, пока селектор не станет неактивным...
$browser->waitUntilDisabled('.selector');
// Ждать максимум одну секунду, пока селектор не станет неактивным...
$browser->waitUntilDisabled('.selector', 1);
#Ограничение области действия селекторов при доступности
Иногда нужно дождаться появления элемента, соответствующего селектору, а затем взаимодействовать с ним. Например, дождаться появления модального окна и нажать кнопку "OK" внутри него. Для этого используется метод whenAvailable. Все операции внутри замыкания будут ограничены исходным селектором:
$browser->whenAvailable('.modal', function (Browser $modal) {
$modal->assertSee('Hello World')
->press('OK');
});
#Ожидание текста
Метод waitForText ждёт, пока указанный текст появится на странице:
// Ждать максимум пять секунд появления текста...
$browser->waitForText('Hello World');
// Ждать максимум одну секунду появления текста...
$browser->waitForText('Hello World', 1);
Метод waitUntilMissingText ждёт, пока указанный текст исчезнет со страницы:
// Ждать максимум пять секунд исчезновения текста...
$browser->waitUntilMissingText('Hello World');
// Ждать максимум одну секунду исчезновения текста...
$browser->waitUntilMissingText('Hello World', 1);
#Ожидание ссылок
Метод waitForLink ждёт, пока ссылка с указанным текстом появится на странице:
// Ждать максимум пять секунд появления ссылки...
$browser->waitForLink('Create');
// Ждать максимум одну секунду появления ссылки...
$browser->waitForLink('Create', 1);
#Ожидание полей ввода
Метод waitForInput ждёт, пока указанное поле ввода станет видимым на странице:
// Ждать максимум пять секунд появления поля...
$browser->waitForInput($field);
// Ждать максимум одну секунду появления поля...
$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 или точку с запятой:
// Ждать максимум пять секунд, пока выражение станет true...
$browser->waitUntil('App.data.servers.length > 0');
// Ждать максимум одну секунду, пока выражение станет true...
$browser->waitUntil('App.data.servers.length > 0', 1);
#Ожидание Vue-выражений
Методы waitUntilVue и waitUntilVueIsNot позволяют ждать, пока атрибут Vue-компонента не примет заданное значение:
// Ждать, пока атрибут компонента не будет содержать заданное значение...
$browser->waitUntilVue('user.name', 'Taylor', '@user');
// Ждать, пока атрибут компонента не перестанет содержать заданное значение...
$browser->waitUntilVueIsNot('user.name', null, '@user');
#Ожидание JavaScript-событий
Метод waitForEvent позволяет приостановить выполнение теста до наступления JavaScript-события:
$browser->waitForEvent('load');
Обработчик события привязывается к текущей области, по умолчанию это элемент body. При использовании ограниченного селектора обработчик привязывается к соответствующему элементу:
$browser->with('iframe', function (Browser $iframe) {
// Ждать событие load у iframe...
$iframe->waitForEvent('load');
});
Также можно передать селектор вторым аргументом методу waitForEvent, чтобы привязать слушатель события к конкретному элементу:
$browser->waitForEvent('load', '.selector');
Можно ждать события на объектах document и window:
// Ждать прокрутки документа...
$browser->waitForEvent('scroll', 'document');
// Ждать максимум пять секунд изменения размера окна...
$browser->waitForEvent('resize', 'window', 5);
#Ожидание с обратным вызовом
Многие методы «wait» в 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 предоставляет множество утверждений, которые можно использовать для проверки вашего приложения. Все доступные утверждения перечислены ниже:
<style> .collection-method-list > p { columns: 10.8em 3; -moz-columns: 10.8em 3; -webkit-columns: 10.8em 3; } .collection-method-list a { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } </style>assertTitle assertTitleContains assertUrlIs assertSchemeIs assertSchemeIsNot assertHostIs assertHostIsNot assertPortIs assertPortIsNot assertPathBeginsWith assertPathIs assertPathIsNot assertRouteIs assertQueryStringHas assertQueryStringMissing assertFragmentIs assertFragmentBeginsWith assertFragmentIsNot assertHasCookie assertHasPlainCookie assertCookieMissing assertPlainCookieMissing assertCookieValue assertPlainCookieValue assertSee assertDontSee assertSeeIn assertDontSeeIn assertSeeAnythingIn assertSeeNothingIn assertScript assertSourceHas assertSourceMissing assertSeeLink assertDontSeeLink assertInputValue assertInputValueIsNot assertChecked assertNotChecked assertIndeterminate assertRadioSelected assertRadioNotSelected assertSelected assertNotSelected assertSelectHasOptions assertSelectMissingOptions assertSelectHasOption assertSelectMissingOption assertValue assertValueIsNot assertAttribute assertAttributeContains assertAttributeDoesntContain assertAriaAttribute assertDataAttribute assertVisible assertPresent assertNotPresent assertMissing assertInputPresent assertInputMissing assertDialogOpened assertEnabled assertDisabled assertButtonEnabled assertButtonDisabled assertFocused assertNotFocused assertAuthenticated assertGuest assertAuthenticatedAs assertVue assertVueIsNot assertVueContains assertVueDoesntContain
#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');
#assertPathIs
Проверяет, что путь текущего URL совпадает с указанным:
$browser->assertPathIs('/home');
#assertPathIsNot
Проверяет, что путь текущего URL не совпадает с указанным:
$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');
#assertHasCookie
Проверяет, что заданный зашифрованный cookie присутствует:
$browser->assertHasCookie($name);
#assertHasPlainCookie
Проверяет, что заданный незашифрованный cookie присутствует:
$browser->assertHasPlainCookie($name);
#assertCookieMissing
Проверяет, что заданный зашифрованный cookie отсутствует:
$browser->assertCookieMissing($name);
#assertPlainCookieMissing
Проверяет, что заданный незашифрованный cookie отсутствует:
$browser->assertPlainCookieMissing($name);
#assertCookieValue
Проверяет, что зашифрованный cookie имеет заданное значение:
$browser->assertCookieValue($name, $value);
#assertPlainCookieValue
Проверяет, что незашифрованное 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);
#assertScript
Проверяет, что заданное JavaScript-выражение возвращает указанное значение:
$browser->assertScript('window.isLoaded')
->assertScript('document.readyState', 'complete');
#assertSourceHas
Проверяет, что указанный исходный код присутствует на странице:
$browser->assertSourceHas($code);
#assertSourceMissing
Проверяет, что указанный исходный код отсутствует на странице:
$browser->assertSourceMissing($code);
#assertSeeLink
Проверяет, что указанная ссылка присутствует на странице:
$browser->assertSeeLink($linkText);
#assertDontSeeLink
Проверяет, что указанная ссылка отсутствует на странице:
$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);
#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-label следующим образом:
$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 компонента следующим образом:
/**
* Пример базового теста Vue.
*/
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);
#Pages
Иногда для тестов требуется выполнить несколько сложных действий последовательно. Это может усложнить чтение и понимание тестов. Dusk Pages позволяют определить выразительные действия, которые затем можно выполнить на странице одним методом. Pages также позволяют определить сокращения для часто используемых селекторов вашего приложения или отдельной страницы.
#Генерация страниц
Чтобы сгенерировать объект страницы, выполните Artisan-команду dusk:page. Все объекты страниц будут размещены в директории tests/Browser/Pages вашего приложения:
php artisan dusk:page Login
#Настройка страниц
По умолчанию страницы имеют три метода: url, assert и elements. Сейчас мы рассмотрим методы url и assert. Метод elements будет подробно рассмотрен ниже.
#Метод url
Метод url должен возвращать путь URL, который представляет страницу. Dusk будет использовать этот URL при переходе на страницу в браузере:
/**
* Получить URL страницы.
*/
public function url(): string
{
return '/login';
}
#Метод assert
Метод assert может выполнять любые проверки, необходимые для подтверждения, что браузер действительно находится на данной странице. На самом деле не обязательно помещать что-либо в этот метод, но вы можете делать проверки по желанию. Эти проверки будут выполняться автоматически при переходе на страницу:
/**
* Проверить, что браузер находится на странице.
*/
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" на странице входа приложения:
/**
* Получить сокращения элементов для страницы.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@email' => 'input[name=email]',
];
}
После определения сокращения вы можете использовать сокращённый селектор везде, где обычно используете полный CSS-селектор:
$browser->type('@email', 'taylor@laravel.com');
#Глобальные сокращённые селекторы
После установки Dusk в директории tests/Browser/Pages появится базовый класс Page. В нём есть метод siteElements, который можно использовать для определения глобальных сокращённых селекторов, доступных на всех страницах вашего приложения:
/**
* Получить глобальные сокращения элементов для сайта.
*
* @return array<string, string>
*/
public static function siteElements(): array
{
return [
'@element' => '#selector',
];
}
#Методы страниц
Помимо стандартных методов страниц, вы можете определить дополнительные методы, которые будут использоваться в тестах. Например, представим, что вы создаёте приложение для управления музыкой. Частое действие на одной из страниц — создание плейлиста. Вместо того чтобы повторять логику создания плейлиста в каждом тесте, вы можете определить метод createPlaylist в классе страницы:
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
class Dashboard extends Page
{
// Другие методы страницы...
/**
* Создать новый плейлист.
*/
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');
#Components
Компоненты похожи на «объекты страниц» Dusk, но предназначены для частей интерфейса и функционала, которые повторно используются в приложении, например, навигационная панель или окно уведомлений. Компоненты не привязаны к конкретным URL.
#Генерация компонентов
Чтобы сгенерировать компонент, выполните Artisan-команду 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
{
/**
* Получить корневой селектор компонента.
*/
public function selector(): string
{
return '.date-picker';
}
/**
* Проверить, что страница браузера содержит компонент.
*/
public function assert(Browser $browser): void
{
$browser->assertVisible($this->selector());
}
/**
* Получить сокращения элементов компонента.
*
* @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',
];
}
/**
* Выбрать заданную дату.
*/
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);
});
}
}
#Использование компонентов
После определения компонента мы можем легко выбрать дату в date picker из любого теста. Если логика выбора даты изменится, достаточно обновить только компонент:
<?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
{
/**
* Пример базового теста компонента.
*/
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');
});
}
}
#Непрерывная интеграция
Большинство конфигураций непрерывной интеграции Dusk ожидают, что ваше Laravel-приложение будет запущено с использованием встроенного PHP-сервера разработки на порту 8000. Поэтому перед продолжением убедитесь, что в вашей среде CI установлена переменная окружения APP_URL со значением http://127.0.0.1:8000.
#Heroku CI
Чтобы запускать Dusk-тесты на Heroku CI, добавьте следующий Google Chrome buildpack и скрипты в файл app.json вашего Heroku-приложения:
{
"environments": {
"test": {
"buildpacks": [
{ "url": "heroku/php" },
{ "url": "https://github.com/heroku/heroku-buildpack-google-chrome" }
],
"scripts": {
"test-setup": "cp .env.testing .env",
"test": "nohup bash -c './vendor/laravel/dusk/bin/chromedriver-linux > /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:
- 7.3
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
Если вы используете GitHub Actions для запуска Dusk-тестов, вы можете использовать следующий файл конфигурации в качестве отправной точки. Как и в 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@v4
- 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 &
- 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@v2
with:
name: screenshots
path: tests/Browser/screenshots
- name: Upload Console Logs
if: failure()
uses: actions/upload-artifact@v2
with:
name: console
path: tests/Browser/console
#Chipper CI
Если вы используете Chipper CI для запуска Dusk-тестов, вы можете использовать следующий файл конфигурации в качестве отправной точки. Мы будем использовать встроенный PHP-сервер для запуска Laravel, чтобы слушать запросы:
# файл .chipperci.yml
version: 1
environment:
php: 8.2
node: 16
# Включить Chrome в среду сборки
services:
- dusk
# Собирать все коммиты
on:
push:
branches: .*
pipeline:
- name: Setup
cmd: |
cp -v .env.example .env
composer install --no-interaction --prefer-dist --optimize-autoloader
php artisan key:generate
# Создать dusk env файл, убедившись, что APP_URL использует 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.