Skip to content

Laravel Dusk

はじめに

Laravel Dusk は、表現力豊かで使いやすいブラウザ自動化およびテストAPIを提供します。デフォルトでは、DuskはローカルコンピュータにJDKやSeleniumをインストールする必要はありません。代わりに、DuskはスタンドアロンのChromeDriverを使用します。ただし、他のSelenium互換のドライバを自由に使用することもできます。

インストール

始めるには、Google Chromeをインストールし、laravel/dusk Composer依存関係をプロジェクトに追加する必要があります:

composer require laravel/dusk --dev

Warning

Duskのサービスプロバイダを手動で登録する場合、本番環境では決して登録しないでください。登録すると、任意のユーザーがアプリケーションに認証できるようになる可能性があります。

Duskパッケージをインストールした後、dusk:install Artisanコマンドを実行します。dusk:installコマンドは、tests/Browserディレクトリ、Duskテストの例、およびオペレーティングシステム用のChrome Driverバイナリをインストールします:

php artisan dusk:install

次に、アプリケーションの.envファイルでAPP_URL環境変数を設定します。この値は、ブラウザでアプリケーションにアクセスするために使用するURLと一致する必要があります。

Note

ローカル開発環境を管理するためにLaravel Sailを使用している場合は、Duskテストの設定と実行に関するSailのドキュメントも参照してください。

ChromeDriverのインストール管理

Laravel Duskによってdusk:installコマンドでインストールされるものとは異なるバージョンのChromeDriverをインストールしたい場合は、dusk:chrome-driverコマンドを使用できます:

# オペレーティングシステム用の最新バージョンのChromeDriverをインストール...
php artisan dusk:chrome-driver

# オペレーティングシステム用の特定のバージョンのChromeDriverをインストール...
php artisan dusk:chrome-driver 86

# サポートされているすべてのOS用の特定のバージョンのChromeDriverをインストール...
php artisan dusk:chrome-driver --all

# オペレーティングシステムで検出されたChrome / Chromiumのバージョンに一致するChromeDriverをインストール...
php artisan dusk:chrome-driver --detect

Warning

Duskはchromedriverバイナリが実行可能である必要があります。Duskの実行に問題がある場合は、次のコマンドを使用してバイナリが実行可能であることを確認してください: chmod -R 0755 vendor/laravel/dusk/bin/

他のブラウザの使用

デフォルトでは、DuskはGoogle ChromeとスタンドアロンのChromeDriverを使用してブラウザテストを実行します。ただし、独自のSeleniumサーバーを起動し、任意のブラウザに対してテストを実行することもできます。

始めるには、アプリケーションのベースDuskテストケースであるtests/DuskTestCase.phpファイルを開きます。このファイル内で、startChromeDriverメソッドの呼び出しを削除します。これにより、Duskが自動的にChromeDriverを起動するのを停止します:

/**
 * Duskテスト実行の準備。
 *
 * @beforeClass
 */
public static function prepare(): void
{
    // static::startChromeDriver();
}

次に、driverメソッドを変更して、選択したURLとポートに接続できるようにします。さらに、WebDriverに渡すべき「desired capabilities」を変更することもできます:

use Facebook\WebDriver\Remote\RemoteWebDriver;

/**
 * RemoteWebDriverインスタンスの作成。
 */
protected function driver(): RemoteWebDriver
{
    return RemoteWebDriver::create(
        'http://localhost:4444/wd/hub', DesiredCapabilities::phantomjs()
    );
}

はじめる

テストの生成

Duskテストを生成するには、dusk:make Artisanコマンドを使用します。生成されたテストはtests/Browserディレクトリに配置されます:

php artisan dusk:make LoginTest

各テスト後のデータベースリセット

書くほとんどのテストは、アプリケーションのデータベースからデータを取得するページと対話します。ただし、DuskテストはRefreshDatabaseトレイトを使用してはいけません。RefreshDatabaseトレイトは、HTTPリクエスト全体で適用または利用できないデータベーストランザクションを利用します。代わりに、DatabaseMigrationsトレイトとDatabaseTruncationトレイトの2つのオプションがあります。

データベースマイグレーションの使用

DatabaseMigrationsトレイトは、各テストの前にデータベースマイグレーションを実行します。ただし、各テストのたびにデータベーステーブルをドロップして再作成することは、通常、テーブルを切り捨てるよりも遅くなります:

<?php

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

