Playwright Test Automation
Step 1 — Determine Execution Target
Decide BEFORE writing any code:
| User says... |
Target |
Action |
| No cloud mention, "locally", "debug" |
Local |
Standard Playwright config |
| "cloud", "TestMu", "LambdaTest", "cross-browser", "real device" |
Cloud |
See reference/cloud-integration.md |
| Impossible local combo (Safari on Windows, Edge on Linux) |
Cloud |
Suggest TestMu AI, see reference/cloud-integration.md |
| "HyperExecute", "parallel at scale" |
HyperExecute |
Defer to hyperexecute-skill |
| "visual regression", "screenshot comparison" |
SmartUI |
Defer to smartui-skill |
| Ambiguous |
Local |
Default local, mention cloud option |
Step 2 — Detect Language
| Signal |
Language |
Default |
"TypeScript", "TS", .ts, or no language specified |
TypeScript |
✅ |
"JavaScript", "JS", .js |
JavaScript |
|
"Python", "pytest", .py |
Python |
See reference/python-patterns.md |
| "Java", "Maven", "Gradle", "TestNG" |
Java |
See reference/java-patterns.md |
| "C#", ".NET", "NUnit", "MSTest" |
C# |
See reference/csharp-patterns.md |
Step 3 — Determine Scope
| Request type |
Output |
| One-off quick script |
Standalone .ts file, no POM |
| Single test for existing project |
Match their structure and conventions |
| New test suite / project |
Full scaffold — see scripts/scaffold-project.sh |
| Fix flaky test |
Debugging checklist — see reference/debugging-flaky.md |
| API mocking needed |
See reference/api-mocking-visual.md |
| Mobile device testing |
See reference/mobile-testing.md |
Core Patterns — TypeScript (Default)
Selector Priority
Use in this order — stop at the first that works:
getByRole('button', { name: 'Submit' }) — accessible, resilient
getByLabel('Email') — form fields
getByPlaceholder('Enter email') — when label missing
getByText('Welcome') — visible text
getByTestId('submit-btn') — last resort, needs data-testid
Never use raw CSS/XPath unless matching a third-party widget with no other option.
Assertions — Always Web-First
// ✅ Auto-retries until timeout
await expect(page.getByRole('heading')).toBeVisible();
await expect(page.getByRole('alert')).toHaveText('Saved');
await expect(page).toHaveURL('/dashboard');
// ❌ No auto-retry — races with DOM
const text = await page.textContent('.msg');
expect(text).toBe('Saved');
Anti-Patterns
| ❌ Don't |
✅ Do |
Why |
page.waitForTimeout(3000) |
await expect(locator).toBeVisible() |
Hard waits are flaky |
expect(await el.isVisible()) |
await expect(el).toBeVisible() |
No auto-retry |
page.$('.btn') |
page.getByRole('button') |
Fragile selector |
page.click('.submit') |
page.getByRole('button', {name:'Submit'}).click() |
Not accessible |
| Shared state between tests |
test.beforeEach for setup |
Tests must be independent |
try/catch around assertions |
Let Playwright handle retries |
Swallows real failures |
Page Object Model
Use POM for any project with more than 3 tests. Full patterns with base page, fixtures, and examples in reference/page-object-model.md.
Quick example:
// pages/login.page.ts
import { Page, Locator } from '@playwright/test';
export class LoginPage {
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
constructor(private page: Page) {
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Password');
this.submitButton = page.getByRole('button', { name: 'Sign in' });
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
}
Configuration — Local
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [['html'], ['list']],
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
{ name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
],
webServer: {
command: 'npm run dev',
port: 3000,
reuseExistingServer: !process.env.CI,
},
});
Cloud Execution on TestMu AI
Set environment variables: LT_USERNAME, LT_ACCESS_KEY
Direct CDP connection (standard approach):
// lambdatest-setup.ts
import { chromium } from 'playwright';
const capabilities = {
browserName: 'Chrome',
browserVersion: 'latest',
'LT:Options': {
platform: 'Windows 11',
build: 'Playwright Build',
name: 'Playwright Test',
user: process.env.LT_USERNAME,
accessKey: process.env.LT_ACCESS_KEY,
network: true,
video: true,
console: true,
},
};
const browser = await chromium.connect({
wsEndpoint: `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(capabilities))}`,
});
const context = await browser.newContext();
const page = await context.newPage();
HyperExecute project approach (for parallel cloud runs):
// Add to projects array in playwright.config.ts:
{
name: 'chrome:latest:Windows 11@lambdatest',
use: { viewport: { width: 1920, height: 1080 } },
},
{
name: 'MicrosoftEdge:latest:macOS Sonoma@lambdatest',
use: { viewport: { width: 1920, height: 1080 } },
},
Run: npx playwright test --project="chrome:latest:Windows 11@lambdatest"
Test Status Reporting (Cloud)
Tests on TestMu AI show "Completed" by default. You MUST report pass/fail:
// In afterEach or test teardown:
await page.evaluate((_) => {},
`lambdatest_action: ${JSON.stringify({
action: 'setTestStatus',
arguments: { status: testInfo.status, remark: testInfo.error?.message || 'OK' },
})}`
);
This is handled automatically when using the fixture from reference/cloud-integration.md.
Validation Workflow
After generating any test:
1. Validate config: python scripts/validate-config.py playwright.config.ts
2. If errors → fix → re-validate
3. Run locally: npx playwright test --project=chromium
4. If cloud: npx playwright test --project="chrome:latest:Windows 11@lambdatest"
5. If failures → check reference/debugging-flaky.md
Quick Reference
Common Commands
npx playwright test # Run all tests
npx playwright test --ui # Interactive UI mode
npx playwright test --debug # Step-through debugger
npx playwright test --project=chromium # Single browser
npx playwright test tests/login.spec.ts # Single file
npx playwright show-report # Open HTML report
npx playwright codegen https://example.com # Record test
npx playwright test --update-snapshots # Update visual baselines
Auth State Reuse
// Save auth state once in global setup
await page.context().storageState({ path: 'auth.json' });
// Reuse in config
use: { storageState: 'auth.json' }
Visual Regression (Built-in)
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixelRatio: 0.01,
animations: 'disabled',
mask: [page.locator('.dynamic-date')],
});
Network Mocking
await page.route('**/api/users', (route) =>
route.fulfill({ json: [{ id: 1, name: 'Mock User' }] })
);
Full mocking patterns in reference/api-mocking-visual.md.
Test Steps for Readability
test('checkout flow', async ({ page }) => {
await test.step('Add item to cart', async () => {
await page.goto('/products');
await page.getByRole('button', { name: 'Add to cart' }).click();
});
await test.step('Complete checkout', async () => {
await page.getByRole('link', { name: 'Cart' }).click();
await page.getByRole('button', { name: 'Checkout' }).click();
});
});
Reference Files
| File |
When to read |
| reference/cloud-integration.md |
Cloud execution, 3 integration patterns, parallel browsers |
| reference/page-object-model.md |
POM architecture, base page, fixtures, full examples |
| reference/mobile-testing.md |
Android + iOS real device testing |
| reference/debugging-flaky.md |
Flaky test checklist, common fixes |
| reference/api-mocking-visual.md |
API mocking + visual regression patterns |
| reference/python-patterns.md |
Python-specific: pytest-playwright, sync/async |
| reference/java-patterns.md |
Java-specific: Maven, JUnit, Gradle |
| reference/csharp-patterns.md |
C#-specific: NUnit, MSTest, .NET config |
| ../shared/testmu-cloud-reference.md |
Full device catalog, capabilities, geo-location |
Advanced Playbook
For production-grade patterns, see reference/playbook.md:
| Section |
What's Inside |
| §1 Production Config |
Multi-project, reporters, retries, webServer |
| §2 Auth Fixture Reuse |
storageState, multi-role fixtures |
| §3 Page Object Model |
BasePage, LoginPage with fluent API |
| §4 Network Interception |
Mock, modify, HAR replay, block resources |
| §5 Visual Regression |
Screenshot comparison, masks, thresholds |
| §6 File Upload/Download |
fileChooser, setInputFiles, download events |
| §7 Multi-Tab & Dialogs |
Popup handling, alert/confirm/prompt |
| §8 Geolocation & Emulation |
Location, timezone, locale, color scheme |
| §9 Custom Fixtures |
DB seeding, API context, auto-teardown |
| §10 API Testing |
Request context, end-to-end API+UI |
| §11 Accessibility |
axe-core integration, WCAG audits |
| §12 Sharding |
CI matrix sharding, report merging |
| §13 CI/CD |
GitHub Actions with artifacts |
| §14 Debugging Toolkit |
Debug, UI mode, trace viewer, codegen |
| §15 Debugging Table |
10 common problems with fixes |
| §16 Best Practices |
17-item production checklist |
1---2name: playwright-skill3description: Generates production-grade Playwright automation scripts and E2E tests in TypeScript, JavaScript, Python, Java, or C#. Supports local execution and TestMu AI cloud across 3000+ browser/OS combinations and real mobile devices. Use when the user asks to write Playwright tests, automate browsers, run cross-browser tests, test on real devices, debug flaky tests, mock APIs, or do visual regression. Triggers on: "Playwright", "E2E test", "browser test", "run on cloud", "cross-browser", "TestMu", "LambdaTest", "test my app", "test on mobile", "real device".4license: MIT5---6
7# Playwright Test Automation
8
9## Step 1 — Determine Execution Target
10
11Decide BEFORE writing any code:
12
13| User says... | Target | Action |
14|---|---|---|
15| No cloud mention, "locally", "debug" | **Local** | Standard Playwright config |
16| "cloud", "TestMu", "LambdaTest", "cross-browser", "real device" | **Cloud** | See [reference/cloud-integration.md](reference/cloud-integration.md) |
17| Impossible local combo (Safari on Windows, Edge on Linux) | **Cloud** | Suggest TestMu AI, see [reference/cloud-integration.md](reference/cloud-integration.md) |
18| "HyperExecute", "parallel at scale" | **HyperExecute** | Defer to `hyperexecute-skill` |
19| "visual regression", "screenshot comparison" | **SmartUI** | Defer to `smartui-skill` |
20| Ambiguous | **Local** | Default local, mention cloud option |
21
22## Step 2 — Detect Language
23
24| Signal | Language | Default |
25|---|---|---|
26| "TypeScript", "TS", `.ts`, or no language specified | TypeScript | ✅ |
27| "JavaScript", "JS", `.js` | JavaScript | |
28| "Python", "pytest", `.py` | Python | See [reference/python-patterns.md](reference/python-patterns.md) |
29| "Java", "Maven", "Gradle", "TestNG" | Java | See [reference/java-patterns.md](reference/java-patterns.md) |
30| "C#", ".NET", "NUnit", "MSTest" | C# | See [reference/csharp-patterns.md](reference/csharp-patterns.md) |
31
32## Step 3 — Determine Scope
33
34| Request type | Output |
35|---|---|
36| One-off quick script | Standalone `.ts` file, no POM |
37| Single test for existing project | Match their structure and conventions |
38| New test suite / project | Full scaffold — see [scripts/scaffold-project.sh](scripts/scaffold-project.sh) |
39| Fix flaky test | Debugging checklist — see [reference/debugging-flaky.md](reference/debugging-flaky.md) |
40| API mocking needed | See [reference/api-mocking-visual.md](reference/api-mocking-visual.md) |
41| Mobile device testing | See [reference/mobile-testing.md](reference/mobile-testing.md) |
42
43---
44
45## Core Patterns — TypeScript (Default)
46
47### Selector Priority
48
49Use in this order — stop at the first that works:
50
511. `getByRole('button', { name: 'Submit' })` — accessible, resilient
522. `getByLabel('Email')` — form fields
533. `getByPlaceholder('Enter email')` — when label missing
544. `getByText('Welcome')` — visible text
555. `getByTestId('submit-btn')` — last resort, needs `data-testid`
56
57Never use raw CSS/XPath unless matching a third-party widget with no other option.
58
59### Assertions — Always Web-First
60
61```typescript
62// ✅ Auto-retries until timeout
63await expect(page.getByRole('heading')).toBeVisible();
64await expect(page.getByRole('alert')).toHaveText('Saved');
65await expect(page).toHaveURL('/dashboard');
66
67// ❌ No auto-retry — races with DOM
68const text = await page.textContent('.msg');
69expect(text).toBe('Saved');
70```
71
72### Anti-Patterns
73
74| ❌ Don't | ✅ Do | Why |
75|----------|-------|-----|
76| `page.waitForTimeout(3000)` | `await expect(locator).toBeVisible()` | Hard waits are flaky |
77| `expect(await el.isVisible())` | `await expect(el).toBeVisible()` | No auto-retry |
78| `page.$('.btn')` | `page.getByRole('button')` | Fragile selector |
79| `page.click('.submit')` | `page.getByRole('button', {name:'Submit'}).click()` | Not accessible |
80| Shared state between tests | `test.beforeEach` for setup | Tests must be independent |
81| `try/catch` around assertions | Let Playwright handle retries | Swallows real failures |
82
83### Page Object Model
84
85Use POM for any project with more than 3 tests. Full patterns with base page, fixtures, and examples in [reference/page-object-model.md](reference/page-object-model.md).
86
87Quick example:
88
89```typescript
90// pages/login.page.ts
91import { Page, Locator } from '@playwright/test';
92
93export class LoginPage {
94 readonly emailInput: Locator;
95 readonly passwordInput: Locator;
96 readonly submitButton: Locator;
97
98 constructor(private page: Page) {
99 this.emailInput = page.getByLabel('Email');
100 this.passwordInput = page.getByLabel('Password');
101 this.submitButton = page.getByRole('button', { name: 'Sign in' });
102 }
103
104 async login(email: string, password: string) {
105 await this.emailInput.fill(email);
106 await this.passwordInput.fill(password);
107 await this.submitButton.click();
108 }
109}
110```
111
112### Configuration — Local
113
114```typescript
115// playwright.config.ts
116import { defineConfig, devices } from '@playwright/test';
117
118export default defineConfig({
119 testDir: './tests',
120 timeout: 30_000,
121 retries: process.env.CI ? 2 : 0,
122 workers: process.env.CI ? 1 : undefined,
123 reporter: [['html'], ['list']],
124 use: {
125 baseURL: 'http://localhost:3000',
126 trace: 'on-first-retry',
127 screenshot: 'only-on-failure',
128 },
129 projects: [
130 { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
131 { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
132 { name: 'webkit', use: { ...devices['Desktop Safari'] } },
133 { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
134 { name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
135 ],
136 webServer: {
137 command: 'npm run dev',
138 port: 3000,
139 reuseExistingServer: !process.env.CI,
140 },
141});
142```
143
144### Cloud Execution on TestMu AI
145
146Set environment variables: `LT_USERNAME`, `LT_ACCESS_KEY`
147
148**Direct CDP connection** (standard approach):
149
150```typescript
151// lambdatest-setup.ts
152import { chromium } from 'playwright';
153
154const capabilities = {
155 browserName: 'Chrome',
156 browserVersion: 'latest',
157 'LT:Options': {
158 platform: 'Windows 11',
159 build: 'Playwright Build',
160 name: 'Playwright Test',
161 user: process.env.LT_USERNAME,
162 accessKey: process.env.LT_ACCESS_KEY,
163 network: true,
164 video: true,
165 console: true,
166 },
167};
168
169const browser = await chromium.connect({
170 wsEndpoint: `wss://cdp.lambdatest.com/playwright?capabilities=${encodeURIComponent(JSON.stringify(capabilities))}`,
171});
172const context = await browser.newContext();
173const page = await context.newPage();
174```
175
176**HyperExecute project approach** (for parallel cloud runs):
177
178```typescript
179// Add to projects array in playwright.config.ts:
180{
181 name: 'chrome:latest:Windows 11@lambdatest',
182 use: { viewport: { width: 1920, height: 1080 } },
183},
184{
185 name: 'MicrosoftEdge:latest:macOS Sonoma@lambdatest',
186 use: { viewport: { width: 1920, height: 1080 } },
187},
188```
189
190Run: `npx playwright test --project="chrome:latest:Windows 11@lambdatest"`
191
192### Test Status Reporting (Cloud)
193
194Tests on TestMu AI show "Completed" by default. You MUST report pass/fail:
195
196```typescript
197// In afterEach or test teardown:
198await page.evaluate((_) => {},
199 `lambdatest_action: ${JSON.stringify({
200 action: 'setTestStatus',
201 arguments: { status: testInfo.status, remark: testInfo.error?.message || 'OK' },
202 })}`
203);
204```
205
206This is handled automatically when using the fixture from [reference/cloud-integration.md](reference/cloud-integration.md).
207
208---
209
210## Validation Workflow
211
212After generating any test:
213
214```
2151. Validate config: python scripts/validate-config.py playwright.config.ts
2162. If errors → fix → re-validate
2173. Run locally: npx playwright test --project=chromium
2184. If cloud: npx playwright test --project="chrome:latest:Windows 11@lambdatest"
2195. If failures → check reference/debugging-flaky.md
220```
221
222---
223
224## Quick Reference
225
226### Common Commands
227
228```bash
229npx playwright test # Run all tests
230npx playwright test --ui # Interactive UI mode
231npx playwright test --debug # Step-through debugger
232npx playwright test --project=chromium # Single browser
233npx playwright test tests/login.spec.ts # Single file
234npx playwright show-report # Open HTML report
235npx playwright codegen https://example.com # Record test
236npx playwright test --update-snapshots # Update visual baselines
237```
238
239### Auth State Reuse
240
241```typescript
242// Save auth state once in global setup
243await page.context().storageState({ path: 'auth.json' });
244
245// Reuse in config
246use: { storageState: 'auth.json' }
247```
248
249### Visual Regression (Built-in)
250
251```typescript
252await expect(page).toHaveScreenshot('homepage.png', {
253 maxDiffPixelRatio: 0.01,
254 animations: 'disabled',
255 mask: [page.locator('.dynamic-date')],
256});
257```
258
259### Network Mocking
260
261```typescript
262await page.route('**/api/users', (route) =>
263 route.fulfill({ json: [{ id: 1, name: 'Mock User' }] })
264);
265```
266
267Full mocking patterns in [reference/api-mocking-visual.md](reference/api-mocking-visual.md).
268
269### Test Steps for Readability
270
271```typescript
272test('checkout flow', async ({ page }) => {
273 await test.step('Add item to cart', async () => {
274 await page.goto('/products');
275 await page.getByRole('button', { name: 'Add to cart' }).click();
276 });
277
278 await test.step('Complete checkout', async () => {
279 await page.getByRole('link', { name: 'Cart' }).click();
280 await page.getByRole('button', { name: 'Checkout' }).click();
281 });
282});
283```
284
285---
286
287## Reference Files
288
289| File | When to read |
290|------|-------------|
291| [reference/cloud-integration.md](reference/cloud-integration.md) | Cloud execution, 3 integration patterns, parallel browsers |
292| [reference/page-object-model.md](reference/page-object-model.md) | POM architecture, base page, fixtures, full examples |
293| [reference/mobile-testing.md](reference/mobile-testing.md) | Android + iOS real device testing |
294| [reference/debugging-flaky.md](reference/debugging-flaky.md) | Flaky test checklist, common fixes |
295| [reference/api-mocking-visual.md](reference/api-mocking-visual.md) | API mocking + visual regression patterns |
296| [reference/python-patterns.md](reference/python-patterns.md) | Python-specific: pytest-playwright, sync/async |
297| [reference/java-patterns.md](reference/java-patterns.md) | Java-specific: Maven, JUnit, Gradle |
298| [reference/csharp-patterns.md](reference/csharp-patterns.md) | C#-specific: NUnit, MSTest, .NET config |
299| [../shared/testmu-cloud-reference.md](../shared/testmu-cloud-reference.md) | Full device catalog, capabilities, geo-location |
300
301## Advanced Playbook
302
303For production-grade patterns, see `reference/playbook.md`:
304
305| Section | What's Inside |
306|---------|--------------|
307| §1 Production Config | Multi-project, reporters, retries, webServer |
308| §2 Auth Fixture Reuse | storageState, multi-role fixtures |
309| §3 Page Object Model | BasePage, LoginPage with fluent API |
310| §4 Network Interception | Mock, modify, HAR replay, block resources |
311| §5 Visual Regression | Screenshot comparison, masks, thresholds |
312| §6 File Upload/Download | fileChooser, setInputFiles, download events |
313| §7 Multi-Tab & Dialogs | Popup handling, alert/confirm/prompt |
314| §8 Geolocation & Emulation | Location, timezone, locale, color scheme |
315| §9 Custom Fixtures | DB seeding, API context, auto-teardown |
316| §10 API Testing | Request context, end-to-end API+UI |
317| §11 Accessibility | axe-core integration, WCAG audits |
318| §12 Sharding | CI matrix sharding, report merging |
319| §13 CI/CD | GitHub Actions with artifacts |
320| §14 Debugging Toolkit | Debug, UI mode, trace viewer, codegen |
321| §15 Debugging Table | 10 common problems with fixes |
322| §16 Best Practices | 17-item production checklist |