e2e-tester
E2E Tester
Two-phase workflow: explore interactively via agent-browser, then generate CI-ready test code.
Runtime: agent-browser CLI (exploration)
Output: Playwright Test files (JS/TS) or Pest Browser tests (Laravel/PHP)
Constraint: Never commit @ref selectors—exploration-only
Prerequisites
- Load agent-browser skill:
use agent browser - App running at accessible URL
- Verify:
agent-browser open <url>thenagent-browser snapshot -i
Two Modes
🔍 Exploration (agent-browser)
Use for discovery—DO NOT commit these selectors:
# Navigate and inspect
agent-browser open https://myapp.local
agent-browser snapshot -i # Get interactive elements with @refs
# Interact using refs from snapshot
agent-browser click @e1
agent-browser fill @e2 "test@example.com"
agent-browser fill @e3 "password123"
agent-browser click @e4
agent-browser wait --load networkidle
agent-browser snapshot -i # Check result
✅ Committed Test (CI-ready)
Generate this for test files—NO @refs, use stable locators:
For Playwright (JS/TS):
import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: /sign in/i }).click();
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.getByRole('heading', { name: /welcome/i })).toBeVisible();
});
For Pest Browser (Laravel): See reference/pest-browser.md
Locator Strategy (CI-safe)
Use in order of preference:
// 1. Test IDs (most stable)
page.getByTestId('sign-in-btn')
// 2. Role + name (semantic, accessible)
page.getByRole('button', { name: /submit/i })
// 3. Label (for form inputs)
page.getByLabel('Email')
// 4. CSS only if stable (data attributes)
page.locator('[data-action="save"]')
Anti-patterns
- ❌
@refselectors (exploration only) - ❌
.nth()unless unavoidable - ❌ CSS utility classes (
bg-blue-500,flex) - ❌ Dynamic/generated selectors
- ❌
waitForTimeout(use native waits)
Canonical Playwright Template
// tests/e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('shows validation error for empty form', async ({ page }) => {
await page.getByRole('button', { name: /sign in/i }).click();
await expect(page.getByRole('alert')).toContainText('required');
});
test('logs in with valid credentials', async ({ page }) => {
await page.getByLabel('Email').fill('user@test.com');
await page.getByLabel('Password').fill('secure123');
await page.getByRole('button', { name: /sign in/i }).click();
await expect(page).toHaveURL(/\/dashboard/);
});
});
Wait Strategies
In agent-browser (exploration)
agent-browser wait @e1 # Wait for element
agent-browser wait --text "Success" # Wait for text
agent-browser wait --url "**/dashboard" # Wait for URL
agent-browser wait --load networkidle # Network idle
In CI tests (Playwright)
// ✅ Good: Playwright's built-in waits
await expect(page).toHaveURL(/\/dashboard/);
await expect(locator).toBeVisible();
await page.waitForResponse(res => res.url().includes('/api/users'));
// ❌ Avoid
await page.waitForTimeout(1000);
Auth: storageState Pattern
Login once, reuse session:
// global-setup.ts
import { chromium } from '@playwright/test';
export default async function globalSetup() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.TEST_USER_EMAIL!);
await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD!);
await page.getByRole('button', { name: /sign in/i }).click();
await page.waitForURL(/\/dashboard/);
await page.context().storageState({ path: '.auth/user.json' });
await browser.close();
}
// playwright.config.ts
export default defineConfig({
globalSetup: './global-setup.ts',
use: { storageState: '.auth/user.json' },
});
agent-browser Quick Reference
| Task | Command |
|---|---|
| Navigate | agent-browser open <url> |
| Inspect | agent-browser snapshot -i |
| Click | agent-browser click @e1 |
| Fill | agent-browser fill @e1 "text" |
| Screenshot | agent-browser screenshot |
| Wait | agent-browser wait --text "Done" |
| Check state | agent-browser is visible @e1 |
Rules
In agent-browser (exploration)
- Use
@reffrom snapshot to interact - Re-snapshot after navigation/DOM changes
- Use
--headedfor visual debugging
In CI tests (committed)
- Use
expectassertions - Use CI-safe locators:
getByTestId,getByRole,getByLabel - Never use
@refselectors - Add
data-testidto interactive elements when needed
Reference Files
reference/pest-browser.md- Laravel/Pest v4 browser testingreference/advanced-patterns.md- Page objects, dynamic contentreference/ci-cd.md- GitHub Actions, Docker config
More from sebastiaanwouters/dotagents
flyctl
Deploy and manage apps on Fly.io using flyctl CLI. Triggers on: fly deploy, fly.io, flyctl, deploy to fly. Handles launch, deploy, scale, secrets, volumes, databases.
79teacher
Guide learning and deep understanding through proven methodologies (Socratic, Feynman, Problem-Based). Use when user says "help me understand", "teach me", "explain this", "learn about", "socratic", "feynman", "problem-based", "I don't understand", "confused about", "why does", or wants to truly grasp a concept.
77chef
Telegram communication for AI agents. ALL methods are BLOCKING. Use for user interviews, status updates, and feedback collection.
34bitwarden
Retrieves API keys, passwords, secrets from Bitwarden vault using bw CLI. Triggers on missing env variables, missing API keys, missing secrets, "secret not found", "env not set", or "use bw".
29librarian
Use for code research that needs dependency internals, upstream implementation examples, or external prior art. Always delegate to a subagent that investigates with opensrc and web search, then return only distilled findings, versions, paths, and links.
29frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications. Generates creative, polished code that avoids generic AI aesthetics.
28