uses(DatabaseMigrations::class);

//
<?php

namespace Tests\Browser;

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

class ExampleTest extends DuskTestCase
{
    use DatabaseMigrations;

    //
}

Warning

Duskテストを実行する際にSQLiteインメモリデータベースを使用することはできません。ブラウザは独自のプロセス内で実行されるため、他のプロセスのインメモリデータベースにアクセスできません。

データベース切り捨ての使用

DatabaseTruncationトレイトは、最初のテストでデータベースをマイグレーションして、データベーステーブルが正しく作成されていることを確認します。ただし、後続のテストでは、データベーステーブルは単に切り捨てられます - すべてのデータベースマイグレーションを再実行するよりも高速化を提供します:

<?php

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

uses(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プロパティを定義できます:

Note

Pestを使用している場合、プロパティやメソッドはベースのDuskTestCaseクラスまたはテストファイルが拡張するクラスに定義する必要があります。

/**
 * 切り捨てるテーブルを示す。
 *
 * @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
{
    //
}

テストの実行

ブラウザテストを実行するには、dusk Artisanコマンドを実行します:

php artisan dusk

前回のduskコマンド実行時にテストが失敗した場合、dusk:failsコマンドを使用して失敗したテストを最初に再実行することで時間を節約できます:

php artisan dusk:fails

duskコマンドは、Pest / PHPUnitテストランナーが通常受け入れる引数を受け入れます。例えば、特定のグループのテストのみを実行できます:

php artisan dusk --group=foo

Note

ローカル開発環境を管理するためにLaravel Sailを使用している場合は、SailのドキュメントでDuskテストの設定と実行に関する情報を確認してください。

ChromeDriverの手動起動

デフォルトでは、Duskは自動的にChromeDriverを起動しようとします。特定のシステムでこれが機能しない場合は、duskコマンドを実行する前に手動でChromeDriverを起動することができます。ChromeDriverを手動で起動することを選択した場合は、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}ファイルを作成します。例えば、local環境からduskコマンドを開始する場合は、.env.dusk.localファイルを作成する必要があります。

テストを実行すると、Duskは.envファイルをバックアップし、Dusk環境を.envに名前変更します。テストが完了すると、.envファイルが復元されます。

ブラウザの基本

ブラウザの作成

まず、アプリケーションにログインできることを確認するテストを書いてみましょう。テストを生成した後、ログインページに移動し、いくつかの資格情報を入力し、「ログイン」ボタンをクリックするように変更できます。ブラウザインスタンスを作成するには、Duskテスト内からbrowseメソッドを呼び出します:

<?php

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

uses(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;

    /**
     * 基本的なブラウザテストの例。
     */
    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によってブラウザインスタンスが自動的にこのクロージャに渡され、このオブジェクトはアプリケーションとの対話やアサーションを行うために使用される主要なオブジェクトです。

複数のブラウザの作成

場合によっては、テストを正しく実行するために複数のブラウザが必要になることがあります。例えば、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($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);

ブラウザマクロ

さまざまなテストで再利用できるカスタムブラウザメソッドを定義したい場合は、Browserクラスのmacroメソッドを使用できます。通常、このメソッドはサービスプロバイダ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関数は、最初の引数として名前を受け取り、2番目の引数としてクロージャを受け取ります。マクロのクロージャは、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');
});

Warning

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');

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>

// テスト...

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

Duskセレクタを使用すると、CSSセレクタを覚える代わりに効果的なテストを書くことに集中できます。セレクタを定義するには、HTML要素にdusk属性を追加します。そして、Duskブラウザとインタラクションする際に、セレクタに@を付けてテスト内の添付された要素を操作します。

// HTML...

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

// テスト...

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

必要に応じて、Duskセレクタが使用するHTML属性をselectorHtmlAttributeメソッドでカスタマイズできます。通常、このメソッドはアプリケーションのAppServiceProviderbootメソッドから呼び出すべきです。

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');

typeメソッドにCSSセレクタを必ずしも渡す必要はないことに注意してください。CSSセレクタが提供されない場合、Duskは指定されたname属性を持つinputまたはtextareaフィールドを検索します。

フィールドの内容をクリアせずにテキストを追加するには、appendメソッドを使用できます。

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

入力の値をクリアするには、clearメソッドを使用します。

$browser->clear('email');

