omgkit 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  3. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  4. package/plugin/skills/databases/redis/SKILL.md +53 -860
  5. package/plugin/skills/devops/aws/SKILL.md +68 -672
  6. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  7. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  8. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  9. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  10. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  11. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  12. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  13. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  14. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  15. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  16. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  17. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  18. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  19. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  20. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  21. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  22. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  23. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  24. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  25. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  26. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  27. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  28. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  29. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  30. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  31. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  32. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  33. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  34. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  35. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  36. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  37. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  38. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  39. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  40. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  41. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  42. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  43. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  44. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  45. package/plugin/skills/security/oauth/SKILL.md +80 -934
  46. package/plugin/skills/security/owasp/SKILL.md +78 -862
  47. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  48. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  49. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  50. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  51. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  52. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  53. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  54. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  55. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,35 +1,11 @@
1
1
  ---
2
- name: playwright
3
- description: Playwright E2E testing with browser automation, visual regression, API testing, and CI integration
4
- category: testing
5
- triggers:
6
- - playwright
7
- - e2e testing
8
- - browser automation
9
- - end-to-end
10
- - visual testing
11
- - cross-browser testing
2
+ name: Testing with Playwright
3
+ description: Claude writes reliable E2E tests using Playwright for browser automation. Use when writing end-to-end tests, implementing Page Object Model, visual regression testing, API mocking, or cross-browser testing.
12
4
  ---
13
5
 
14
- # Playwright
6
+ # Testing with Playwright
15
7
 
16
- Enterprise-grade **E2E testing framework** following industry best practices. This skill covers browser automation, visual regression testing, API testing, network interception, and CI/CD integration patterns used by top engineering teams.
17
-
18
- ## Purpose
19
-
20
- Build reliable end-to-end test suites:
21
-
22
- - Write resilient E2E tests with auto-waiting
23
- - Implement Page Object Model patterns
24
- - Configure cross-browser and mobile testing
25
- - Set up visual regression testing
26
- - Integrate with CI/CD pipelines
27
- - Mock APIs and intercept network requests
28
- - Generate test reports and traces
29
-
30
- ## Features
31
-
32
- ### 1. Configuration Setup
8
+ ## Quick Start
33
9
 
34
10
  ```typescript
35
11
  // playwright.config.ts
@@ -38,744 +14,146 @@ import { defineConfig, devices } from "@playwright/test";
38
14
  export default defineConfig({
39
15
  testDir: "./tests/e2e",
40
16
  fullyParallel: true,
41
- forbidOnly: !!process.env.CI,
42
17
  retries: process.env.CI ? 2 : 0,
43
- workers: process.env.CI ? 1 : undefined,
44
- reporter: [
45
- ["list"],
46
- ["html", { outputFolder: "playwright-report" }],
47
- ["json", { outputFile: "test-results.json" }],
48
- ["junit", { outputFile: "junit.xml" }],
49
- ],
18
+ reporter: [["list"], ["html"]],
50
19
  use: {
51
- baseURL: process.env.BASE_URL || "http://localhost:3000",
20
+ baseURL: "http://localhost:3000",
52
21
  trace: "on-first-retry",
53
22
  screenshot: "only-on-failure",
54
- video: "retain-on-failure",
55
- actionTimeout: 10000,
56
- navigationTimeout: 30000,
57
23
  },
58
24
  projects: [
59
- {
60
- name: "chromium",
61
- use: { ...devices["Desktop Chrome"] },
62
- },
63
- {
64
- name: "firefox",
65
- use: { ...devices["Desktop Firefox"] },
66
- },
67
- {
68
- name: "webkit",
69
- use: { ...devices["Desktop Safari"] },
70
- },
71
- {
72
- name: "mobile-chrome",
73
- use: { ...devices["Pixel 5"] },
74
- },
75
- {
76
- name: "mobile-safari",
77
- use: { ...devices["iPhone 12"] },
78
- },
79
- {
80
- name: "tablet",
81
- use: { ...devices["iPad Pro 11"] },
82
- },
25
+ { name: "chromium", use: { ...devices["Desktop Chrome"] } },
26
+ { name: "firefox", use: { ...devices["Desktop Firefox"] } },
27
+ { name: "mobile", use: { ...devices["iPhone 12"] } },
83
28
  ],
84
- webServer: {
85
- command: "npm run dev",
86
- url: "http://localhost:3000",
87
- reuseExistingServer: !process.env.CI,
88
- timeout: 120000,
89
- },
29
+ webServer: { command: "npm run dev", url: "http://localhost:3000" },
90
30
  });
91
31
  ```
