Laravel Dusk
번역일: 2026년 7월 2일
Laravel Dusk
소개
Laravel Dusk는 표현력 있고 사용하기 쉬운 브라우저 자동화 및 테스트 도구입니다. Dusk를 사용하면 Selenium이나 WebDriver를 별도로 설치할 필요 없이 실제 브라우저 환경에서 애플리케이션을 테스트할 수 있습니다. 기본적으로 ChromeDriver를 사용하지만, 다른 WebDriver 호환 드라이버로도 교체할 수 있습니다.
Dusk가 특히 유용한 상황은 Vue.js, Alpine.js 같은 프론트엔드 프레임워크를 사용하는 SPA나, JavaScript 상태 변화에 따라 동작이 달라지는 인터랙티브한 화면을 테스트할 때입니다. 순수 HTTP 요청 기반의 테스트(PHPUnit의 기능 테스트)로는 검증하기 어려운 UI 동작을 실제 브라우저로 직접 확인할 수 있습니다.
설치
먼저 프로젝트에 google/chrome-php가 설치되어 있는지 확인하고, Composer로 Dusk 패키지를 추가합니다:
composer require laravel/dusk --devWARNING
Dusk 서비스 프로바이더를 수동으로 등록하는 경우, 절대 프로덕션 환경에 등록하지 마십시오. 등록되면 외부 사용자가 애플리케이션에 임의로 인증할 수 있는 보안 취약점이 생깁니다.
Dusk 패키지를 설치한 후 dusk:install Artisan 명령어를 실행합니다. 이 명령어는 tests/Browser 디렉터리와 예제 테스트 파일, 그리고 운영 체제에 맞는 Chrome Driver 바이너리를 설치합니다:
php artisan dusk:install그다음, 애플리케이션의 .env 파일에서 APP_URL 환경 변수를 설정합니다. 이 값은 브라우저에서 실제로 접속하는 URL과 일치해야 합니다.
NOTE
로컬 개발 환경으로 Laravel Sail을 사용하는 경우, Dusk 테스트 설정 및 실행에 관한 Sail 공식 문서도 함께 참고하세요.
ChromeDriver 설치 관리
dusk:install 명령어로 설치되는 ChromeDriver 버전과 시스템에 설치된 Chrome 버전이 맞지 않아 테스트가 실패하는 경우가 있습니다. 이때는 dusk:chrome-driver 명령어로 ChromeDriver 버전을 직접 관리할 수 있습니다:
# 현재 OS에 맞는 최신 ChromeDriver 설치php artisan dusk:chrome-driver# 현재 OS에 맞는 특정 버전 ChromeDriver 설치php artisan dusk:chrome-driver 86# 모든 OS용 특정 버전 ChromeDriver 설치php artisan dusk:chrome-driver --all# 설치된 Chrome/Chromium 버전에 맞는 ChromeDriver 자동 감지 후 설치php artisan dusk:chrome-driver --detectWARNING
Dusk를 실행하려면 chromedriver 바이너리에 실행 권한이 있어야 합니다. 실행이 안 된다면 chmod -R 0755 vendor/laravel/dusk/bin/ 명령으로 권한을 부여하세요.
다른 브라우저 사용하기
Dusk는 기본적으로 Chrome과 ChromeDriver를 사용하지만, WebDriver 프로토콜을 지원하는 다른 브라우저도 사용할 수 있습니다.
Firefox 등 다른 브라우저를 사용하려면 tests/DuskTestCase.php 파일을 열어 startChromeDriver 메서드 호출을 제거하고, driver 메서드에서 원하는 브라우저로 연결되도록 수정합니다:
use Facebook\WebDriver\Firefox\FirefoxOptions;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\WebDriverCapabilityType;
/**
* RemoteWebDriver 인스턴스를 생성합니다.
*/
protected function driver(): RemoteWebDriver
{
$options = (new FirefoxOptions)->addArguments(['-headless']);
return RemoteWebDriver::create(
'http://localhost:4444/wd/hub',
DesiredCapabilities::firefox()->setCapability(
FirefoxOptions::CAPABILITY, $options
)
);
}이 경우 Firefox용 GeckoDriver 등 해당 브라우저의 WebDriver를 별도로 실행해야 합니다.
시작하기
테스트 생성
Dusk 테스트를 생성하려면 dusk:make Artisan 명령어를 사용합니다. 생성된 테스트 파일은 tests/Browser 디렉터리에 위치합니다:
php artisan dusk:make LoginTest테스트 후 데이터베이스 초기화
작성하는 테스트 대부분은 데이터베이스에서 데이터를 가져오는 페이지와 상호작용하게 됩니다. 그런데 Dusk 테스트는 RefreshDatabase 트레이트를 사용하지 않아야 합니다. RefreshDatabase 트레이트는 HTTP 요청 간에 유효하지 않거나 적용할 수 없는 데이터베이스 트랜잭션을 활용하기 때문입니다.
Dusk 테스트에서 데이터베이스를 초기화하는 방법은 두 가지입니다.
DatabaseMigrations 트레이트 사용
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 인메모리 데이터베이스는 사용할 수 없습니다. 브라우저가 별도의 프로세스로 실행되기 때문에, 인메모리 데이터베이스에는 접근할 수 없습니다. 로컬에서는 MySQL이나 PostgreSQL 등 실제 데이터베이스를 사용하세요.
DatabaseTruncation 트레이트 사용
DatabaseTruncation 트레이트는 첫 번째 테스트 실행 전에 마이그레이션을 수행하고, 이후 테스트부터는 마이그레이션을 다시 실행하는 대신 테이블을 비우는(truncate) 방식으로 초기화합니다. 마이그레이션 전체를 반복 실행하는 것보다 훨씬 빠릅니다:
<?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 프로퍼티를 사용합니다:
/**
* truncate 대상에서 제외할 테이블 목록입니다.
*
* @var array
*/
protected $exceptTables = ['users'];truncate 대상 데이터베이스 커넥션을 지정하려면 $connectionsToTruncate 프로퍼티를 정의합니다:
/**
* truncate를 적용할 커넥션 목록입니다.
*
* @var array
*/
protected $connectionsToTruncate = ['mysql'];truncate 전후에 특정 로직을 실행하고 싶다면 beforeTruncatingDatabase나 afterTruncatingDatabase 메서드를 오버라이드하세요:
/**
* 데이터베이스 truncate 전에 실행할 작업입니다.
*/
protected function beforeTruncatingDatabase(): void
{
//
}
/**
* 데이터베이스 truncate 후에 실행할 작업입니다.
*/
protected function afterTruncatingDatabase(): void
{
//
}테스트 실행
브라우저 테스트를 실행하려면 dusk Artisan 명령어를 사용합니다:
php artisan dusk마지막 실행에서 실패한 테스트를 먼저 재실행하고 싶다면 dusk:fails 명령어를 사용합니다. 실패한 테스트가 모두 통과하면 나머지 테스트를 이어서 실행합니다:
php artisan dusk:failsdusk 명령어는 PHPUnit 테스트 러너가 지원하는 옵션을 그대로 사용할 수 있습니다. 예를 들어 특정 그룹의 테스트만 실행할 수 있습니다:
php artisan dusk --group=fooNOTE
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 파일을 백업해두고 .env.dusk.{environment} 파일을 .env로 이름을 바꿔 사용합니다. 테스트가 완료되면 원래 .env 파일을 복원합니다.
브라우저 기본 조작
브라우저 생성
먼저 애플리케이션에 로그인할 수 있는지 확인하는 테스트를 작성해 보겠습니다. 테스트를 생성한 후 로그인 페이지로 이동하고, 자격 증명을 입력하여 "로그인" 버튼을 클릭하도록 수정합니다. 브라우저 인스턴스는 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' => 'test@example.com',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('로그인')
->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' => 'test@example.com',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('email', $user->email)
->type('password', 'password')
->press('로그인')
->assertPathIs('/home');
});
}
}위 예제에서 볼 수 있듯이, browse 메서드는 클로저를 인수로 받습니다. Dusk는 브라우저 인스턴스를 자동으로 생성하여 이 클로저에 전달합니다. 이 브라우저 인스턴스를 통해 애플리케이션과 상호작용하고 어설션을 수행합니다.
여러 브라우저 동시 생성
때로는 여러 브라우저 인스턴스가 동시에 필요할 수 있습니다. 예를 들어 WebSocket을 사용하는 실시간 채팅 기능을 테스트하려면 두 개의 브라우저가 필요합니다. 여러 브라우저를 생성하려면 browse 메서드의 클로저 시그니처에 인수를 추가하면 됩니다:
$this->browse(function (Browser $first, Browser $second) {
$first->loginAs(User::find(1))
->visit('/home')
->waitForText('메시지');
$second->loginAs(User::find(2))
->visit('/home')
->waitForText('메시지')
->type('#message', '안녕하세요!')
->press('전송');
$first->waitForText('안녕하세요!')
->assertSee('김철수');
});페이지 이동
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);브라우저 매크로
여러 테스트에서 반복적으로 사용하는 브라우저 동작을 매크로로 정의해두면 편리합니다. 매크로는 주로 서비스 프로바이더의 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("$('$element')[0].scrollIntoView(true);");
return $this;
});
}
}macro 메서드는 첫 번째 인수로 매크로 이름, 두 번째 인수로 클로저를 받습니다. 매크로의 클로저는 Browser 인스턴스에서 해당 매크로를 메서드로 호출할 때 실행됩니다:
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/pay')
->scrollToElement('#payment-form')
->assertSee('결제 정보를 입력하세요');
});인증
로그인이 필요한 페이지를 테스트할 때마다 매번 로그인 과정을 거치면 테스트가 느려집니다. Dusk의 loginAs 메서드를 사용하면 로그인 화면을 거치지 않고 특정 사용자로 인증 상태를 설정할 수 있습니다. loginAs 메서드는 Authenticatable 모델 인스턴스나 사용자 기본 키를 인수로 받습니다:
use App\Models\User;
use Laravel\Dusk\Browser;
$this->browse(function (Browser $browser) {
$browser->loginAs(User::find(1))
->visit('/home');
});WARNING
loginAs 메서드를 사용하면 해당 파일 내의 모든 테스트에서 사용자 세션이 유지됩니다.
쿠키
cookie 메서드로 암호화된 쿠키의 값을 가져오거나 설정할 수 있습니다:
$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('파일명');responsiveScreenshots 메서드를 사용하면 다양한 브레이크포인트에서 연속으로 스크린샷을 찍을 수 있습니다:
$browser->responsiveScreenshots('파일명');screenshotElement 메서드로 페이지 내 특정 요소만 스크린샷으로 저장할 수 있습니다:
$browser->screenshotElement('#main', '파일명');콘솔 출력 저장
storeConsoleLog 메서드로 현재 브라우저의 콘솔 출력을 파일로 저장할 수 있습니다. 콘솔 로그는 기본적으로 tests/Browser/console 디렉터리에 저장됩니다:
$browser->storeConsoleLog('파일명');페이지 소스 저장
storeSource 메서드로 현재 페이지의 HTML 소스를 파일로 저장할 수 있습니다. 페이지 소스는 기본적으로 tests/Browser/source 디렉터리에 저장됩니다:
$browser->storeSource('파일명');Laravel Dusk
소개
Laravel Dusk는 표현력 있고 사용하기 쉬운 브라우저 자동화 및 테스트 API를 제공합니다. 기본적으로 Dusk는 로컬 컴퓨터에 JDK나 Selenium을 설치할 필요가 없습니다. 대신, 독립 실행형 ChromeDriver를 사용합니다. 물론 원한다면 다른 Selenium 호환 드라이버를 자유롭게 활용할 수도 있습니다.
Laravel Dusk
설치
시작하기 전에 Google Chrome을 설치한 다음, Composer로 laravel/dusk 패키지를 프로젝트에 추가합니다:
composer require laravel/dusk --devWARNING
Dusk의 서비스 프로바이더를 수동으로 등록할 경우, 절대로 프로덕션 환경에 등록하지 마세요. 프로덕션에 등록하면 누구든지 애플리케이션에 임의로 인증할 수 있는 보안 취약점이 발생합니다.
패키지 설치 후 dusk:install Artisan 명령어를 실행합니다. 이 명령어는 tests/Browser 디렉터리와 예제 Dusk 테스트 파일을 생성하고, 현재 운영체제에 맞는 ChromeDriver 바이너리를 설치합니다:
php artisan dusk:install다음으로 애플리케이션의 .env 파일에서 APP_URL 환경 변수를 설정합니다. 이 값은 브라우저에서 실제로 애플리케이션에 접근할 때 사용하는 URL과 일치해야 합니다.
NOTE
로컬 개발 환경으로 Laravel Sail을 사용 중이라면, Sail 문서의 Dusk 테스트 설정 및 실행 항목도 함께 참고하세요.
ChromeDriver 버전 관리
dusk:install 명령어로 설치되는 ChromeDriver 외에 특정 버전을 직접 설치하고 싶다면 dusk:chrome-driver 명령어를 사용합니다:
<h1 id="dusk-selectors">현재 OS에 맞는 최신 ChromeDriver 설치</h1>
php artisan dusk:chrome-driver
<h1 id="text-values-and-attributes">현재 OS에 특정 버전의 ChromeDriver 설치</h1>
php artisan dusk:chrome-driver 86
<h1 id="retrieving-setting-values">지원하는 모든 OS용 ChromeDriver를 특정 버전으로 설치</h1>
php artisan dusk:chrome-driver --all
<h1 id="retrieving-text">현재 OS에 설치된 Chrome / Chromium 버전에 맞는 ChromeDriver 자동 감지 후 설치</h1>
php artisan dusk:chrome-driver --detectWARNING
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()
);
}Laravel Dusk
시작하기
테스트 생성
Dusk 테스트를 생성하려면 dusk:make Artisan 명령어를 사용합니다. 생성된 테스트 파일은 tests/Browser 디렉터리에 위치합니다:
php artisan dusk:make LoginTest각 테스트 후 데이터베이스 초기화
대부분의 테스트는 데이터베이스에서 데이터를 조회하는 페이지와 상호작용합니다. 그런데 Dusk 테스트에서는 RefreshDatabase 트레이트를 사용하면 안 됩니다. RefreshDatabase는 데이터베이스 트랜잭션을 활용하는데, 이 트랜잭션은 HTTP 요청을 넘나드는 상황(브라우저가 별도 프로세스에서 동작하는 Dusk의 구조)에서는 적용되지 않습니다.
대신 두 가지 방법을 사용할 수 있습니다: DatabaseMigrations 트레이트와 DatabaseTruncation 트레이트입니다.
DatabaseMigrations 사용
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;
}WARNING
Dusk 테스트 실행 시 SQLite 인메모리 데이터베이스는 사용할 수 없습니다. 브라우저는 자체 프로세스 안에서 실행되므로, 다른 프로세스의 인메모리 데이터베이스에 접근할 수 없습니다.
DatabaseTruncation 사용
DatabaseTruncation 트레이트를 사용하기 전에 Composer로 doctrine/dbal 패키지를 먼저 설치해야 합니다:
composer require --dev doctrine/dbalDatabaseTruncation 트레이트는 첫 번째 테스트 실행 시 마이그레이션을 수행해 테이블을 정상적으로 생성합니다. 이후 테스트부터는 테이블을 마이그레이션으로 재생성하지 않고 단순히 TRUNCATE로 비워냅니다. 이 방식은 매번 마이그레이션을 다시 실행하는 것보다 빠릅니다:
<?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 프로퍼티를 정의합니다:
/**
* TRUNCATE할 테이블 목록
*
* @var array
*/
protected $tablesToTruncate = ['users'];반대로, TRUNCATE에서 제외할 테이블을 지정하려면 $exceptTables 프로퍼티를 사용합니다:
/**
* TRUNCATE에서 제외할 테이블 목록
*
* @var array
*/
protected $exceptTables = ['users'];TRUNCATE를 수행할 데이터베이스 커넥션을 지정하려면 $connectionsToTruncate 프로퍼티를 정의합니다:
/**
* 테이블을 TRUNCATE할 커넥션 목록
*
* @var array
*/
protected $connectionsToTruncate = ['mysql'];TRUNCATE 전후에 특정 작업을 실행하고 싶다면, 테스트 클래스에 beforeTruncatingDatabase 또는 afterTruncatingDatabase 메서드를 정의합니다:
/**
* 데이터베이스 TRUNCATE 시작 전에 실행할 작업
*/
protected function beforeTruncatingDatabase(): void
{
//
}
/**
* 데이터베이스 TRUNCATE 완료 후에 실행할 작업
*/
protected function afterTruncatingDatabase(): void
{
//
}테스트 실행
브라우저 테스트를 실행하려면 dusk Artisan 명령어를 사용합니다:
php artisan dusk직전 실행에서 실패한 테스트가 있다면, dusk:fails 명령어로 실패한 테스트만 먼저 재실행해 시간을 절약할 수 있습니다:
php artisan dusk:failsdusk 명령어는 PHPUnit 테스트 러너가 지원하는 인수를 그대로 전달받습니다. 예를 들어 특정 그룹의 테스트만 실행할 수도 있습니다:
php artisan dusk --group=fooNOTE
로컬 개발 환경으로 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 파일이 복원됩니다.
Laravel Dusk
브라우저 기초
브라우저 인스턴스 생성
간단한 로그인 테스트를 작성하면서 브라우저 인스턴스를 어떻게 사용하는지 살펴보겠습니다. 테스트 파일을 생성한 뒤, 로그인 페이지로 이동하여 자격증명을 입력하고 "Login" 버튼을 클릭하는 흐름을 구성합니다. Dusk 테스트에서 브라우저 인스턴스를 사용하려면 browse 메서드를 호출하면 됩니다.
<?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 메서드는 클로저를 인자로 받습니다. 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('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);브라우저 매크로
여러 테스트에서 반복적으로 사용할 커스텀 브라우저 메서드가 필요하다면 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 함수는 첫 번째 인자로 매크로 이름, 두 번째 인자로 클로저를 받습니다. 등록된 매크로는 Browser 인스턴스에서 일반 메서드처럼 호출할 수 있습니다.
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/pay')
->scrollToElement('#credit-card-details')
->assertSee('Enter Credit Card Details');
});인증
인증이 필요한 페이지를 테스트할 때마다 로그인 화면을 직접 거치는 것은 번거롭습니다. Dusk의 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');콘솔 출력 저장
storeConsoleLog 메서드를 사용하면 현재 브라우저의 콘솔 출력을 지정한 파일명으로 디스크에 저장할 수 있습니다. 콘솔 출력 파일은 tests/Browser/console 디렉터리에 저장됩니다.
$browser->storeConsoleLog('filename');페이지 소스 저장
storeSource 메서드를 사용하면 현재 페이지의 HTML 소스를 지정한 파일명으로 디스크에 저장할 수 있습니다. 페이지 소스 파일은 tests/Browser/source 디렉터리에 저장됩니다.
$browser->storeSource('filename');Laravel Dusk — 요소와의 상호작용
Dusk 셀렉터
Dusk 테스트를 작성할 때 가장 까다로운 부분 중 하나는 요소를 선택할 CSS 셀렉터를 관리하는 일입니다. 프론트엔드 코드가 조금만 바뀌어도 아래와 같은 셀렉터는 쉽게 깨집니다.
<!-- HTML -->
<button>Login</button>// 테스트
$browser->click('.login-page .container div > button');Dusk 셀렉터를 사용하면 CSS 구조를 외울 필요 없이 테스트 자체에 집중할 수 있습니다. HTML 요소에 dusk 속성을 추가하고, 테스트에서는 @ 접두사를 붙여 해당 요소를 지정하면 됩니다.
<!-- HTML -->
<button dusk="login-button">Login</button>// 테스트
$browser->click('@login-button');Dusk 셀렉터가 사용하는 HTML 속성명을 바꾸고 싶다면 selectorHtmlAttribute 메서드를 사용하세요. 보통 AppServiceProvider의 boot 메서드에서 한 번만 호출하면 됩니다.
use Laravel\Dusk\Dusk;
Dusk::selectorHtmlAttribute('data-dusk');텍스트, 값, 속성
값 읽기 및 설정
페이지 요소의 현재 값, 표시 텍스트, HTML 속성을 다루는 메서드들을 살펴봅니다. CSS 또는 Dusk 셀렉터에 매칭되는 요소의 value를 읽거나 설정할 때는 value 메서드를 사용합니다.
// 값 읽기
$value = $browser->value('selector');
// 값 설정
$browser->value('selector', 'value');특정 name 속성을 가진 입력 필드의 값을 읽으려면 inputValue 메서드를 사용하세요.
$value = $browser->inputValue('field');텍스트 읽기
text 메서드로 셀렉터에 매칭되는 요소의 표시 텍스트를 가져올 수 있습니다.
$text = $browser->text('selector');HTML 속성 읽기
attribute 메서드로 셀렉터에 매칭되는 요소의 특정 HTML 속성값을 가져올 수 있습니다.
$attribute = $browser->attribute('selector', 'value');폼과의 상호작용
값 입력
입력 필드에 텍스트를 입력하려면 type 메서드를 사용합니다.
$browser->type('email', 'taylor@laravel.com');type 메서드는 CSS 셀렉터 없이도 동작합니다. 셀렉터가 없으면 Dusk가 해당 name 속성을 가진 input 또는 textarea를 자동으로 찾습니다.
기존 내용을 지우지 않고 텍스트를 이어서 입력하려면 append 메서드를 사용하세요.
$browser->type('tags', 'foo')
->append('tags', ', bar, baz');입력 필드의 내용을 비우려면 clear 메서드를 사용합니다.
$browser->clear('email');typeSlowly 메서드를 사용하면 키 입력 사이에 지연을 줄 수 있습니다. 기본값은 100밀리초이며, 세 번째 인자로 원하는 값을 지정할 수 있습니다.
$browser->typeSlowly('mobile', '010-1234-5678');
$browser->typeSlowly('mobile', '010-1234-5678', 300);텍스트를 천천히 이어서 입력하려면 appendSlowly 메서드를 사용하세요.
$browser->type('tags', 'foo')
->appendSlowly('tags', ', bar, baz');드롭다운
select 요소에서 값을 선택하려면 select 메서드를 사용합니다. type과 마찬가지로 전체 CSS 셀렉터가 없어도 동작합니다. 값을 지정할 때는 화면에 표시되는 텍스트가 아닌, option의 실제 value 속성값을 전달해야 합니다.
$browser->select('size', 'Large');두 번째 인자를 생략하면 무작위로 옵션을 선택합니다.
$browser->select('size');배열을 전달하면 여러 옵션을 동시에 선택할 수 있습니다.
$browser->select('categories', ['Art', 'Music']);체크박스
체크박스를 체크하려면 check 메서드를 사용합니다. 전체 CSS 셀렉터 없이도, Dusk가 name 속성으로 일치하는 체크박스를 찾습니다.
$browser->check('terms');체크를 해제하려면 uncheck 메서드를 사용합니다.
$browser->uncheck('terms');라디오 버튼
radio 입력을 선택하려면 radio 메서드를 사용합니다. 전체 CSS 셀렉터 없이도 Dusk가 name과 value 속성이 일치하는 radio 입력을 찾습니다.
$browser->radio('size', 'large');파일 첨부
file 입력 요소에 파일을 첨부하려면 attach 메서드를 사용합니다. 전체 CSS 셀렉터 없이도 Dusk가 name 속성으로 일치하는 file 입력을 찾습니다.
$browser->attach('photo', __DIR__.'/photos/mountains.png');WARNING
attach 기능을 사용하려면 서버에 PHP Zip 확장이 설치 및 활성화되어 있어야 합니다.
버튼 클릭
press 메서드로 페이지의 버튼을 클릭할 수 있습니다. 인자로는 버튼의 표시 텍스트 또는 CSS/Dusk 셀렉터를 전달합니다.
$browser->press('Login');폼을 제출할 때 버튼이 비활성화된 후 다시 활성화될 때까지 기다려야 한다면 pressAndWaitFor 메서드를 사용하세요.
// 버튼을 클릭하고 최대 5초 동안 버튼이 다시 활성화될 때까지 대기
$browser->pressAndWaitFor('Save');
// 버튼을 클릭하고 최대 1초 동안 대기
$browser->pressAndWaitFor('Save', 1);링크 클릭
특정 표시 텍스트를 가진 링크를 클릭하려면 clickLink 메서드를 사용합니다.
$browser->clickLink($linkText);특정 텍스트의 링크가 페이지에 표시되는지 확인하려면 seeLink 메서드를 사용하세요.
if ($browser->seeLink($linkText)) {
// ...
}WARNING
이 메서드들은 jQuery를 사용합니다. 페이지에 jQuery가 없다면 Dusk가 테스트 실행 중 자동으로 주입합니다.
키보드 사용
keys 메서드를 사용하면 type 메서드보다 더 복잡한 키 입력 시퀀스를 구현할 수 있습니다. 예를 들어 수정 키(modifier key)를 누른 채로 값을 입력할 수 있습니다. 아래 예시에서는 shift 키를 누른 채 taylor를 입력하고, 이후 수정 키 없이 swift를 입력합니다.
$browser->keys('selector', ['{shift}', 'taylor'], 'swift');애플리케이션의 기본 CSS 셀렉터에 키보드 단축키를 전송하는 데도 유용합니다.
$browser->keys('.app', ['{command}', 'j']);NOTE
{command}와 같은 수정 키는 {}로 감싸며, Facebook\WebDriver\WebDriverKeys 클래스에 정의된 상수와 대응됩니다. 전체 목록은 GitHub에서 확인할 수 있습니다.
유창한 키보드 상호작용
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']);
});키보드 매크로
테스트 전체에서 재사용할 키보드 동작을 정의하려면 Keyboard 클래스의 macro 메서드를 사용하세요. 보통 서비스 프로바이더의 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());마우스 사용
요소 클릭
CSS 또는 Dusk 셀렉터에 매칭되는 요소를 클릭하려면 click 메서드를 사용합니다.
$browser->click('.selector');XPath 표현식에 매칭되는 요소를 클릭하려면 clickAtXPath 메서드를 사용합니다.
$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();ctrl+click 이벤트를 시뮬레이션하려면 controlClick 메서드를 사용합니다.
$browser->controlClick();
$browser->controlClick('.selector');마우스 오버
CSS 또는 Dusk 셀렉터에 매칭되는 요소 위로 마우스를 이동하려면 mouseover 메서드를 사용합니다.
$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);지정한 오프셋만큼 요소를 드래그하려면 dragOffset 메서드를 사용합니다.
$browser->dragOffset('.selector', $x = 10, $y = 10);JavaScript 다이얼로그
Dusk는 JavaScript 다이얼로그를 다루는 다양한 메서드를 제공합니다.
JavaScript 다이얼로그가 나타날 때까지 대기하려면 waitForDialog 메서드를 사용합니다. 선택적으로 최대 대기 시간(초)을 인자로 전달할 수 있습니다.
$browser->waitForDialog($seconds = null);다이얼로그가 열렸는지, 그리고 특정 메시지를 포함하는지 확인하려면 assertDialogOpened 메서드를 사용합니다.
$browser->assertDialogOpened('Dialog message');JavaScript 프롬프트 다이얼로그에 값을 입력하려면 typeInDialog 메서드를 사용합니다.
$browser->typeInDialog('Hello World');"확인" 버튼을 클릭해 다이얼로그를 닫으려면 acceptDialog 메서드를 사용합니다.
$browser->acceptDialog();"취소" 버튼을 클릭해 다이얼로그를 닫으려면 dismissDialog 메서드를 사용합니다.
$browser->dismissDialog();iframe과의 상호작용
iframe 내부의 요소와 상호작용하려면 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 메서드를 사용합니다. 클로저 내부의 모든 작업은 지정한 셀렉터를 기준으로 범위가 좁혀집니다.
$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는 다양한 대기 메서드를 제공합니다.
단순 대기
지정한 밀리초 동안 테스트를 일시 정지하려면 pause 메서드를 사용합니다.
$browser->pause(1000);특정 조건이 true일 때만 일시 정지하려면 pauseIf 메서드를 사용합니다.
$browser->pauseIf(App::environment('production'), 1000);특정 조건이 true가 아닐 때 일시 정지하려면 pauseUnless 메서드를 사용합니다.
$browser->pauseUnless(App::environment('testing'), 1000);셀렉터 대기
waitFor 메서드는 CSS 또는 Dusk 셀렉터에 매칭되는 요소가 페이지에 나타날 때까지 대기합니다. 기본 최대 대기 시간은 5초이며, 두 번째 인자로 커스텀 값을 지정할 수 있습니다. 시간이 초과되면 예외가 발생합니다.
// 최대 5초 대기
$browser->waitFor('.selector');
// 최대 1초 대기
$browser->waitFor('.selector', 1);셀렉터에 매칭되는 요소가 특정 텍스트를 포함할 때까지 대기할 수도 있습니다.
// 최대 5초 대기
$browser->waitForTextIn('.selector', 'Hello World');
// 최대 1초 대기
$browser->waitForTextIn('.selector', 'Hello World', 1);셀렉터에 매칭되는 요소가 페이지에서 사라질 때까지 대기하려면 waitUntilMissing 메서드를 사용합니다.
// 최대 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);요소가 나타날 때 범위 지정
특정 요소가 나타날 때까지 기다린 후 그 안에서 작업을 수행하려면 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 메서드를 사용합니다.
// 최대 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');완전한 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 키워드나 세미콜론은 포함하지 않아도 됩니다.
// 최대 5초 대기
$browser->waitUntil('App.data.servers.length > 0');
// 최대 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');
});두 번째 인자로 셀렉터를 지정하면 특정 요소에 이벤트 리스너를 연결할 수 있습니다.
$browser->waitForEvent('load', '.selector');document와 window 객체의 이벤트도 대기할 수 있습니다.
// 문서가 스크롤될 때까지 대기
$browser->waitForEvent('scroll', 'document');
// 최대 5초 동안 창 크기가 변경될 때까지 대기
$browser->waitForEvent('resize', 'window', 5);콜백을 활용한 대기
Dusk의 다양한 대기 메서드는 내부적으로 waitUsing 메서드를 사용합니다. 이 메서드를 직접 사용하면 클로저가 true를 반환할 때까지 대기할 수 있습니다. 인자로는 최대 대기 시간(초), 클로저 평가 간격(초), 클로저, 실패 시 메시지를 순서대로 전달합니다.
$browser->waitUsing(10, 1, function () use ($something) {
return $something->isReady();
}, "Something wasn't ready in time.");요소를 뷰포트 안으로 스크롤
클릭하려는 요소가 브라우저의 뷰포트 밖에 있어 클릭할 수 없는 경우, scrollIntoView 메서드를 사용하면 해당 요소가 보이는 위치로 페이지를 스크롤합니다.
$browser->scrollIntoView('.selector')
->click('.selector');사용 가능한 Assertion 목록
Dusk는 애플리케이션을 검증하기 위한 다양한 assertion 메서드를 제공합니다. 아래에서 전체 목록을 확인할 수 있습니다.
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의 스킴(scheme)이 주어진 값과 일치하는지 확인합니다.
$browser->assertSchemeIs($scheme);
assertSchemeIsNot
현재 URL의 스킴(scheme)이 주어진 값과 일치하지 않는지 확인합니다.
$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의 해시 프래그먼트(hash fragment)가 주어진 값과 일치하는지 확인합니다.
$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);
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
주어진 체크박스가 indeterminate(미결정) 상태인지 확인합니다.
$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
주어진 셀렉터와 일치하는 요소의 특정 속성(attribute)이 주어진 값을 가지는지 확인합니다.
$browser->assertAttribute($selector, $attribute, $value);
assertAttributeContains
주어진 셀렉터와 일치하는 요소의 특정 속성(attribute)이 주어진 값을 포함하는지 확인합니다.
$browser->assertAttributeContains($selector, $attribute, $value);
assertAttributeDoesntContain
주어진 셀렉터와 일치하는 요소의 특정 속성(attribute)이 주어진 값을 포함하지 않는지 확인합니다.
$browser->assertAttributeDoesntContain($selector, $attribute, $value);
assertAriaAttribute
주어진 셀렉터와 일치하는 요소의 aria 속성이 주어진 값을 가지는지 확인합니다.
$browser->assertAriaAttribute($selector, $attribute, $value);
예를 들어, <button aria-label="추가"></button> 마크업이 있다면 다음과 같이 aria-label 속성을 검증할 수 있습니다.
$browser->assertAriaAttribute('button', 'label', '추가')
assertDataAttribute
주어진 셀렉터와 일치하는 요소의 data 속성이 주어진 값을 가지는지 확인합니다.
$browser->assertDataAttribute($selector, $attribute, $value);
예를 들어, <tr id="row-1" data-content="attendees"></tr> 마크업이 있다면 다음과 같이 data-content 속성을 검증할 수 있습니다.
$browser->assertDataAttribute('#row-1', 'content', 'attendees')
assertVisible
주어진 셀렉터와 일치하는 요소가 화면에 보이는지 확인합니다.
$browser->assertVisible($selector);
assertPresent
주어진 셀렉터와 일치하는 요소가 소스에 존재하는지 확인합니다.
NOTE
assertVisible은 요소가 실제로 화면에 표시되는지를 검사하고, assertPresent는 DOM에 존재하기만 하면 됩니다(숨겨진 요소도 통과). 용도에 맞게 구분해서 사용하세요.
$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
주어진 필드가 활성화(enabled) 상태인지 확인합니다.
$browser->assertEnabled($field);
assertDisabled
주어진 필드가 비활성화(disabled) 상태인지 확인합니다.
$browser->assertDisabled($field);
assertButtonEnabled
주어진 버튼이 활성화(enabled) 상태인지 확인합니다.
$browser->assertButtonEnabled($button);
assertButtonDisabled
주어진 버튼이 비활성화(disabled) 상태인지 확인합니다.
$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: '홍길동'
}
};
}
});이 Vue 컴포넌트의 데이터 상태를 다음과 같이 검증할 수 있습니다.
/**
* 기본 Vue 테스트 예시
*/
public function test_vue(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/')
->assertVue('user.name', '홍길동', '@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의 페이지(Page) 기능을 사용하면, 특정 페이지에서 반복적으로 수행하는 동작을 하나의 메서드로 표현할 수 있습니다. 또한 자주 쓰는 CSS 셀렉터에 짧은 별칭을 부여하는 기능도 제공합니다.
페이지 생성
페이지 객체를 생성하려면 dusk:page Artisan 명령어를 실행합니다. 생성된 파일은 tests/Browser/Pages 디렉터리에 저장됩니다:
php artisan dusk:page Login페이지 구성
기본적으로 페이지 클래스에는 세 가지 메서드가 있습니다: url, assert, elements. 아래에서 url과 assert를 먼저 살펴보고, elements는 단축 셀렉터 섹션에서 자세히 다룹니다.
url 메서드
url 메서드는 해당 페이지의 URL 경로를 반환합니다. Dusk는 브라우저에서 이 페이지로 이동할 때 이 값을 사용합니다:
/**
* 페이지의 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);때로는 이미 특정 페이지에 있는 상태에서, 버튼 클릭 등으로 다른 페이지로 리다이렉트된 경우처럼 명시적으로 visit을 호출하지 않고도 페이지의 셀렉터와 메서드를 현재 테스트 컨텍스트에 불러와야 할 때가 있습니다. 이럴 때는 on 메서드를 사용합니다:
use Tests\Browser\Pages\CreatePlaylist;
$browser->visit('/dashboard')
->clickLink('재생목록 만들기')
->on(new CreatePlaylist)
->assertSee('@create');단축 셀렉터
페이지 클래스의 elements 메서드를 사용하면 CSS 셀렉터에 짧고 기억하기 쉬운 별칭을 지정할 수 있습니다. 예를 들어, 로그인 페이지의 이메일 입력 필드에 단축 셀렉터를 정의해 보겠습니다:
/**
* 페이지의 단축 셀렉터 목록을 반환합니다.
*
* @return array<string, string>
*/
public function elements(): array
{
return [
'@email' => 'input[name=email]',
];
}단축 셀렉터를 정의하면, 전체 CSS 셀렉터를 사용하는 곳 어디서나 별칭으로 대체할 수 있습니다:
$browser->type('@email', 'test@example.com');전역 단축 셀렉터
Dusk를 설치하면 tests/Browser/Pages 디렉터리에 기본 Page 클래스가 생성됩니다. 이 클래스에는 siteElements 메서드가 있으며, 여기에 정의한 단축 셀렉터는 애플리케이션의 모든 페이지에서 전역으로 사용할 수 있습니다:
/**
* 사이트 전체에서 사용할 전역 단축 셀렉터를 반환합니다.
*
* @return array<string, string>
*/
public static function siteElements(): array
{
return [
'@element' => '#selector',
];
}NOTE
특정 페이지에서만 쓰이는 셀렉터는 해당 페이지의 elements 메서드에, 애플리케이션 전반에서 공통으로 쓰이는 셀렉터는 siteElements에 정의하는 것이 좋습니다.
페이지 메서드
기본 메서드 외에도, 테스트에서 반복적으로 사용하는 동작을 페이지 클래스의 메서드로 정의할 수 있습니다. 예를 들어, 음악 관리 애플리케이션에서 "재생목록 만들기"는 자주 수행하는 작업입니다. 이 로직을 매번 테스트마다 반복 작성하는 대신, 페이지 클래스에 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('재생목록 만들기');
}
}메서드를 정의한 뒤에는 해당 페이지를 사용하는 모든 테스트에서 바로 호출할 수 있습니다. 브라우저 인스턴스는 첫 번째 인자로 자동으로 전달되므로, 테스트 코드에서는 나머지 인자만 넘기면 됩니다:
use Tests\Browser\Pages\Dashboard;
$browser->visit(new Dashboard)
->createPlaylist('내 재생목록')
->assertSee('내 재생목록');컴포넌트
컴포넌트는 Dusk의 "페이지 객체"와 비슷한 개념이지만, 특정 페이지에 종속되지 않고 애플리케이션 전반에서 재사용되는 UI 요소를 위한 것입니다. 내비게이션 바, 알림 창, 날짜 선택기처럼 여러 페이지에 걸쳐 반복적으로 등장하는 UI를 컴포넌트로 정의하면, 동일한 브라우저 자동화 로직을 매번 중복 작성하지 않아도 됩니다.
컴포넌트 생성
컴포넌트를 생성하려면 dusk:component Artisan 명령어를 실행하세요. 생성된 컴포넌트 파일은 tests/Browser/Components 디렉터리에 저장됩니다.
php artisan dusk:component DatePicker예를 들어, 날짜 선택기(DatePicker)는 애플리케이션 내 여러 페이지에서 공통으로 사용되는 대표적인 UI 컴포넌트입니다. 이 컴포넌트 없이 날짜를 선택하는 브라우저 자동화 로직을 수십 개의 테스트마다 직접 작성한다면 매우 번거롭고 유지보수도 어렵습니다. 대신 아래처럼 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
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');
});
}
}NOTE
컴포넌트 내부에서 정의한 @date-field, @year-list 같은 엘리먼트 단축키는 해당 컴포넌트의 within 블록 안에서만 유효합니다. 페이지 객체와 달리 컴포넌트는 특정 URL에 종속되지 않으므로, 동일한 컴포넌트를 여러 페이지의 테스트에서 자유롭게 재사용할 수 있습니다.
CI/CD 환경에서 Dusk 테스트 실행
WARNING
대부분의 CI 환경에서는 Laravel 애플리케이션이 포트 8000에서 PHP 내장 개발 서버로 구동된다고 가정합니다. 따라서 CI 환경의 APP_URL 환경 변수가 반드시 http://127.0.0.1:8000으로 설정되어 있어야 합니다.
Heroku CI
Heroku CI에서 Dusk 테스트를 실행하려면 프로젝트의 app.json 파일에 Google Chrome 빌드팩과 실행 스크립트를 다음과 같이 추가합니다.
{
"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
Travis CI는 GUI가 없는 환경이므로, Chrome 브라우저를 헤드리스(headless) 모드로 실행해야 합니다. 아래 .travis.yml 설정을 참고하세요. PHP 내장 웹 서버는 php artisan serve로 띄웁니다.
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 duskGitHub Actions
GitHub Actions를 사용한다면 아래 워크플로 파일을 출발점으로 활용하세요. Travis CI와 마찬가지로 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: 환경 파일 준비
run: cp .env.example .env
- name: 데이터베이스 생성
run: |
sudo systemctl start mysql
mysql --user="root" --password="root" -e "CREATE DATABASE \`my-database\` character set UTF8mb4 collate utf8mb4_bin;"
- name: Composer 의존성 설치
run: composer install --no-progress --prefer-dist --optimize-autoloader
- name: 애플리케이션 키 생성
run: php artisan key:generate
- name: Chrome Driver 업그레이드
run: php artisan dusk:chrome-driver --detect
- name: Chrome Driver 시작
run: ./vendor/laravel/dusk/bin/chromedriver-linux &
- name: Laravel 서버 실행
run: php artisan serve --no-reload &
- name: Dusk 테스트 실행
run: php artisan dusk
- name: 스크린샷 업로드 (실패 시)
if: failure()
uses: actions/upload-artifact@v2
with:
name: screenshots
path: tests/Browser/screenshots
- name: 콘솔 로그 업로드 (실패 시)
if: failure()
uses: actions/upload-artifact@v2
with:
name: console
path: tests/Browser/consoleChipper CI
Chipper CI를 사용한다면 아래 설정 파일을 기반으로 시작하세요. 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
# APP_URL이 BUILD_HOST를 사용하도록 dusk 전용 env 파일 생성
cp -v .env .env.dusk.ci
sed -i "s@APP_URL=.*@APP_URL=http://$BUILD_HOST:8000@g" .env.dusk.ci
- name: 에셋 컴파일
cmd: |
npm ci --no-audit
npm run build
- name: 브라우저 테스트
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 공식 문서를 참고하세요.