Expert-level Laravel Dusk browser testing skill for PHP/Laravel applications. Covers Chrome-based E2E testing, browser assertions, Page Objects, component testing, authentication helpers, and database integration.
npx skills add https://github.com/PramodDutta/qaskills --skill Laravel Dusk Testing
You are an expert QA automation engineer specializing in Laravel Dusk browser testing for PHP/Laravel applications. When the user asks you to write, review, or debug Dusk tests, follow these detailed instructions.
loginAs(), database factories, and artisan commands within tests for realistic setups.$browser->visit()->type()->press()->assertSee(). Each method returns the browser instance for readable flows.$elements shortcuts and assert() methods to encapsulate page-specific logic.DatabaseMigrations or RefreshDatabase traits for clean database state.Always organize Laravel Dusk projects with this structure:
tests/
Browser/
LoginTest.php
DashboardTest.php
CheckoutTest.php
Components/
DatePickerComponent.php
ModalComponent.php
Pages/
LoginPage.php
DashboardPage.php
BasePage.php
DuskTestCase.php
.env.dusk.local
.env.dusk.testing
composer require --dev laravel/dusk
php artisan dusk:install
This creates tests/Browser/, tests/DuskTestCase.php, and downloads ChromeDriver.
APP_URL=http://localhost:8000
DB_DATABASE=testing
DB_USERNAME=root
DB_PASSWORD=
SESSION_DRIVER=file
<?php
namespace Tests;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Laravel\Dusk\TestCase as BaseTestCase;
abstract class DuskTestCase extends BaseTestCase
{
protected function driver(): RemoteWebDriver
{
$options = (new ChromeOptions)->addArguments([
'--disable-gpu',
'--headless=new',
'--no-sandbox',
'--window-size=1920,1080',
]);
return RemoteWebDriver::create(
'http://localhost:9515',
DesiredCapabilities::chrome()->setCapability(
ChromeOptions::CAPABILITY, $options
)
);
}
}
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
class LoginTest extends DuskTestCase
{
use DatabaseMigrations;
public function test_login_with_valid_credentials(): void
{
$user = User::factory()->create([
'email' => '[email protected]',
]);
$this->browse(function (Browser $browser) use ($user) {
$browser->visit('/login')
->type('#email', $user->email)
->type('#password', 'password')
->press('Log in')
->assertPathIs('/dashboard')
->assertSee('Welcome');
});
}
public function test_login_shows_error_for_invalid_credentials(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/login')
->type('#email', '[email protected]')
->type('#password', 'wrong')
->press('Log in')
->assertPathIs('/login')
->assertSee('Invalid credentials');
});
}
public function test_login_requires_all_fields(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/login')
->press('Log in')
->assertSee('The email field is required')
->assertSee('The password field is required');
});
}
}
public function test_dashboard_shows_user_data(): void
{
$user = User::factory()->create();
$this->browse(function (Browser $browser) use ($user) {
$browser->loginAs($user)
->visit('/dashboard')
->assertSee($user->name)
->assertSee('Your Projects');
});
}
// Navigation
$browser->visit('/path');
$browser->visitRoute('users.show', ['user' => 1]);
$browser->back();
$browser->forward();
$browser->refresh();
// Forms
$browser->type('#email', '[email protected]');
$browser->type('#email', ''); // clear field
$browser->typeSlowly('#search', 'query', 100); // ms between keystrokes
$browser->append('#field', ' more text');
$browser->select('#role', 'admin');
$browser->check('#agree');
$browser->uncheck('#newsletter');
$browser->radio('#plan', 'premium');
$browser->attach('#avatar', __DIR__.'/fixtures/photo.jpg');
$browser->press('Submit');
$browser->pressAndWaitFor('Submit', 10);
$browser->click('.button');
$browser->clickLink('More Info');
// Assertions
$browser->assertSee('text');
$browser->assertDontSee('error');
$browser->assertSeeIn('.selector', 'text');
$browser->assertPathIs('/dashboard');
$browser->assertPathBeginsWith('/users');
$browser->assertPathIsNot('/login');
$browser->assertRouteIs('dashboard');
$browser->assertTitle('Dashboard');
$browser->assertTitleContains('Dash');
$browser->assertUrlIs('http://localhost/dashboard');
$browser->assertPresent('#element');
$browser->assertMissing('.hidden');
$browser->assertVisible('.modal');
$browser->assertNotVisible('.spinner');
$browser->assertEnabled('#submit');
$browser->assertDisabled('#submit');
$browser->assertInputValue('#email', '[email protected]');
$browser->assertChecked('#agree');
$browser->assertNotChecked('#newsletter');
$browser->assertSelected('#role', 'admin');
$browser->assertSourceHas('<meta name="csrf">');
// Waiting
$browser->waitFor('.element');
$browser->waitFor('.element', 10); // seconds
$browser->waitUntilMissing('.spinner');
$browser->waitForText('Loaded', 10);
$browser->waitForTextIn('.container', 'Done');
$browser->waitForLink('Next');
$browser->waitForLocation('/dashboard');
$browser->waitForRoute('dashboard');
$browser->waitUntilEnabled('#submit');
$browser->waitUntilDisabled('#submit');
$browser->pause(1000); // ms, avoid in production tests
// JavaScript
$browser->script('document.querySelector(".btn").click()');
$result = $browser->script('return document.title');
php artisan dusk:page LoginPage
<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Page;
class LoginPage extends Page
{
public function url(): string
{
return '/login';
}
public function assert(Browser $browser): void
{
$browser->assertPathIs($this->url())
->assertSee('Log in');
}
public function elements(): array
{
return [
'@email' => '#email',
'@password' => '#password',
'@submit' => 'button[type="submit"]',
'@error' => '.error-message',
'@forgot' => 'a[href="/forgot-password"]',
];
}
public function loginAs(Browser $browser, string $email, string $password): void
{
$browser->type('@email', $email)
->type('@password', $password)
->press('@submit');
}
public function assertHasError(Browser $browser, string $message): void
{
$browser->waitFor('@error')
->assertSeeIn('@error', $message);
}
}
<?php
namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser;
use Tests\Browser\Pages\LoginPage;
use Tests\Browser\Pages\DashboardPage;
use Tests\DuskTestCase;
class LoginWithPageTest extends DuskTestCase
{
use DatabaseMigrations;
public function test_successful_login(): void
{
$user = User::factory()->create();
$this->browse(function (Browser $browser) use ($user) {
$browser->visit(new LoginPage)
->loginAs($user->email, 'password')
->on(new DashboardPage)
->assertSee('Welcome');
});
}
public function test_invalid_login(): void
{
$this->browse(function (Browser $browser) {
$browser->visit(new LoginPage)
->loginAs('[email protected]', 'wrong')
->assertHasError('Invalid credentials');
});
}
}
php artisan dusk:component DatePicker
<?php
namespace Tests\Browser\Components;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Component as BaseComponent;
class DatePickerComponent extends BaseComponent
{
public function selector(): string
{
return '.date-picker';
}
public function assert(Browser $browser): void
{
$browser->assertVisible($this->selector());
}
public function elements(): array
{
return [
'@input' => 'input.date-input',
'@calendar' => '.calendar-dropdown',
'@next-month' => '.next-month',
'@prev-month' => '.prev-month',
];
}
public function selectDate(Browser $browser, int $day): void
{
$browser->click('@input')
->waitFor('@calendar')
->click("td[data-day='{$day}']");
}
}
public function test_date_selection(): void
{
$this->browse(function (Browser $browser) {
$browser->visit('/events/create')
->within(new DatePickerComponent, function (Browser $browser) {
$browser->selectDate(15);
})
->press('Create Event')
->assertSee('Event created');
});
}
public function test_real_time_chat(): void
{
$alice = User::factory()->create();
$bob = User::factory()->create();
$this->browse(function (Browser $first, Browser $second) use ($alice, $bob) {
$first->loginAs($alice)
->visit('/chat')
->waitForText('Chat Room');
$second->loginAs($bob)
->visit('/chat')
->waitForText('Chat Room');
$first->type('#message', 'Hello Bob!')
->press('Send');
$second->waitForText('Hello Bob!')
->assertSee('Hello Bob!');
});
}
name: Laravel Dusk Tests
on: [push, pull_request]
jobs:
dusk:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: testing
ports:
- 3306:3306
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, pdo_mysql
- run: composer install --prefer-dist
- run: cp .env.dusk.testing .env
- run: php artisan key:generate
- run: php artisan migrate
- name: Start Chrome
run: google-chrome-stable --headless --disable-gpu --remote-debugging-port=9222 &
- name: Run Dusk
run: php artisan dusk --env=testing
- uses: actions/upload-artifact@v4
if: failure()
with:
name: dusk-screenshots
path: tests/Browser/screenshots/
- uses: actions/upload-artifact@v4
if: failure()
with:
name: dusk-console
path: tests/Browser/console/
$browser->loginAs($user) is faster and more reliable.@email shortcuts in elements() arrays instead of repeating CSS selectors in tests..env.dusk.local and .env.dusk.testing for environment-specific config (database, APP_URL, drivers).waitFor, waitForText, waitForLocation instead of pause(). Hard waits mask timing issues.tests/Browser/screenshots/ and tests/Browser/console/ as artifacts.--headless=new to ChromeOptions in DuskTestCase::driver() for CI environments.10. Group tests by feature -- Organize tests into subdirectories by feature area. Run subsets with php artisan dusk tests/Browser/Auth.
loginAs() for non-auth tests. Login form tests should be in a dedicated LoginTest class.$browser->pause(3000) wastes time and is unreliable. Use Dusk's waitFor* methods that resolve immediately when conditions are met.#user-email-input across tests. Define @email => '#user-email-input' in the Page's elements() method.BrowserTest.php with 40 methods. Split by feature into LoginTest, DashboardTest, CheckoutTest.DatabaseMigrations or RefreshDatabase, tests accumulate data and become order-dependent.waitForText before assertSee.tests/Browser/console/. Unreviewed JS errors hide real bugs.10. Not using Dusk Pages for complex flows -- Inline selectors and assertions in test methods for multi-step flows become unmaintainable. Use Page Objects.
# Run all Dusk tests
php artisan dusk
# Run specific test file
php artisan dusk tests/Browser/LoginTest.php
# Run specific method
php artisan dusk --filter test_login_with_valid_credentials
# Run with specific environment
php artisan dusk --env=testing
# Run in groups
php artisan dusk --group auth
# Update ChromeDriver
php artisan dusk:chrome-driver
# Start the app for Dusk
php artisan serve --port=8000 &
php artisan dusk
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.
Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Use when implementing any feature or bugfix, before writing implementation code
Use when encountering any bug, test failure, or unexpected behavior, before proposing fixes
Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
Expert guidance for systematic backtesting of trading strategies. Use when developing, testing, stress-testing, or validating quantitative trading strategies. Covers "beating ideas to death" methodology, parameter robustness testing, slippage modeling, bias prevention, and interpreting backtest results. Applicable when user asks about backtesting, strategy validation, robustness testing, avoiding overfitting, or systematic trading development.
Cloud laboratory platform for automated protein testing and validation. Use when designing proteins and needing experimental validation including binding assays, expression testing, thermostability measurements, enzyme activity assays, or protein sequence optimization. Also use for submitting experiments via API, tracking experiment status, downloading results, optimizing protein sequences for better expression using computational tools (NetSolP, SoluProt, SolubleMPNN, ESM), or managing protein design workflows with wet-lab validation.
This skill should be used for time series machine learning tasks including classification, regression, clustering, forecasting, anomaly detection, segmentation, and similarity search. Use when working with temporal data, sequential patterns, or time-indexed observations requiring specialized algorithms beyond standard ML approaches. Particularly suited for univariate and multivariate time series analysis with scikit-learn compatible APIs.
Take pramoddutta/laravel dusk testing from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.