92
32
 
93
- ### 2. Page Object Model
94
-
95
- ```typescript
96
- // tests/pages/base.page.ts
97
- import { Page, Locator, expect } from "@playwright/test";
98
-
99
- export abstract class BasePage {
100
- constructor(protected page: Page) {}
101
-
102
- abstract get url(): string;
103
-
104
- async navigate(): Promise<void> {
105
- await this.page.goto(this.url);
106
- await this.waitForPageLoad();
107
- }
33
+ ## Features
108
34
 
109
- async waitForPageLoad(): Promise<void> {
110
- await this.page.waitForLoadState("networkidle");
111
- }
35
+ | Feature | Description | Reference |
36
+ |---------|-------------|-----------|
37
+ | Page Object Model | Maintainable test architecture pattern | [POM Guide](https://playwright.dev/docs/pom) |
38
+ | Auto-Waiting | Built-in waiting for elements and assertions | [Auto-Waiting](https://playwright.dev/docs/actionability) |
39
+ | Network Mocking | Intercept and mock API responses | [Network](https://playwright.dev/docs/network) |
40
+ | Visual Testing | Screenshot comparison for regression testing | [Visual Comparisons](https://playwright.dev/docs/test-snapshots) |
41
+ | Cross-Browser | Chrome, Firefox, Safari, mobile devices | [Browsers](https://playwright.dev/docs/browsers) |
42
+ | Trace Viewer | Debug failing tests with timeline | [Trace Viewer](https://playwright.dev/docs/trace-viewer) |
112
43
 
113
- async getTitle(): Promise<string> {
114
- return this.page.title();
115
- }
44
+ ## Common Patterns
116
45
 
117
- async screenshot(name: string): Promise<void> {
118
- await this.page.screenshot({ path: `screenshots/${name}.png` });
119
- }
120
- }
46
+ ### Page Object Model
121
47
 
48
+ ```typescript
122
49
  // tests/pages/login.page.ts
123
50
  import { Page, Locator, expect } from "@playwright/test";
124
- import { BasePage } from "./base.page";
125
51
 