typeSlowlyメソッドを使用して、Duskにゆっくりと入力させることができます。デフォルトでは、Duskはキー入力間に100ミリ秒待機します。キー入力間の時間をカスタマイズするには、メソッドの3番目の引数として適切なミリ秒数を渡すことができます。

$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');

2番目の引数を省略することで、ランダムなオプションを選択できます。

$browser->select('size');

selectメソッドの2番目の引数として配列を渡すことで、複数のオプションを選択するように指示できます。

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

チェックボックス

チェックボックス入力を「チェック」するには、checkメソッドを使用できます。他の多くの入力関連メソッドと同様に、完全なCSSセレクタは必要ありません。CSSセレクタの一致が見つからない場合、Duskは一致するname属性を持つチェックボックスを検索します。

$browser->check('terms');

チェックボックス入力の「チェックを外す」には、uncheckメソッドを使用できます。

$browser->uncheck('terms');

ラジオボタン

radio入力オプションを「選択」するには、radioメソッドを使用できます。他の多くの入力関連メソッドと同様に、完全なCSSセレクタは必要ありません。CSSセレクタの一致が見つからない場合、Duskは一致するnamevalue属性を持つradio入力を検索します。

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

ファイルの添付

attachメソッドは、file入力要素にファイルを添付するために使用できます。他の多くの入力関連メソッドと同様に、完全なCSSセレクタは必要ありません。CSSセレクタの一致が見つからない場合、Duskは一致するname属性を持つfile入力を検索します。

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

Warning

attach関数を使用するには、Zip PHP拡張機能がサーバーにインストールされ、有効になっている必要があります。

ボタンの押下

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)) {
    // ...
}

Warning

これらのメソッドはjQueryとインタラクションします。ページ上でjQueryが利用できない場合、Duskはテストの期間中、自動的にページに注入します。

キーボードの使用

keysメソッドを使用すると、typeメソッドで通常許可されるよりも複雑な入力シーケンスを指定された要素に提供できます。例えば、修飾キーを押しながら値を入力するようにDuskに指示できます。この例では、shiftキーが押された状態でtaylorが指定されたセレクタに一致する要素に入力されます。taylorが入力された後、swiftが修飾キーなしで入力されます。

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

keysメソッドのもう一つの価値ある使用例は、アプリケーションのプライマリCSSセレクタに「キーボードショートカット」の組み合わせを送信することです。

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

Note

すべての修飾キー(例:{command})は{}文字で囲まれ、Facebook\WebDriver\WebDriverKeysクラスで定義された定数に一致します。これはGitHubで見つけることができます。

フルエントなキーボードインタラクション

Duskはまた、Laravel\Dusk\Keyboardクラスを介して複雑なキーボードインタラクションをフルエントに実行できるwithKeyboardメソッドも提供します。Keyboardクラスは、pressreleasetype、およびpauseメソッドを提供します。

use Laravel\Dusk\Keyboard;

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

キーボードマクロ

テストスイート全体で簡単に再利用できるカスタムキーボードインタラクションを定義したい場合は、Keyboardクラスが提供するmacroメソッドを使用できます。通常、このメソッドはサービスプロバイダbootメソッドから呼び出すべきです。

<?php

namespace App\Providers;

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

class AppServiceProvider extends ServiceProvider
{
    /**
     * 全アプリケーションサービスの初期化処理
     *
     * @return void
     */
    public function boot()
    {
        Keyboard::macro('openSearch', function () {
            $this->press(WebDriverKeys::CONTROL)
                ->press('f')
                ->release(WebDriverKeys::CONTROL)
                ->release('f');
        });
    }
}
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関数は、最初の引数として名前を、2番目の引数としてクロージャを受け取ります。マクロのクロージャは、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メソッドを使用して、JavaScriptダイアログが表示されるのを待つことができます。このメソッドは、ダイアログが表示されるまで待機する秒数を示すオプションの引数を受け取ります。

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

assertDialogOpenedメソッドは、ダイアログが表示され、指定されたメッセージを含むことをアサートするために使用できます。

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

JavaScriptダイアログにプロンプトが含まれている場合、typeInDialogメソッドを使用してプロンプトに値を入力できます。

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

"OK"ボタンをクリックして開いているJavaScriptダイアログを閉じるには、acceptDialogメソッドを呼び出すことができます。

$browser->acceptDialog();

