Skip to main content
playwright Playwright E2E testing, page objects, fixtures, visual regression, accessibility testing, and CI integration patterns.
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/a5c-ai/babysitter --skill playwrightThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... Related occupations SOC
Based on SOC occupation classification
More from this repository Reference for querying the Atlas knowledge graph through its MCP tools โ the SECONDARY enrichment/comparison layer that adds best-practice context to systems you have ALREADY scanned from your real sources (`az`, repos, dirs). Use when you need to look up nodes, edges, kinds, clusters, stats, or wiki pages in Atlas to compare against your real inventory. (atlas graph, query atlas, atlas mcp, search the graph, graph neighbors, atlas record, atlas kinds, enrichment layer)
Atlas turns your STATED NEED into a real systems atlas by SCANNING your actual sources (Azure via `az`, git repos, local dirs) and process/data mining them, THEN enriching against the Atlas knowledge graph. Use this skill when asked to inventory/map your real systems, scan your cloud + repos + directories, mine the real processes or data they contain, or collect their real constraints/gotchas. (atlas, scan my systems, inventory our azure account, map my repos, real systems atlas, process mining, data mining, collect nuances, system discovery)
assimilate-popular-workflows This skill should be used when the user asks to "find skills in the wild", "assimilate popular workflows", "discover SKILL.md files in repos", "research external skills", "find workflow patterns", "survey the skill landscape", "what skills exist out there", or wants to investigate public repositories for extractable processes, babysitter plugins, and reusable procedural insights. Searches GitHub for SKILL.md files, classifies repos by archetype, and maintains structured research under docs/reference-repos/.
name playwright description Playwright E2E testing, page objects, fixtures, visual regression, accessibility testing, and CI integration patterns. allowed-tools Read, Write, Edit, Bash, Glob, Grep graph {"domains":["domain:web-development"],"specializations":["specialization:web-development"],"skillAreas":["skill-area:e2e-testing","skill-area:cross-browser-testing"],"roles":["role:qa-engineer","role:frontend-engineer"],"workflows":["workflow:feature-development"],"topics":["topic:test-driven-development"]}
Playwright Skill
Expert assistance for building comprehensive E2E test suites with Playwright, including page objects, fixtures, visual regression, and CI/CD integration.
Capabilities
Generate Playwright test project structure
Create page object models for maintainable tests
Implement custom fixtures and test utilities
Configure visual regression testing
Set up accessibility testing with axe-core
Integrate with CI/CD pipelines (GitHub Actions, etc.)
Generate API testing alongside UI tests
Usage
Invoke this skill when you need to:
Set up Playwright testing for a web application
Create page object patterns for test organization
Implement visual regression testing
Configure cross-browser testing
Set up CI/CD test automation
Inputs
Parameter Type Required Description
projectType string No web, api, component (default: web) framework string No react, nextjs, vue, angular browsers array No chromium, firefox, webkit (default: all) features array No visual, a11y, api, component ci string No github, gitlab, jenkins
Test Configuration {
"projectType" : "web" ,
"framework" : "nextjs" ,
"browsers" : [ "chromium" , "firefox" ] ,
"features" : [ "visual" , "a11y" , "api" ] ,
"ci" : "github" ,
"baseUrl" : "http://localhost:3000"
}
Output Structure tests/
โโโ playwright.config.ts # Playwright configuration
โโโ fixtures/
โ โโโ base.ts # Base test fixture
โ โโโ auth.ts # Authentication fixture
โ โโโ api.ts # API helper fixture
โโโ pages/
โ โโโ BasePage.ts # Base page object
โ โโโ LoginPage.ts # Login page object
โ โโโ DashboardPage.ts # Dashboard page object
โโโ e2e/
โ โโโ auth/
โ โ โโโ login.spec.ts
โ โ โโโ logout.spec.ts
โ โโโ dashboard/
โ โ โโโ dashboard.spec.ts
โ โโโ api/
โ โโโ users.api.spec.ts
โโโ visual/
โ โโโ homepage.visual.spec.ts
โ โโโ screenshots/ # Baseline screenshots
โโโ a11y/
โ โโโ accessibility.spec.ts
โโโ utils/
โ โโโ helpers.ts
โ โโโ test-data.ts
โโโ .github/
โโโ workflows/
โโโ playwright.yml # CI workflow
Generated Code Patterns
Playwright Configuration
import { defineConfig, devices } from '@playwright/test' ;
export default defineConfig ({
testDir : './tests' ,
fullyParallel : true ,
forbidOnly : !!process.env .CI ,
retries : process.env .CI ? 2 : 0 ,
workers : process.env .CI ? 1 : undefined ,
reporter : [
['html' , { open : 'never' }],
['json' , { outputFile : 'test-results/results.json' }],
['junit' , { outputFile : 'test-results/junit.xml' }],
],
use : {
baseURL : process.env .BASE_URL ?? 'http://localhost:3000' ,
trace : 'on-first-retry' ,
screenshot : 'only-on-failure' ,
video : 'retain-on-failure' ,
},
projects : [
{ 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' ],
},
{
name : 'mobile-chrome' ,
use : { ...devices['Pixel 5' ] },
dependencies : ['setup' ],
},
{
name : 'mobile-safari' ,
use : { ...devices['iPhone 13' ] },
dependencies : ['setup' ],
},
],
webServer : {
command : 'npm run dev' ,
url : 'http://localhost:3000' ,
reuseExistingServer : !process.env .CI ,
},
});
Base Page Object
import { Page , Locator , expect } from '@playwright/test' ;
export abstract class BasePage {
readonly page : Page ;
readonly header : Locator ;
readonly footer : Locator ;
readonly loadingSpinner : Locator ;
constructor (page : Page ) {
this .page = page;
this .header = page.locator ('header' );
this .footer = page.locator ('footer' );
this .loadingSpinner = page.locator ('[data-testid="loading"]' );
}
abstract get url (): string ;
async goto ( ) {
await this .page .goto (this .url );
await this .waitForPageLoad ();
}
async waitForPageLoad ( ) {
await this .loadingSpinner .waitFor ({ state : 'hidden' });
}
async expectToBeVisible ( ) {
await expect (this .page ).toHaveURL (new RegExp (this .url ));
}
async getToastMessage (): Promise <string | null > {
const toast = this .page .locator ('[role="alert"]' );
if (await toast.isVisible ()) {
return toast.textContent ();
}
return null ;
}
}
Login Page Object
import { Page , Locator , expect } from '@playwright/test' ;
import { BasePage } from './BasePage' ;
export class LoginPage extends BasePage {
readonly emailInput : Locator ;
readonly passwordInput : Locator ;
readonly submitButton : Locator ;
readonly errorMessage : Locator ;
readonly forgotPasswordLink : 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.locator ('[role="alert"]' );
this .forgotPasswordLink = page.getByRole ('link' , { name : 'Forgot password?' });
}
get url () {
return '/login' ;
}
async login (email : string , password : string ) {
await this .emailInput .fill (email);
await this .passwordInput .fill (password);
await this .submitButton .click ();
}
async expectErrorMessage (message : string ) {
await expect (this .errorMessage ).toContainText (message);
}
async expectLoginSuccess ( ) {
await expect (this .page ).toHaveURL (/\/dashboard/ );
}
}
Custom Fixtures
import { test as base, expect } from '@playwright/test' ;
import { LoginPage } from '../pages/LoginPage' ;
import { DashboardPage } from '../pages/DashboardPage' ;
interface TestFixtures {
loginPage : LoginPage ;
dashboardPage : DashboardPage ;
}
interface WorkerFixtures {
authenticatedPage : void ;
}
export const test = base.extend <TestFixtures , WorkerFixtures >({
loginPage : async ({ page }, use) => {
const loginPage = new LoginPage (page);
await use (loginPage);
},
dashboardPage : async ({ page }, use) => {
const dashboardPage = new DashboardPage (page);
await use (dashboardPage);
},
authenticatedPage : [
async ({ browser }, use) => {
const context = await browser.newContext ({
storageState : 'tests/.auth/user.json' ,
});
await use ();
await context.close ();
},
{ scope : 'worker' },
],
});
export { expect };
Authentication Setup
import { test as setup, expect } from '@playwright/test' ;
import path from 'path' ;
const authFile = path.join (__dirname, '.auth/user.json' );
setup ('authenticate' , async ({ page }) => {
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' }).click ();
await expect (page).toHaveURL (/\/dashboard/ );
await page.context ().storageState ({ path : authFile });
});
E2E Test Example
import { test, expect } from '../../fixtures/base' ;
test.describe ('Login' , () => {
test.beforeEach (async ({ loginPage }) => {
await loginPage.goto ();
});
test ('should login with valid credentials' , async ({ loginPage }) => {
await loginPage.login ('user@example.com' , 'password123' );
await loginPage.expectLoginSuccess ();
});
test ('should show error with invalid credentials' , async ({ loginPage }) => {
await loginPage.login ('invalid@example.com' , 'wrongpassword' );
await loginPage.expectErrorMessage ('Invalid email or password' );
});
test ('should show validation errors for empty fields' , async ({ loginPage }) => {
await loginPage.submitButton .click ();
await expect (loginPage.emailInput ).toHaveAttribute ('aria-invalid' , 'true' );
await expect (loginPage.passwordInput ).toHaveAttribute ('aria-invalid' , 'true' );
});
});
Visual Regression Test
import { test, expect } from '@playwright/test' ;
test.describe ('Visual Regression' , () => {
test ('homepage should match snapshot' , async ({ page }) => {
await page.goto ('/' );
await expect (page).toHaveScreenshot ('homepage.png' , {
fullPage : true ,
animations : 'disabled' ,
});
});
test ('login page should match snapshot' , async ({ page }) => {
await page.goto ('/login' );
await expect (page).toHaveScreenshot ('login-page.png' );
});
test ('dashboard should match snapshot @authenticated' , async ({ page }) => {
await page.goto ('/dashboard' );
await expect (page).toHaveScreenshot ('dashboard.png' , {
mask : [page.locator ('[data-testid="user-avatar"]' )],
});
});
});
Accessibility Test
import { test, expect } from '@playwright/test' ;
import AxeBuilder from '@axe-core/playwright' ;
test.describe ('Accessibility' , () => {
test ('homepage should have no accessibility violations' , async ({ page }) => {
await page.goto ('/' );
const accessibilityScanResults = await new AxeBuilder ({ page })
.withTags (['wcag2a' , 'wcag2aa' , 'wcag21aa' ])
.analyze ();
expect (accessibilityScanResults.violations ).toEqual ([]);
});
test ('login form should be keyboard accessible' , async ({ page }) => {
await page.goto ('/login' );
await page.keyboard .press ('Tab' );
await expect (page.getByLabel ('Email' )).toBeFocused ();
await page.keyboard .press ('Tab' );
await expect (page.getByLabel ('Password' )).toBeFocused ();
await page.keyboard .press ('Tab' );
await expect (page.getByRole ('button' , { name : 'Sign in' })).toBeFocused ();
});
});
API Test
import { test, expect } from '@playwright/test' ;
test.describe ('Users API' , () => {
test ('should get user list' , async ({ request }) => {
const response = await request.get ('/api/users' );
expect (response.ok ()).toBeTruthy ();
const body = await response.json ();
expect (body.users ).toBeInstanceOf (Array );
expect (body.users .length ).toBeGreaterThan (0 );
});
test ('should create a new user' , async ({ request }) => {
const response = await request.post ('/api/users' , {
data : {
name : 'Test User' ,
email : 'test@example.com' ,
},
});
expect (response.status ()).toBe (201 );
const user = await response.json ();
expect (user.name ).toBe ('Test User' );
expect (user.email ).toBe ('test@example.com' );
});
});
GitHub Actions Workflow
name: Playwright Tests
on:
push:
branches: [main , develop ]
pull_request:
branches: [main ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
env:
BASE_URL: ${{ secrets.BASE_URL }}
TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Dependencies {
"devDependencies" : {
"@playwright/test" : "^1.50.0" ,
"@axe-core/playwright" : "^4.10.0"
}
}
Workflow
Setup configuration - Create playwright.config.ts
Create page objects - Model application pages
Define fixtures - Set up test utilities
Write tests - E2E, visual, a11y tests
Configure CI - GitHub Actions workflow
Generate reports - HTML, JSON, JUnit
Best Practices Applied
Page Object Model for maintainability
Custom fixtures for reusability
Parallel test execution
Cross-browser testing
Visual regression baselines
Accessibility testing integration
Proper test isolation
References
Target Processes
e2e-testing-setup
visual-regression-testing
accessibility-testing
api-testing
ci-cd-integration