aeo-optimization
AI Engine Optimization - semantic triples, page templates, content clusters for AI citations
E2E testing with Playwright - Page Objects, cross-browser, CI/CD
$ npx -y skills add alinaqi/claude-bootstrap --skill playwright-testing --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/playwright-testingContext preview
The summary Claude sees to decide when to auto-load this skill.
E2E testing with Playwright - Page Objects, cross-browser, CI/CD
name: playwright-testing description: E2E testing with Playwright - Page Objects, cross-browser, CI/CD when-to-use: When writing or debugging E2E tests with Playwright user-invocable: true paths: ["**/e2e/**", "**/*.spec.ts", "**/playwright/**", "playwright.config.*"] effort: medium
For end-to-end testing of web applications with Playwright - cross-browser, fast, reliable.
**Sources:** [Playwright Best Practices](https://playwright.dev/docs/best-practices) | [Playwright Docs](https://playwright.dev/docs/intro) | [Better Stack Guide](https://betterstack.com/community/guides/testing/playwright-best-practices/)
---
# New project npm init playwright@latest # Existing project npm install -D @playwright/test npx playwright install
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['list'],
process.env.CI ? ['github'] : ['line'],
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
// Auth setup - runs once before all tests
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
dependencies: ['setup'],
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
dependencies: ['setup'],
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
dependencies: ['setup'],
},
// Mobile viewports
{
name: 'mobile-chrome',
use: { ...devices['Pixel 5'] },
dependencies: ['setup'],
},
{
name: 'mobile-safari',
use: { ...devices['iPhone 12'] },
dependencies: ['setup'],
},
],
// Start dev server before tests
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
});---
project/ ├── e2e/ │ ├── fixtures/ │ │ ├── auth.fixture.ts # Auth fixtures │ │ └── test.fixture.ts # Extended test with fixtures │ ├── pages/ │ │ ├── base.page.ts # Base page object │ │ ├── login.page.ts # Login page object │ │ ├── dashboard.page.ts # Dashboard page object │ │ └── index.ts # Export all pages │ ├── tests/ │ │ ├── auth.spec.ts # Auth tests │ │ ├── dashboard.spec.ts # Dashboard tests │ │ └── checkout.spec.ts # Checkout flow tests │ ├── utils/ │ │ ├── helpers.ts # Test helpers │ │ └── test-data.ts # Test data factories │ └── auth.setup.ts # Global auth setup ├── playwright.config.ts └── .auth/ # Stored auth state (gitignored)
---
Use locators that mirror how users interact with the page:
// ✅ BEST: Role-based (accessible, resilient)
page.getByRole('button', { name: 'Submit' })
page.getByRole('textbox', { name: 'Email' })
page.getByRole('link', { name: 'Sign up' })
page.getByRole('heading', { name: 'Welcome' })
// ✅ GOOD: User-facing text
page.getByLabel('Email address')
page.getByPlaceholder('Enter your email')
page.getByText('Welcome back')
page.getByTitle('Profile settings')
// ✅ GOOD: Test IDs (stable, explicit)
page.getByTestId('submit-button')
page.getByTestId('user-avatar')
// ⚠️ AVOID: CSS selectors (brittle)
page.locator('.btn-primary')
page.locator('#submit')
// ❌ NEVER: XPath (extremely brittle)
page.locator('//div[@class="container"]/button[1]')// Narrow down to specific section
const form = page.getByRole('form', { name: 'Login' });
await form.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
await form.getByRole('button', { name: 'Submit' }).click();
// Filter within a list
const productCard = page.getByTestId('product-card')
.filter({ hasText: 'Pro Plan' });
await productCard.getByRole('button', { name: 'Buy' }).click();---
// e2e/pages/base.page.ts
import { Page, Locator } from '@playwright/test';
export abstract class BasePage {
constructor(protected page: Page) {}
async navigate(path: string = '/') {
await this.page.goto(path);
}
async waitForPageLoad() {
await this.page.waitForLoadState('networkidle');
}
// Common elements
get header() {
return this.page.getByRole('banner');
}
get footer() {
return this.page.getByRole('contentinfo');
}
// Common actions
async clickNavLink(name: string) {
await this.header.getByRole('link', { name }).click();
}
}// e2e/pages/login.page.ts
import { Page, expect } from '@playwright/test';
import { BasePage } from './base.page';
export class LoginPage extends BasePage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
super(page);
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign in' });
this.errorMessage = page.getByRole('alert');
}
async goto() {
await this.navigate('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async expectError(message: string) {
await expect(this.errorMessage).toContainText(message);
}
async expectLoggedIn() {Turn Claude Code into a self-reviewing, test-enforced engineering system that remembers context across sessions — then route work across 13 models from a single dashboard.
Repo: alinaqi/claude-bootstrap
AI Engine Optimization - semantic triples, page templates, content clusters for AI citations
Claude Code Agent Teams - default team-based development with strict TDD pipeline enforcement
Build AI agents with Pydantic AI (Python) and Claude SDK (Node.js)
Latest AI models reference - Claude, OpenAI, Gemini, Eleven Labs, Replicate
Android Kotlin development with Coroutines, Jetpack Compose, Hilt, and MockK testing