126
- export class LoginPage extends BasePage {
52
+ export class LoginPage {
127
53
  readonly emailInput: Locator;
128
54
  readonly passwordInput: Locator;
129
55
  readonly submitButton: Locator;
130
- readonly errorMessage: Locator;
131
- readonly forgotPasswordLink: Locator;
132
- readonly rememberMeCheckbox: Locator;
133
56
 
134
- constructor(page: Page) {
135
- super(page);
57
+ constructor(private page: Page) {
136
58
  this.emailInput = page.getByLabel("Email");
137
59
  this.passwordInput = page.getByLabel("Password");
138
60
  this.submitButton = page.getByRole("button", { name: "Sign in" });
139
- this.errorMessage = page.getByRole("alert");
140
- this.forgotPasswordLink = page.getByRole("link", { name: "Forgot password?" });
141
- this.rememberMeCheckbox = page.getByLabel("Remember me");
142
- }
143
-
144
- get url(): string {
145
- return "/login";
146
- }
147
-
148
- async login(email: string, password: string): Promise<void> {
149
- await this.emailInput.fill(email);
150
- await this.passwordInput.fill(password);
151
- await this.submitButton.click();
152
61
  }
153
62
 
154
- async loginWithRememberMe(email: string, password: string): Promise<void> {
63
+ async login(email: string, password: string) {
155
64
  await this.emailInput.fill(email);
156
65
  await this.passwordInput.fill(password);
157
- await this.rememberMeCheckbox.check();
158
66
  await this.submitButton.click();
159
67
  }
160
68
 
161
- async expectErrorMessage(message: string): Promise<void> {
162
- await expect(this.errorMessage).toContainText(message);
163
- }
164
-
165
- async expectLoginFormVisible(): Promise<void> {
166
- await expect(this.emailInput).toBeVisible();
167
- await expect(this.passwordInput).toBeVisible();
168
- await expect(this.submitButton).toBeVisible();
69
+ async expectError(message: string) {
70
+ await expect(this.page.getByRole("alert")).toContainText(message);
169
71
  }
170
72
  }
73
+ ```
171
74
 
172
- // tests/pages/dashboard.page.ts
173
- import { Page, Locator, expect } from "@playwright/test";
174
- import { BasePage } from "./base.page";
175
-
176
- export class DashboardPage extends BasePage {
177
- readonly welcomeMessage: Locator;
178
- readonly userMenu: Locator;
179
- readonly logoutButton: Locator;
180
- readonly navigationItems: Locator;
181
- readonly searchInput: Locator;
182
-
183
- constructor(page: Page) {
184
- super(page);
185
- this.welcomeMessage = page.getByTestId("welcome-message");
186
- this.userMenu = page.getByTestId("user-menu");
187
- this.logoutButton = page.getByRole("button", { name: "Logout" });
188
- this.navigationItems = page.getByRole("navigation").getByRole("link");
189
- this.searchInput = page.getByPlaceholder("Search...");
190
- }
191
-
192
- get url(): string {
193
- return "/dashboard";
194
- }
195
-
196
- async expectWelcomeMessage(name: string): Promise<void> {
197
- await expect(this.welcomeMessage).toContainText(`Welcome, ${name}`);
198
- }
75
+ ### API Mocking
199
76
 
200
- async logout(): Promise<void> {
201
- await this.userMenu.click();
202
- await this.logoutButton.click();
203
- }
77
+ ```typescript
78
+ import { test, expect } from "@playwright/test";
204
79
 
205
- async search(query: string): Promise<void> {
206
- await this.searchInput.fill(query);
207
- await this.searchInput.press("Enter");
208
- }
80
+ test("mock API response", async ({ page }) => {
81
+ await page.route("**/api/users", (route) =>
82
+ route.fulfill({
83
+ status: 200,
84
+ contentType: "application/json",
85
+ body: JSON.stringify({ users: [{ id: 1, name: "John" }] }),
86
+ })
87
+ );
88
+
89
+ await page.goto("/users");
90
+ await expect(page.getByText("John")).toBeVisible();
91
+ });
209
92
 
210
- async navigateTo(item: string): Promise<void> {
211
- await this.navigationItems.filter({ hasText: item }).click();
212
- }
213
- }
93
+ test("capture network requests", async ({ page }) => {
94
+ const requestPromise = page.waitForRequest("**/api/analytics");
95
+ await page.goto("/dashboard");
96
+ const request = await requestPromise;
97
+ expect(request.postDataJSON()).toMatchObject({ event: "page_view" });
98
+ });
214
99
  ```
215
100
 
216
- ### 3. Test Fixtures
101
+ ### Authentication Fixture
217
102
 
218
103
  ```typescript
219
104
  // tests/fixtures/auth.fixture.ts
220
- import { test as base, expect } from "@playwright/test";
105
+ import { test as base } from "@playwright/test";
221
106
  import { LoginPage } from "../pages/login.page";
222
- import { DashboardPage } from "../pages/dashboard.page";
223
-
224
- interface TestUser {
225
- email: string;
226
- password: string;
227
- name: string;
228
- }
229
107
 
230
- interface AuthFixtures {
231
- loginPage: LoginPage;
232
- dashboardPage: DashboardPage;
233
- testUser: TestUser;
234
- authenticatedPage: DashboardPage;
235
- }
236
-
237
- export const test = base.extend<AuthFixtures>({
238
- testUser: {
239
- email: "test@example.com",
240
- password: "SecurePassword123!",
241
- name: "Test User",
242
- },
243
-
244
- loginPage: async ({ page }, use) => {
245
- const loginPage = new LoginPage(page);
246
- await use(loginPage);
247
- },
248
-
249
- dashboardPage: async ({ page }, use) => {
250
- const dashboardPage = new DashboardPage(page);
251
- await use(dashboardPage);
252
- },
253
-
254
- authenticatedPage: async ({ page, testUser }, use) => {
255
- // Login via API for faster test setup
108
+ export const test = base.extend<{ authenticatedPage: Page }>({
109
+ authenticatedPage: async ({ page }, use) => {
110
+ // Fast auth via API
256
111
  const response = await page.request.post("/api/auth/login", {
257
- data: {
258
- email: testUser.email,
259
- password: testUser.password,
260
- },
112
+ data: { email: "test@example.com", password: "password" },
261
113
  });
262
-
263
114
  const { token } = await response.json();
264
115
 
265
- // Set auth cookie/storage
266
116
  await page.context().addCookies([
267
- {
268
- name: "auth_token",
269
- value: token,
270
- domain: "localhost",
271
- path: "/",
272
- },
117
+ { name: "auth_token", value: token, domain: "localhost", path: "/" },
273
118
  ]);
274
119
 
275
- const dashboardPage = new DashboardPage(page);
276
- await dashboardPage.navigate();
277
- await use(dashboardPage);
278
- },
279
- });
280
-
281
- export { expect };
282
- ```
283
-
284
- ### 4. API Mocking and Network Interception
285
-
286
- ```typescript
287
- // tests/e2e/api-mocking.spec.ts
288
- import { test, expect } from "@playwright/test";
289
-
290
- test.describe("API Mocking", () => {
291
- test("mock successful API response", async ({ page }) => {
292
- await page.route("**/api/users", (route) =>
293
- route.fulfill({
294
- status: 200,
295
- contentType: "application/json",
296
- body: JSON.stringify({
297
- users: [
298
- { id: 1, name: "John Doe", email: "john@example.com" },
299
- { id: 2, name: "Jane Smith", email: "jane@example.com" },
300
- ],
301
- }),
302
- })
303
- );
304
-
305
- await page.goto("/users");
306
-
307
- await expect(page.getByText("John Doe")).toBeVisible();
308
- await expect(page.getByText("Jane Smith")).toBeVisible();
309
- });
310
-
311
- test("mock API error response", async ({ page }) => {
312
- await page.route("**/api/users", (route) =>
313
- route.fulfill({
314
- status: 500,
315
- contentType: "application/json",
316
- body: JSON.stringify({ error: "Internal server error" }),
317
- })
318
- );
319
-
320
- await page.goto("/users");
321
-
322
- await expect(page.getByText("Failed to load users")).toBeVisible();
323
- });
324
-
325
- test("intercept and modify requests", async ({ page }) => {
326
- await page.route("**/api/search**", (route) => {
327
- const url = new URL(route.request().url());
328
- url.searchParams.set("limit", "10");
329
-
330
- route.continue({ url: url.toString() });
331
- });
332
-
333
- await page.goto("/search");
334
- await page.fill('[name="query"]', "test");
335
- await page.click('button[type="submit"]');
336
-
337
- // Verify modified request was made
338
- });
339
-
340
- test("delay API response", async ({ page }) => {
341
- await page.route("**/api/slow-endpoint", async (route) => {
342
- await new Promise((resolve) => setTimeout(resolve, 2000));
343
- await route.fulfill({
344
- status: 200,
345
- body: JSON.stringify({ data: "delayed response" }),
346
- });
347
- });
348
-
349
- await page.goto("/slow-page");
350
-
351
- // Verify loading state appears
352
- await expect(page.getByTestId("loading-spinner")).toBeVisible();
353
-
354
- // Verify content appears after delay
355
- await expect(page.getByText("delayed response")).toBeVisible({
356
- timeout: 5000,
357
- });
358
- });
359
-
360
- test("capture and assert network requests", async ({ page }) => {
361
- const requestPromise = page.waitForRequest("**/api/analytics");
362
-
363
120
  await page.goto("/dashboard");
364
-
365
- const request = await requestPromise;
366
- const postData = request.postDataJSON();
367
-
368
- expect(postData).toMatchObject({
369
- event: "page_view",
370
- page: "/dashboard",
371
- });
372
- });
373
-
374
- test("wait for specific response", async ({ page }) => {
375
- const responsePromise = page.waitForResponse(
376
- (response) =>
377
- response.url().includes("/api/data") && response.status() === 200
378
- );
379
-
380
- await page.goto("/data-page");
381
-
382
- const response = await responsePromise;
383
- const data = await response.json();
384
-
385
- expect(data).toHaveProperty("items");
386
- });
387
- });
388
- ```
389
-
390
- ### 5. Visual Regression Testing
391
-
392
- ```typescript
393
- // tests/e2e/visual.spec.ts
394
- import { test, expect } from "@playwright/test";
395
-
396
- test.describe("Visual Regression Tests", () => {
397
- test.beforeEach(async ({ page }) => {
398
- // Disable animations for consistent screenshots
399
- await page.addStyleTag({
400
- content: `
401
- *, *::before, *::after {
402
- animation-duration: 0s !important;
403
- transition-duration: 0s !important;
404
- }
405
- `,
406
- });
407
- });
408
-
409
- test("homepage visual snapshot", async ({ page }) => {
410
- await page.goto("/");
411
- await page.waitForLoadState("networkidle");
412
-
413
- await expect(page).toHaveScreenshot("homepage.png", {
414
- fullPage: true,
415
- maxDiffPixels: 100,
416
- });
417
- });
418
-
419
- test("component visual snapshot", async ({ page }) => {
420
- await page.goto("/components/button");
421
-
422
- const button = page.getByTestId("primary-button");
423
- await expect(button).toHaveScreenshot("primary-button.png");
424
-
425
- // Hover state
426
- await button.hover();
427
- await expect(button).toHaveScreenshot("primary-button-hover.png");
428
-
429
- // Focus state
430
- await button.focus();
431
- await expect(button).toHaveScreenshot("primary-button-focus.png");
432
- });
433
-
434
- test("responsive visual snapshots", async ({ page }) => {
435
- await page.goto("/");
436
-
437
- // Desktop
438
- await page.setViewportSize({ width: 1920, height: 1080 });
439
- await expect(page).toHaveScreenshot("homepage-desktop.png");
440
-
441
- // Tablet
442
- await page.setViewportSize({ width: 768, height: 1024 });
443
- await expect(page).toHaveScreenshot("homepage-tablet.png");
444
-
445
- // Mobile
446
- await page.setViewportSize({ width: 375, height: 667 });
447
- await expect(page).toHaveScreenshot("homepage-mobile.png");
448
- });
449
-
450
- test("dark mode visual snapshot", async ({ page }) => {
451
- await page.goto("/");
452
-
453
- // Toggle dark mode
454
- await page.click('[data-testid="theme-toggle"]');
455
- await page.waitForSelector('[data-theme="dark"]');
456
-
457
- await expect(page).toHaveScreenshot("homepage-dark-mode.png", {
458
- fullPage: true,
459
- });
460
- });
461
-
462
- test("mask dynamic content", async ({ page }) => {
463
- await page.goto("/dashboard");
464
-
465
- await expect(page).toHaveScreenshot("dashboard.png", {
466
- mask: [
467
- page.getByTestId("timestamp"),
468
- page.getByTestId("user-avatar"),
469
- page.getByTestId("random-content"),
470
- ],
471
- });
472
- });
473
- });
474
- ```
475
-
476
- ### 6. Authentication and Session Management
477
-
478
- ```typescript
479
- // tests/e2e/auth.spec.ts
480
- import { test, expect } from "../fixtures/auth.fixture";
481
-
482
- test.describe("Authentication Flow", () => {
483
- test("successful login redirects to dashboard", async ({ page, loginPage, testUser }) => {
484
- await loginPage.navigate();
485
- await loginPage.login(testUser.email, testUser.password);
486
-
487
- await expect(page).toHaveURL("/dashboard");
488
- await expect(page.getByTestId("welcome-message")).toContainText(
489
- testUser.name
490
- );
491
- });
492
-
493
- test("invalid credentials show error message", async ({ loginPage }) => {
494
- await loginPage.navigate();
495
- await loginPage.login("wrong@example.com", "wrongpassword");
496
-
497
- await loginPage.expectErrorMessage("Invalid email or password");
498
- });
499
-
500
- test("logout clears session", async ({ page, authenticatedPage }) => {
501
- await authenticatedPage.logout();
502
-
503
- await expect(page).toHaveURL("/login");
504
-
505
- // Verify session is cleared by trying to access protected route
506
- await page.goto("/dashboard");
507
- await expect(page).toHaveURL("/login");
508
- });
509
-
510
- test("remember me persists session", async ({ page, loginPage, testUser, context }) => {
511
- await loginPage.navigate();
512
- await loginPage.loginWithRememberMe(testUser.email, testUser.password);
513
-
514
- // Create new page in same context (simulates new tab)
515
- const newPage = await context.newPage();
516
- await newPage.goto("/dashboard");
517
-
518
- await expect(newPage).toHaveURL("/dashboard");
519
- await newPage.close();
520
- });
521
-
522
- test("session expires after timeout", async ({ page, authenticatedPage }) => {
523
- // Mock session expiration
524
- await page.route("**/api/session/check", (route) =>
525
- route.fulfill({
526
- status: 401,
527
- body: JSON.stringify({ error: "Session expired" }),
528
- })
529
- );
530
-
531
- // Trigger session check
532
- await page.click('[data-testid="refresh-data"]');
533
-
534
- await expect(page.getByText("Session expired")).toBeVisible();
535
- });
536
- });
537
-
538
- // tests/auth.setup.ts - Global auth setup
539
- import { test as setup, expect } from "@playwright/test";
540
- import path from "path";
541
-
542
- const authFile = path.join(__dirname, "../.auth/user.json");
543
-
544
- setup("authenticate", async ({ page }) => {
545
- await page.goto("/login");
546
- await page.fill('[name="email"]', process.env.TEST_USER_EMAIL!);
547
- await page.fill('[name="password"]', process.env.TEST_USER_PASSWORD!);
548
- await page.click('button[type="submit"]');
549
-
550
- await expect(page).toHaveURL("/dashboard");
551
-
552
- await page.context().storageState({ path: authFile });
553
- });
554
- ```
555
-
556
- ### 7. Form and User Interaction Testing
557
-
558
- ```typescript
559
- // tests/e2e/forms.spec.ts
560
- import { test, expect } from "@playwright/test";
561
-
562
- test.describe("Form Interactions", () => {
563
- test("complete multi-step form", async ({ page }) => {
564
- await page.goto("/onboarding");
565
-
566
- // Step 1: Personal Info
567
- await page.fill('[name="firstName"]', "John");
568
- await page.fill('[name="lastName"]', "Doe");
569
- await page.fill('[name="email"]', "john.doe@example.com");
570
- await page.click('button:has-text("Next")');
571
-
572
- // Step 2: Preferences
573
- await page.check('[name="newsletter"]');
574
- await page.selectOption('[name="timezone"]', "America/New_York");
575
- await page.click('button:has-text("Next")');
576
-
577
- // Step 3: Confirmation
578
- await expect(page.getByText("John Doe")).toBeVisible();
579
- await expect(page.getByText("john.doe@example.com")).toBeVisible();
580
- await page.click('button:has-text("Complete")');
581
-
582
- await expect(page).toHaveURL("/welcome");
583
- });
584
-
585
- test("form validation errors", async ({ page }) => {
586
- await page.goto("/register");
587
-
588
- // Submit empty form
589
- await page.click('button[type="submit"]');
590
-
591
- // Check validation messages
592
- await expect(page.getByText("Email is required")).toBeVisible();
593
- await expect(page.getByText("Password is required")).toBeVisible();
594
-
595
- // Fill invalid email
596
- await page.fill('[name="email"]', "invalid-email");
597
- await page.click('button[type="submit"]');
598
- await expect(page.getByText("Invalid email format")).toBeVisible();
599
-
600
- // Fill weak password
601
- await page.fill('[name="email"]', "valid@example.com");
602
- await page.fill('[name="password"]', "123");
603
- await page.click('button[type="submit"]');
604
- await expect(page.getByText("Password must be at least 8 characters")).toBeVisible();
605
- });
606
-
607
- test("file upload", async ({ page }) => {
608
- await page.goto("/upload");
609
-
610
- const fileInput = page.locator('input[type="file"]');
611
- await fileInput.setInputFiles({
612
- name: "test-file.pdf",
613
- mimeType: "application/pdf",
614
- buffer: Buffer.from("PDF content"),
615
- });
616
-
617
- await expect(page.getByText("test-file.pdf")).toBeVisible();
618
- await page.click('button:has-text("Upload")');
619
-
620
- await expect(page.getByText("Upload successful")).toBeVisible();
621
- });
622
-
623
- test("drag and drop", async ({ page }) => {
624
- await page.goto("/kanban");
625
-
626
- const sourceCard = page.locator('[data-testid="card-1"]');
627
- const targetColumn = page.locator('[data-testid="done-column"]');
628
-
629
- await sourceCard.dragTo(targetColumn);
630
-
631
- await expect(targetColumn.locator('[data-testid="card-1"]')).toBeVisible();
632
- });
633
-
634
- test("autocomplete interaction", async ({ page }) => {
635
- await page.goto("/search");
636
-
637
- await page.fill('[name="search"]', "pla");
638
- await expect(page.getByRole("listbox")).toBeVisible();
639
-
640
- const suggestions = page.getByRole("option");
641
- await expect(suggestions).toHaveCount(5);
642
-
643
- await suggestions.filter({ hasText: "Playwright" }).click();
644
- await expect(page.locator('[name="search"]')).toHaveValue("Playwright");
645
- });
121
+ await use(page);
122
+ },
646
123
  });
647
124
  ```
648
125
 
649
- ## Use Cases
650
-
651
- ### CI/CD Integration
652
-
653
- ```yaml
654
- # .github/workflows/e2e.yml
655
- name: E2E Tests
656
-
657
- on:
658
- push:
659
- branches: [main]
660
- pull_request:
661
- branches: [main]
662
-
663
- jobs:
664
- e2e:
665
- runs-on: ubuntu-latest
666
- steps:
667
- - uses: actions/checkout@v4
668
-
669
- - name: Setup Node.js
670
- uses: actions/setup-node@v4
671
- with:
672
- node-version: "20"
673
- cache: "npm"
674
-
675
- - name: Install dependencies
676
- run: npm ci
677
-
678
- - name: Install Playwright browsers
679
- run: npx playwright install --with-deps
680
-
681
- - name: Build application
682
- run: npm run build
683
-
684
- - name: Run E2E tests
685
- run: npx playwright test
686
- env:
687
- BASE_URL: http://localhost:3000
688
- TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
689
- TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
690
-
691
- - name: Upload test results
692
- uses: actions/upload-artifact@v4
693
- if: always()
694
- with:
695
- name: playwright-report
696
- path: playwright-report/
697
- retention-days: 30
698
-
699
- - name: Upload test traces
700
- uses: actions/upload-artifact@v4
701
- if: failure()
702
- with:
703
- name: test-traces
704
- path: test-results/
705
- ```
706
-
707
- ### Accessibility Testing
126
+ ### Visual Regression Testing
708
127
 
709
128
  ```typescript
710
- // tests/e2e/accessibility.spec.ts
711
- import { test, expect } from "@playwright/test";
712
- import AxeBuilder from "@axe-core/playwright";
713
-
714
- test.describe("Accessibility", () => {
715
- test("homepage has no accessibility violations", async ({ page }) => {
716
- await page.goto("/");
717
-
718
- const results = await new AxeBuilder({ page })
719
- .withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"])
720
- .analyze();
721
-
722
- expect(results.violations).toEqual([]);
129
+ test("visual snapshot", async ({ page }) => {
130
+ await page.goto("/");
131
+ await page.addStyleTag({
132
+ content: "*, *::before, *::after { animation-duration: 0s !important; }",
723
133
  });
724
134
 
725
- test("form has proper labels", async ({ page }) => {
726
- await page.goto("/contact");
727
-
728
- const results = await new AxeBuilder({ page })
729
- .include("form")
730
- .analyze();
731
-
732
- expect(results.violations).toEqual([]);
135
+ await expect(page).toHaveScreenshot("homepage.png", {
136
+ fullPage: true,
137
+ maxDiffPixels: 100,
733
138
  });
734
139
 
735
- test("keyboard navigation works", async ({ page }) => {
736
- await page.goto("/");
737
-
738
- // Tab through interactive elements
739
- await page.keyboard.press("Tab");
740
- await expect(page.locator(":focus")).toHaveAttribute("role", "link");
741
-
742
- await page.keyboard.press("Tab");
743
- await expect(page.locator(":focus")).toHaveAttribute("role", "button");
744
-
745
- // Activate with Enter
746
- await page.keyboard.press("Enter");
747
- await expect(page).toHaveURL(/\/about/);
140
+ // Mask dynamic content
141
+ await expect(page).toHaveScreenshot("dashboard.png", {
142
+ mask: [page.getByTestId("timestamp"), page.getByTestId("avatar")],
748
143
  });
749
144
  });
750
145
  ```
751
146
 
752
147
  ## Best Practices
753
148
 
754
- ### Do's
755
-
756
- - Use Page Object Model for maintainable tests
757
- - Prefer user-facing locators (getByRole, getByLabel)
758
- - Set up proper test isolation with fixtures
759
- - Use API authentication for faster test setup
760
- - Enable traces and screenshots for debugging
761
- - Run tests in parallel for faster execution
762
- - Implement visual regression for UI consistency
763
- - Use test.describe for logical grouping
764
- - Mock external APIs for reliable tests
765
- - Test across multiple browsers and devices
766
-
767
- ### Don'ts
768
-
769
- - Don't use fragile CSS selectors
770
- - Don't rely on timing with arbitrary waits
771
- - Don't share state between tests
772
- - Don't test third-party services directly
773
- - Don't skip flaky tests without investigating
774
- - Don't ignore accessibility testing
775
- - Don't hardcode test data
776
- - Don't run all browsers in CI by default
777
- - Don't ignore test failures in PRs
778
- - Don't forget to clean up test artifacts
149
+ | Do | Avoid |
150
+ |----|-------|
151
+ | Use Page Object Model for maintainability | Fragile CSS selectors |
152
+ | Prefer user-facing locators (getByRole, getByLabel) | Relying on arbitrary waits |
153
+ | Use API auth for faster test setup | Sharing state between tests |
154
+ | Enable traces and screenshots for debugging | Testing third-party services directly |
155
+ | Run tests in parallel for speed | Skipping flaky tests without fixing |
156
+ | Mock external APIs for reliability | Hardcoding test data |
779
157
 
780
158
  ## References
781
159
 
@@ -783,4 +161,3 @@ test.describe("Accessibility", () => {
783
161
  - [Playwright Best Practices](https://playwright.dev/docs/best-practices)
784
162
  - [Page Object Model](https://playwright.dev/docs/pom)
785
163
  - [Visual Comparisons](https://playwright.dev/docs/test-snapshots)
786
- - [Accessibility Testing](https://playwright.dev/docs/accessibility-testing)