omgkit 2.1.0 → 2.2.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 (56) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/SKILL_STANDARDS.md +743 -0
  3. package/plugin/skills/databases/mongodb/SKILL.md +797 -28
  4. package/plugin/skills/databases/postgresql/SKILL.md +494 -18
  5. package/plugin/skills/databases/prisma/SKILL.md +776 -30
  6. package/plugin/skills/databases/redis/SKILL.md +885 -25
  7. package/plugin/skills/devops/aws/SKILL.md +686 -28
  8. package/plugin/skills/devops/docker/SKILL.md +466 -18
  9. package/plugin/skills/devops/github-actions/SKILL.md +684 -29
  10. package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
  11. package/plugin/skills/frameworks/django/SKILL.md +920 -20
  12. package/plugin/skills/frameworks/express/SKILL.md +1361 -35
  13. package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
  14. package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
  15. package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
  16. package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
  17. package/plugin/skills/frameworks/rails/SKILL.md +594 -28
  18. package/plugin/skills/frameworks/react/SKILL.md +1006 -32
  19. package/plugin/skills/frameworks/spring/SKILL.md +528 -35
  20. package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
  21. package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
  23. package/plugin/skills/frontend/responsive/SKILL.md +847 -21
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
  26. package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
  27. package/plugin/skills/languages/javascript/SKILL.md +935 -31
  28. package/plugin/skills/languages/python/SKILL.md +489 -25
  29. package/plugin/skills/languages/typescript/SKILL.md +379 -30
  30. package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
  31. package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
  32. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
  33. package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
  34. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
  35. package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
  36. package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
  37. package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
  38. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
  39. package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
  40. package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
  41. package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
  42. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
  43. package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
  44. package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
  45. package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
  46. package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
  47. package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
  48. package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
  49. package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
  50. package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
  51. package/plugin/skills/security/better-auth/SKILL.md +1065 -28
  52. package/plugin/skills/security/oauth/SKILL.md +968 -31
  53. package/plugin/skills/security/owasp/SKILL.md +894 -33
  54. package/plugin/skills/testing/playwright/SKILL.md +764 -38
  55. package/plugin/skills/testing/pytest/SKILL.md +873 -36
  56. package/plugin/skills/testing/vitest/SKILL.md +980 -35
@@ -1,60 +1,786 @@
1
1
  ---
2
2
  name: playwright
3
- description: Playwright E2E testing. Use for browser automation, E2E tests.
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
4
12
  ---
5
13
 
6
- # Playwright Skill
14
+ # Playwright
7
15
 
8
- ## Basic Test
9
- ```typescript
10
- import { test, expect } from '@playwright/test';
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.
11
17
 
12
- test('homepage has title', async ({ page }) => {
13
- await page.goto('/');
14
- await expect(page).toHaveTitle(/My App/);
15
- });
18
+ ## Purpose
16
19
 
17
- test('login flow', async ({ page }) => {
18
- await page.goto('/login');
19
- await page.fill('[name="email"]', 'test@example.com');
20
- await page.fill('[name="password"]', 'password');
21
- await page.click('button[type="submit"]');
22
- await expect(page).toHaveURL('/dashboard');
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
33
+
34
+ ```typescript
35
+ // playwright.config.ts
36
+ import { defineConfig, devices } from "@playwright/test";
37
+
38
+ export default defineConfig({
39
+ testDir: "./tests/e2e",
40
+ fullyParallel: true,
41
+ forbidOnly: !!process.env.CI,
42
+ 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
+ ],
50
+ use: {
51
+ baseURL: process.env.BASE_URL || "http://localhost:3000",
52
+ trace: "on-first-retry",
53
+ screenshot: "only-on-failure",
54
+ video: "retain-on-failure",
55
+ actionTimeout: 10000,
56
+ navigationTimeout: 30000,
57
+ },
58
+ 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
+ },
83
+ ],
84
+ webServer: {
85
+ command: "npm run dev",
86
+ url: "http://localhost:3000",
87
+ reuseExistingServer: !process.env.CI,
88
+ timeout: 120000,
89
+ },
23
90
  });
24
91
  ```
25
92
 
26
- ## Page Object Model
93
+ ### 2. Page Object Model
94
+
27
95
  ```typescript