"キャンセル"ボタンをクリックして開いているJavaScriptダイアログを閉じるには、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) {
    // 現在のスコープは `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セレクタに一致する要素がページに表示されるまでテストの実行を一時停止するために使用できます。デフォルトでは、これによりテストは最大5秒間一時停止され、その後例外がスローされます。必要に応じて、メソッドの2番目の引数としてカスタムタイムアウトしきい値を渡すことができます。

// セレクタを最大5秒間待機...
$browser->waitFor('.selector');

// セレクタを最大1秒間待機...
$browser->waitFor('.selector', 1);

指定されたセレクタに一致する要素が指定されたテキストを含むまで待機することもできます。

// セレクタが指定されたテキストを含むまで最大5秒間待機...
$browser->waitForTextIn('.selector', 'Hello World');

// セレクタが指定されたテキストを含むまで最大1秒間待機...
$browser->waitForTextIn('.selector', 'Hello World', 1);

指定されたセレクタがページから欠落するまで待機することもできます。

// セレクタが欠落するまで最大5秒間待機...
$browser->waitUntilMissing('.selector');

// セレクタが欠落するまで最大1秒間待機...
$browser->waitUntilMissing('.selector', 1);

または、指定されたセレクタが有効または無効になるまで待機することもできます。

// セレクタが有効になるまで最大5秒間待機...
$browser->waitUntilEnabled('.selector');

// セレクタが有効になるまで最大1秒間待機...
$browser->waitUntilEnabled('.selector', 1);

// セレクタが無効になるまで最大5秒間待機...
$browser->waitUntilDisabled('.selector');

// セレクタが無効になるまで最大1秒間待機...
$browser->waitUntilDisabled('.selector', 1);

利用可能なセレクタのスコープ

指定されたセレクタに一致する要素が表示されるのを待ち、その要素と対話する必要がある場合があります。例えば、モーダルウィンドウが利用可能になるのを待ち、そのモーダル内の"OK"ボタンを押したい場合があります。whenAvailableメソッドを使用してこれを実現できます。クロージャ内で行われるすべての要素操作は、元のセレクタにスコープされます。

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

テキストの待機

waitForTextメソッドは、指定されたテキストがページに表示されるまで待機するために使用できます。

// テキストを最大5秒間待機...
$browser->waitForText('Hello World');

// テキストを最大1秒間待機...
$browser->waitForText('Hello World', 1);

表示されたテキストがページから削除されるまで待機するには、waitUntilMissingTextメソッドを使用できます。

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

// テキストが削除されるまで最大5秒間待つ...
$browser->waitUntilMissingText('Hello World');

// テキストが削除されるまで最大1秒間待つ...
$browser->waitUntilMissingText('Hello World', 1);

リンクの待機

waitForLinkメソッドは、指定されたリンクテキストがページに表示されるまで待機するために使用できます。

// リンクが表示されるまで最大5秒間待つ...
$browser->waitForLink('Create');

// リンクが表示されるまで最大1秒間待つ...
$browser->waitForLink('Create', 1);

入力の待機

waitForInputメソッドは、指定された入力フィールドがページに表示されるまで待機するために使用できます。

// 入力が表示されるまで最大5秒間待つ...
$browser->waitForInput($field);

// 入力が表示されるまで最大1秒間待つ...
$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になるまで最大5秒間待つ...
$browser->waitUntil('App.data.servers.length > 0');

// 式がtrueになるまで最大1秒間待つ...
$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) {
    // iframeのloadイベントを待つ...
    $iframe->waitForEvent('load');
});

waitForEventメソッドの2番目の引数としてセレクターを指定することで、特定の要素にイベントリスナーをアタッチできます。

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

documentおよびwindowオブジェクトのイベントを待機することもできます。

// ドキュメントがスクロールされるまで待つ...
$browser->waitForEvent('scroll', 'document');

// ウィンドウがリサイズされるまで最大5秒間待つ...
$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');

assertHasCookie

指定された暗号化されたクッキーが存在することをアサートします:

$browser->assertHasCookie($name);

assertHasPlainCookie

指定された暗号化されていないクッキーが存在することをアサートします:

$browser->assertHasPlainCookie($name);

assertCookieMissing

指定された暗号化されたクッキーが存在しないことをアサートします:

$browser->assertCookieMissing($name);

assertPlainCookieMissing

指定された暗号化されていないクッキーが存在しないことをアサートします:

$browser->assertPlainCookieMissing($name);

assertCookieValue

暗号化されたクッキーが指定された値を持つことをアサートします:

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

assertPlainCookieValue

暗号化されていないクッキーが指定された値を持つことをアサートします:

$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);

指定されたリンクがページ上に存在することをアサートします:

$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);

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

指定されたセレクタに一致する要素が指定されたデータ属性に指定された値を持つことをアサートします:

$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>

// コンポーネント定義...

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');
    });
});
/**
 * 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);

ページ

テストによっては、いくつかの複雑なアクションを順番に実行する必要がある場合があります。これにより、テストが読みにくく、理解しにくくなる可能性があります。Duskのページを使用すると、単一のメソッドで実行できる表現力豊かなアクションを定義できます。ページでは、アプリケーションまたは単一のページの一般的なセレクターへのショートカットを定義することもできます。

ページの生成

ページオブジェクトを生成するには、dusk:page Artisanコマンドを実行します。すべてのページオブジェクトは、アプリケーションのtests/Browser/Pagesディレクトリに配置されます:

php artisan dusk:page Login

ページの設定

デフォルトでは、ページにはurlassertelementsの3つのメソッドがあります。ここでは、urlassertメソッドについて説明します。elementsメソッドについては、後ほど詳しく説明します

urlメソッド

urlメソッドは、ページを表すURLのパスを返す必要があります。Duskは、ブラウザでこのURLに移動するときにこの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をインストールすると、ベースのPageクラスがtests/Browser/Pagesディレクトリに配置されます。このクラスには、アプリケーション全体で利用可能なグローバルショートハンドセレクタを定義するために使用できるsiteElementsメソッドが含まれています:

/**
 * サイトのグローバル要素ショートカットを取得します。
 *
 * @return array<string, string>
 */
public static function siteElements(): array
{
    return [
        '@element' => '#selector',
    ];
}

ページメソッド

ページには、デフォルトで定義されているメソッドに加えて、テスト全体で使用できる追加のメソッドを定義することもできます。例えば、音楽管理アプリケーションを構築しているとします。アプリケーションの1つのページで一般的なアクションは、プレイリストを作成することです。各テストでプレイリストを作成するロジックを繰り返し書く代わりに、ページクラスにcreatePlaylistメソッドを定義できます:

<?php

namespace Tests\Browser\Pages;

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

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');

コンポーネント

コンポーネントはDuskの「ページオブジェクト」に似ていますが、アプリケーション全体で再利用されるUIや機能の部品、例えばナビゲーションバーや通知ウィンドウに対して意図されています。そのため、コンポーネントは特定のURLにバインドされません。

コンポーネントの生成

コンポーネントを生成するには、dusk:component Artisanコマンドを実行します。新しいコンポーネントは、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);
                });
    }
}

コンポーネントの使用

コンポーネントが定義されたら、テスト内で日付ピッカーから簡単に日付を選択できます。また、日付選択に必要なロジックが変更された場合、コンポーネントのみを更新する必要があります。

<?php

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

uses(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
{
    /**
     * 基本的なコンポーネントテストの例。
     */
    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');
        });
    }
}

継続的インテグレーション

Warning

ほとんどのDusk継続的インテグレーションの設定では、Laravelアプリケーションが組み込みのPHP開発サーバーを使用してポート8000で提供されることを期待しています。したがって、続行する前に、継続的インテグレーション環境にAPP_URL環境変数の値がhttp://127.0.0.1:8000であることを確認する必要があります。

Heroku CI

Heroku CIでDuskテストを実行するには、以下のGoogle Chromeビルドパックとスクリプトをherokuのapp.jsonファイルに追加します:

{
  "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

Travis CIでDuskテストを実行するには、以下の.travis.yml設定を使用します。Travis CIはグラフィカル環境ではないため、Chromeブラウザを起動するためにいくつかの追加手順が必要です。さらに、php artisan serveを使用してPHPの組み込みWebサーバーを起動します。

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

GitHub Actionsを使用してDuskテストを実行する場合、以下の設定ファイルを出発点として使用できます。TravisCIと同様に、php artisan serveコマンドを使用してPHPの組み込みWebサーバーを起動します。

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 --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

Chipper CIを使用してDuskテストを実行する場合、以下の設定ファイルを出発点として使用できます。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

Chipper CIでDuskテストを実行する方法、特にデータベースの使用方法について詳しく知るには、公式のChipper CIドキュメントを参照してください。

ユーザーノート