28
- class LoginPage {
29
- constructor(private page: Page) {}
96
+ // tests/pages/base.page.ts
97
+ import { Page, Locator, expect } from "@playwright/test";
30
98
 
31
- async login(email: string, password: string) {
32
- await this.page.fill('[name="email"]', email);
33
- await this.page.fill('[name="password"]', password);
34
- await this.page.click('button[type="submit"]');
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
+ }
108
+
109
+ async waitForPageLoad(): Promise<void> {
110
+ await this.page.waitForLoadState("networkidle");
111
+ }
112
+
113
+ async getTitle(): Promise<string> {
114
+ return this.page.title();
115
+ }
116
+
117
+ async screenshot(name: string): Promise<void> {
118
+ await this.page.screenshot({ path: `screenshots/${name}.png` });
119
+ }
120
+ }
121
+
122
+ // tests/pages/login.page.ts
123
+ import { Page, Locator, expect } from "@playwright/test";
124
+ import { BasePage } from "./base.page";
125
+
126
+ export class LoginPage extends BasePage {
127
+ readonly emailInput: Locator;
128
+ readonly passwordInput: Locator;
129
+ readonly submitButton: Locator;
130
+ readonly errorMessage: Locator;
131
+ readonly forgotPasswordLink: Locator;
132
+ readonly rememberMeCheckbox: Locator;
133
+
134
+ constructor(page: Page) {
135
+ super(page);
136
+ this.emailInput = page.getByLabel("Email");
137
+ this.passwordInput = page.getByLabel("Password");
138
+ 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
+ }
153
+
154
+ async loginWithRememberMe(email: string, password: string): Promise<void> {
155
+ await this.emailInput.fill(email);
156
+ await this.passwordInput.fill(password);
157
+ await this.rememberMeCheckbox.check();
158
+ await this.submitButton.click();
159
+ }
160
+
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();
169
+ }
170
+ }
171
+
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
+ }
199
+
200
+ async logout(): Promise<void> {
201
+ await this.userMenu.click();
202
+ await this.logoutButton.click();
203
+ }
204
+
205
+ async search(query: string): Promise<void> {
206
+ await this.searchInput.fill(query);
207
+ await this.searchInput.press("Enter");
208
+ }
209
+
210
+ async navigateTo(item: string): Promise<void> {
211
+ await this.navigationItems.filter({ hasText: item }).click();
35
212
  }
36
213
  }
37
214
  ```
38
215
 
39
- ## Config
216
+ ### 3. Test Fixtures
217
+
40
218
  ```typescript
41
- // playwright.config.ts
42
- export default defineConfig({
43
- testDir: './tests',
44
- use: {
45
- baseURL: 'http://localhost:3000',
46
- screenshot: 'only-on-failure',
219
+ // tests/fixtures/auth.fixture.ts
220
+ import { test as base, expect } from "@playwright/test";
221
+ 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
+
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",
47
242
  },
48
- projects: [
49
- { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
50
- { name: 'mobile', use: { ...devices['iPhone 12'] } },
51
- ],
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
256
+ const response = await page.request.post("/api/auth/login", {
257
+ data: {
258
+ email: testUser.email,
259
+ password: testUser.password,
260
+ },
261
+ });
262
+
263
+ const { token } = await response.json();
264
+
265
+ // Set auth cookie/storage
266
+ await page.context().addCookies([
267
+ {
268
+ name: "auth_token",
269
+ value: token,
270
+ domain: "localhost",
271
+ path: "/",
272
+ },
273
+ ]);
274
+
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
+ 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
+ });
52
473
  });
53
474
  ```
54
475
 
55
- ## Run
56
- ```bash
57
- npx playwright test
58
- npx playwright test --ui
59
- npx playwright codegen
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
+ });
60
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
+ });
646
+ });
647
+ ```
648
+
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
708
+
709
+ ```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([]);
723
+ });
724
+
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([]);
733
+ });
734
+
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/);
748
+ });
749
+ });
750
+ ```
751
+
752
+ ## Best Practices
753
+
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
779
+
780
+ ## References
781
+
782
+ - [Playwright Documentation](https://playwright.dev/docs/intro)
783
+ - [Playwright Best Practices](https://playwright.dev/docs/best-practices)
784
+ - [Page Object Model](https://playwright.dev/docs/pom)
785
+ - [Visual Comparisons](https://playwright.dev/docs/test-snapshots)
786
+ - [Accessibility Testing](https://playwright.dev/docs/accessibility-testing)