specweave 1.0.239 → 1.0.241

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 (161) hide show
  1. package/CLAUDE.md +31 -30
  2. package/README.md +1 -1
  3. package/bin/specweave.js +16 -0
  4. package/dist/plugins/specweave-ado/lib/ado-permission-gate.d.ts.map +1 -1
  5. package/dist/plugins/specweave-ado/lib/ado-permission-gate.js +17 -2
  6. package/dist/plugins/specweave-ado/lib/ado-permission-gate.js.map +1 -1
  7. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts +7 -0
  8. package/dist/plugins/specweave-github/lib/github-feature-sync.d.ts.map +1 -1
  9. package/dist/plugins/specweave-github/lib/github-feature-sync.js +53 -0
  10. package/dist/plugins/specweave-github/lib/github-feature-sync.js.map +1 -1
  11. package/dist/plugins/specweave-jira/lib/jira-permission-gate.d.ts.map +1 -1
  12. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js +17 -2
  13. package/dist/plugins/specweave-jira/lib/jira-permission-gate.js.map +1 -1
  14. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts +1 -0
  15. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.d.ts.map +1 -1
  16. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js +7 -3
  17. package/dist/plugins/specweave-testing/lib/playwright-cli-detector.js.map +1 -1
  18. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.d.ts.map +1 -1
  19. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js +27 -19
  20. package/dist/plugins/specweave-testing/lib/playwright-cli-runner.js.map +1 -1
  21. package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts +8 -0
  22. package/dist/plugins/specweave-testing/lib/playwright-routing.d.ts.map +1 -1
  23. package/dist/plugins/specweave-testing/lib/playwright-routing.js +10 -7
  24. package/dist/plugins/specweave-testing/lib/playwright-routing.js.map +1 -1
  25. package/dist/src/adapters/agents-md-generator.js +1 -1
  26. package/dist/src/adapters/agents-md-generator.js.map +1 -1
  27. package/dist/src/adapters/claude/README.md +1 -1
  28. package/dist/src/adapters/claude-md-generator.js +1 -1
  29. package/dist/src/adapters/claude-md-generator.js.map +1 -1
  30. package/dist/src/cli/commands/init.d.ts.map +1 -1
  31. package/dist/src/cli/commands/init.js +10 -1
  32. package/dist/src/cli/commands/init.js.map +1 -1
  33. package/dist/src/cli/commands/refresh-marketplace.d.ts.map +1 -1
  34. package/dist/src/cli/commands/refresh-marketplace.js +7 -67
  35. package/dist/src/cli/commands/refresh-marketplace.js.map +1 -1
  36. package/dist/src/cli/commands/team.d.ts +20 -0
  37. package/dist/src/cli/commands/team.d.ts.map +1 -0
  38. package/dist/src/cli/commands/team.js +101 -0
  39. package/dist/src/cli/commands/team.js.map +1 -0
  40. package/dist/src/cli/helpers/init/claude-settings-env.d.ts +16 -0
  41. package/dist/src/cli/helpers/init/claude-settings-env.d.ts.map +1 -0
  42. package/dist/src/cli/helpers/init/claude-settings-env.js +44 -0
  43. package/dist/src/cli/helpers/init/claude-settings-env.js.map +1 -0
  44. package/dist/src/cli/helpers/init/plugin-installer.d.ts.map +1 -1
  45. package/dist/src/cli/helpers/init/plugin-installer.js +9 -13
  46. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -1
  47. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  48. package/dist/src/cli/helpers/issue-tracker/index.js +12 -6
  49. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  50. package/dist/src/cli/helpers/issue-tracker/types.d.ts +2 -0
  51. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  52. package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
  53. package/dist/src/core/increment/discipline-checker.js +1 -1
  54. package/dist/src/core/increment/discipline-checker.js.map +1 -1
  55. package/dist/src/core/increment/status-commands.d.ts.map +1 -1
  56. package/dist/src/core/increment/status-commands.js +7 -0
  57. package/dist/src/core/increment/status-commands.js.map +1 -1
  58. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts +2 -2
  59. package/dist/src/core/lazy-loading/llm-plugin-detector.d.ts.map +1 -1
  60. package/dist/src/core/lazy-loading/llm-plugin-detector.js +63 -25
  61. package/dist/src/core/lazy-loading/llm-plugin-detector.js.map +1 -1
  62. package/dist/src/core/reflection/reflect-handler.js +2 -2
  63. package/dist/src/core/reflection/reflect-handler.js.map +1 -1
  64. package/dist/src/core/session/handoff-context.js +2 -2
  65. package/dist/src/core/session/handoff-context.js.map +1 -1
  66. package/dist/src/sync/ado-reconciler.d.ts.map +1 -1
  67. package/dist/src/sync/ado-reconciler.js +21 -2
  68. package/dist/src/sync/ado-reconciler.js.map +1 -1
  69. package/dist/src/sync/engine.d.ts.map +1 -1
  70. package/dist/src/sync/engine.js +2 -0
  71. package/dist/src/sync/engine.js.map +1 -1
  72. package/dist/src/sync/github-reconciler.d.ts.map +1 -1
  73. package/dist/src/sync/github-reconciler.js +52 -26
  74. package/dist/src/sync/github-reconciler.js.map +1 -1
  75. package/dist/src/sync/jira-reconciler.d.ts.map +1 -1
  76. package/dist/src/sync/jira-reconciler.js +16 -3
  77. package/dist/src/sync/jira-reconciler.js.map +1 -1
  78. package/dist/src/sync/providers/ado.d.ts.map +1 -1
  79. package/dist/src/sync/providers/ado.js +4 -2
  80. package/dist/src/sync/providers/ado.js.map +1 -1
  81. package/dist/src/sync/providers/github.d.ts.map +1 -1
  82. package/dist/src/sync/providers/github.js +11 -0
  83. package/dist/src/sync/providers/github.js.map +1 -1
  84. package/dist/src/sync/providers/jira.d.ts.map +1 -1
  85. package/dist/src/sync/providers/jira.js +14 -2
  86. package/dist/src/sync/providers/jira.js.map +1 -1
  87. package/dist/src/sync/sync-coordinator.d.ts.map +1 -1
  88. package/dist/src/sync/sync-coordinator.js +31 -6
  89. package/dist/src/sync/sync-coordinator.js.map +1 -1
  90. package/dist/src/utils/auto-install.js +4 -4
  91. package/dist/src/utils/auto-install.js.map +1 -1
  92. package/package.json +2 -2
  93. package/plugins/FINAL-AUDIT-RECOMMENDATIONS.md +3 -3
  94. package/plugins/SKILLS-VS-AGENTS.md +1 -1
  95. package/plugins/specweave/PLUGIN.md +0 -2
  96. package/plugins/specweave/commands/export-skills.md +1 -1
  97. package/plugins/specweave/commands/role-orchestrator.md +1 -1
  98. package/plugins/specweave/hooks/log-decision.sh +6 -0
  99. package/plugins/specweave/hooks/stop-auto-v5.sh +17 -1
  100. package/plugins/specweave/hooks/stop-reflect.sh +16 -2
  101. package/plugins/specweave/hooks/stop-sync.sh +17 -9
  102. package/plugins/specweave/hooks/user-prompt-submit.sh +119 -35
  103. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +52 -26
  104. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
  105. package/plugins/specweave/scripts/read-grill-context.sh +149 -0
  106. package/plugins/specweave/skills/code-review/SKILL.md +608 -0
  107. package/plugins/specweave/skills/done/SKILL.md +1 -1
  108. package/plugins/specweave/skills/grill/SKILL.md +91 -0
  109. package/plugins/specweave/skills/performance/SKILL.md +6 -0
  110. package/plugins/specweave/skills/security/SKILL.md +7 -0
  111. package/plugins/specweave/skills/security-patterns/SKILL.md +6 -0
  112. package/plugins/specweave/skills/tdd-orchestrator/SKILL.md +1 -1
  113. package/plugins/specweave/skills/team-build/SKILL.md +1 -1
  114. package/plugins/specweave/skills/team-orchestrate/SKILL.md +1 -1
  115. package/plugins/specweave/skills/tech-lead/SKILL.md +7 -0
  116. package/plugins/specweave-ado/lib/ado-permission-gate.js +18 -2
  117. package/plugins/specweave-ado/lib/ado-permission-gate.ts +19 -2
  118. package/plugins/specweave-frontend/skills/frontend/SKILL.md +138 -2
  119. package/plugins/specweave-frontend/skills/i18n-expert/SKILL.md +989 -0
  120. package/plugins/specweave-github/hooks/github-auto-create-handler.sh +23 -1
  121. package/plugins/specweave-github/lib/github-feature-sync.js +41 -0
  122. package/plugins/specweave-github/lib/github-feature-sync.ts +62 -0
  123. package/plugins/specweave-infrastructure/PLUGIN.md +2 -1
  124. package/plugins/specweave-infrastructure/skills/gcp-deep-dive/SKILL.md +1172 -0
  125. package/plugins/specweave-infrastructure/skills/observability/SKILL.md +6 -0
  126. package/plugins/specweave-infrastructure/skills/opentelemetry/SKILL.md +6 -0
  127. package/plugins/specweave-jira/lib/jira-permission-gate.js +18 -2
  128. package/plugins/specweave-jira/lib/jira-permission-gate.ts +19 -2
  129. package/plugins/specweave-mobile/PLUGIN.md +1 -2
  130. package/plugins/specweave-mobile/README.md +13 -12
  131. package/plugins/specweave-mobile/skills/capacitor-ionic/SKILL.md +4 -18
  132. package/plugins/specweave-mobile/skills/deep-linking-push/SKILL.md +4 -22
  133. package/plugins/specweave-mobile/skills/expo/SKILL.md +4 -24
  134. package/plugins/specweave-mobile/skills/mobile-testing/SKILL.md +4 -22
  135. package/plugins/specweave-mobile/skills/react-native-expert/SKILL.md +404 -47
  136. package/plugins/specweave-testing/PLUGIN.md +3 -11
  137. package/plugins/specweave-testing/lib/playwright-cli-detector.js +8 -3
  138. package/plugins/specweave-testing/lib/playwright-cli-detector.ts +8 -3
  139. package/plugins/specweave-testing/lib/playwright-cli-runner.js +25 -20
  140. package/plugins/specweave-testing/lib/playwright-cli-runner.ts +24 -19
  141. package/plugins/specweave-testing/lib/playwright-routing.js +1 -6
  142. package/plugins/specweave-testing/lib/playwright-routing.ts +11 -8
  143. package/plugins/specweave-testing/skills/accessibility-testing/SKILL.md +998 -0
  144. package/plugins/specweave-testing/skills/e2e-testing/SKILL.md +29 -28
  145. package/plugins/specweave-testing/skills/mutation-testing/SKILL.md +769 -0
  146. package/plugins/specweave-testing/skills/performance-testing/SKILL.md +961 -0
  147. package/plugins/specweave-testing/skills/qa-engineer/SKILL.md +2 -0
  148. package/plugins/specweave/.specweave/logs/decisions.jsonl +0 -12
  149. package/plugins/specweave/.specweave/logs/reflect/reflect.log +0 -8
  150. package/plugins/specweave/.specweave/logs/stop-auto.log +0 -6
  151. package/plugins/specweave/.specweave/logs/stop-sync.log +0 -10
  152. package/plugins/specweave/.specweave/state/dashboard.json +0 -43
  153. package/plugins/specweave/skills/infrastructure/SKILL.md +0 -86
  154. package/plugins/specweave/skills/qa-lead/SKILL.md +0 -77
  155. package/plugins/specweave-mobile/skills/mobile-architect/SKILL.md +0 -30
  156. package/plugins/specweave-testing/commands/e2e-setup.md +0 -1103
  157. package/plugins/specweave-testing/commands/test-coverage.md +0 -983
  158. package/plugins/specweave-testing/commands/test-generate.md +0 -1160
  159. package/plugins/specweave-testing/commands/test-init.md +0 -413
  160. package/plugins/specweave-testing/commands/ui-automate.md +0 -182
  161. package/plugins/specweave-testing/commands/ui-inspect.md +0 -82
@@ -1,1103 +0,0 @@
1
- ---
2
- description: Set up comprehensive Playwright E2E testing with best practices, page objects, and CI/CD integration.
3
- ---
4
-
5
- # /sw-testing:e2e-setup
6
-
7
- Set up comprehensive Playwright E2E testing with best practices, page objects, and CI/CD integration.
8
-
9
- You are an expert E2E testing engineer who implements production-ready Playwright test suites.
10
-
11
- ## CLI vs MCP Mode
12
-
13
- SpecWeave supports two modes for Playwright browser automation:
14
-
15
- - **@playwright/cli** (recommended for test execution): Token-efficient, file-based output, CI-friendly
16
- - **@playwright/mcp** (for interactive exploration): Rich inline snapshots, good for debugging
17
-
18
- Install CLI mode: `npm install -g @playwright/cli@latest`
19
-
20
- Configure preference in `.specweave/config.json`:
21
- ```json
22
- {
23
- "testing": {
24
- "playwright": { "preferCli": true }
25
- }
26
- }
27
- ```
28
-
29
- ## Your Task
30
-
31
- Set up a complete Playwright E2E testing framework with page objects, fixtures, and testing patterns.
32
-
33
- ### 1. Playwright Stack Features
34
-
35
- **Cross-Browser Testing**:
36
- - Chromium, Firefox, WebKit support
37
- - Mobile viewport emulation
38
- - Device-specific testing (iPhone, Pixel, etc.)
39
- - Browser context isolation
40
- - Persistent state management
41
-
42
- **Reliability Features**:
43
- - Auto-wait for elements
44
- - Network idle detection
45
- - Retry mechanisms
46
- - Screenshot/video on failure
47
- - Trace recording for debugging
48
-
49
- **Performance**:
50
- - Parallel test execution
51
- - Sharding for CI/CD
52
- - Browser reuse
53
- - Worker threads
54
- - Test isolation
55
-
56
- ### 2. Advanced Playwright Configuration
57
-
58
- **playwright.config.ts** (Production-Grade):
59
- ```typescript
60
- import { defineConfig, devices } from '@playwright/test';
61
- import dotenv from 'dotenv';
62
-
63
- dotenv.config();
64
-
65
- export default defineConfig({
66
- testDir: './tests/e2e',
67
- fullyParallel: true,
68
- forbidOnly: !!process.env.CI,
69
- retries: process.env.CI ? 2 : 0,
70
- workers: process.env.CI ? 1 : undefined,
71
-
72
- // Reporter configuration
73
- reporter: [
74
- ['html', { outputFolder: 'playwright-report' }],
75
- ['json', { outputFile: 'test-results/results.json' }],
76
- ['junit', { outputFile: 'test-results/junit.xml' }],
77
- ['github'], // GitHub Actions annotations
78
- ],
79
-
80
- use: {
81
- // Base URL for navigation
82
- baseURL: process.env.BASE_URL || 'http://localhost:3000',
83
-
84
- // Tracing and debugging
85
- trace: 'on-first-retry',
86
- screenshot: 'only-on-failure',
87
- video: 'retain-on-failure',
88
-
89
- // Timeouts
90
- actionTimeout: 10000,
91
- navigationTimeout: 30000,
92
-
93
- // Browser options
94
- viewport: { width: 1280, height: 720 },
95
- ignoreHTTPSErrors: true,
96
-
97
- // Collect HTTP Archive (HAR) files
98
- recordHar: process.env.CI ? undefined : { path: 'test-results/har' },
99
- },
100
-
101
- // Test timeout
102
- timeout: 30000,
103
-
104
- // Global setup/teardown
105
- globalSetup: require.resolve('./tests/e2e/global-setup.ts'),
106
- globalTeardown: require.resolve('./tests/e2e/global-teardown.ts'),
107
-
108
- projects: [
109
- // Setup project for authentication
110
- {
111
- name: 'setup',
112
- testMatch: /.*\.setup\.ts/,
113
- },
114
-
115
- // Desktop browsers
116
- {
117
- name: 'chromium',
118
- use: {
119
- ...devices['Desktop Chrome'],
120
- storageState: 'playwright/.auth/user.json',
121
- },
122
- dependencies: ['setup'],
123
- },
124
- {
125
- name: 'firefox',
126
- use: {
127
- ...devices['Desktop Firefox'],
128
- storageState: 'playwright/.auth/user.json',
129
- },
130
- dependencies: ['setup'],
131
- },
132
- {
133
- name: 'webkit',
134
- use: {
135
- ...devices['Desktop Safari'],
136
- storageState: 'playwright/.auth/user.json',
137
- },
138
- dependencies: ['setup'],
139
- },
140
-
141
- // Mobile browsers
142
- {
143
- name: 'mobile-chrome',
144
- use: {
145
- ...devices['Pixel 5'],
146
- storageState: 'playwright/.auth/user.json',
147
- },
148
- dependencies: ['setup'],
149
- },
150
- {
151
- name: 'mobile-safari',
152
- use: {
153
- ...devices['iPhone 12'],
154
- storageState: 'playwright/.auth/user.json',
155
- },
156
- dependencies: ['setup'],
157
- },
158
-
159
- // Branded browsers
160
- {
161
- name: 'edge',
162
- use: {
163
- ...devices['Desktop Edge'],
164
- channel: 'msedge',
165
- storageState: 'playwright/.auth/user.json',
166
- },
167
- dependencies: ['setup'],
168
- },
169
- {
170
- name: 'chrome',
171
- use: {
172
- ...devices['Desktop Chrome'],
173
- channel: 'chrome',
174
- storageState: 'playwright/.auth/user.json',
175
- },
176
- dependencies: ['setup'],
177
- },
178
- ],
179
-
180
- // Web server configuration
181
- webServer: {
182
- command: 'npm run dev',
183
- url: 'http://localhost:3000',
184
- reuseExistingServer: !process.env.CI,
185
- timeout: 120000,
186
- stdout: 'pipe',
187
- stderr: 'pipe',
188
- },
189
- });
190
- ```
191
-
192
- ### 3. Page Object Model (POM)
193
-
194
- **tests/e2e/pages/BasePage.ts**:
195
- ```typescript
196
- import { Page, Locator } from '@playwright/test';
197
-
198
- export abstract class BasePage {
199
- readonly page: Page;
200
-
201
- constructor(page: Page) {
202
- this.page = page;
203
- }
204
-
205
- // Common navigation
206
- async goto(path: string) {
207
- await this.page.goto(path);
208
- }
209
-
210
- // Wait helpers
211
- async waitForNetworkIdle() {
212
- await this.page.waitForLoadState('networkidle');
213
- }
214
-
215
- async waitForDomContentLoaded() {
216
- await this.page.waitForLoadState('domcontentloaded');
217
- }
218
-
219
- // Screenshot helpers
220
- async takeScreenshot(name: string) {
221
- await this.page.screenshot({
222
- path: `test-results/screenshots/${name}.png`,
223
- fullPage: true,
224
- });
225
- }
226
-
227
- // Cookie helpers
228
- async getCookies() {
229
- return await this.page.context().cookies();
230
- }
231
-
232
- async setCookies(cookies: any[]) {
233
- await this.page.context().addCookies(cookies);
234
- }
235
-
236
- // Local storage helpers
237
- async getLocalStorage(key: string): Promise<string | null> {
238
- return await this.page.evaluate((key) => {
239
- return localStorage.getItem(key);
240
- }, key);
241
- }
242
-
243
- async setLocalStorage(key: string, value: string) {
244
- await this.page.evaluate(({ key, value }) => {
245
- localStorage.setItem(key, value);
246
- }, { key, value });
247
- }
248
-
249
- // Common assertions
250
- async assertUrl(expectedUrl: string) {
251
- await this.page.waitForURL(expectedUrl);
252
- }
253
-
254
- async assertTitle(expectedTitle: string) {
255
- await this.page.waitForFunction(
256
- (title) => document.title === title,
257
- expectedTitle
258
- );
259
- }
260
- }
261
- ```
262
-
263
- **tests/e2e/pages/LoginPage.ts**:
264
- ```typescript
265
- import { Page, Locator, expect } from '@playwright/test';
266
- import { BasePage } from './BasePage';
267
-
268
- export class LoginPage extends BasePage {
269
- // Locators
270
- readonly emailInput: Locator;
271
- readonly passwordInput: Locator;
272
- readonly loginButton: Locator;
273
- readonly errorMessage: Locator;
274
- readonly rememberMeCheckbox: Locator;
275
- readonly forgotPasswordLink: Locator;
276
-
277
- constructor(page: Page) {
278
- super(page);
279
- this.emailInput = page.locator('input[name="email"]');
280
- this.passwordInput = page.locator('input[name="password"]');
281
- this.loginButton = page.locator('button[type="submit"]');
282
- this.errorMessage = page.locator('[role="alert"]');
283
- this.rememberMeCheckbox = page.locator('input[name="rememberMe"]');
284
- this.forgotPasswordLink = page.locator('a[href="/forgot-password"]');
285
- }
286
-
287
- // Actions
288
- async navigate() {
289
- await this.goto('/login');
290
- }
291
-
292
- async login(email: string, password: string, rememberMe = false) {
293
- await this.emailInput.fill(email);
294
- await this.passwordInput.fill(password);
295
-
296
- if (rememberMe) {
297
- await this.rememberMeCheckbox.check();
298
- }
299
-
300
- await this.loginButton.click();
301
- }
302
-
303
- async loginAsAdmin() {
304
- await this.login(
305
- process.env.ADMIN_EMAIL || 'admin@example.com',
306
- process.env.ADMIN_PASSWORD || 'admin123'
307
- );
308
- }
309
-
310
- async loginAsUser() {
311
- await this.login(
312
- process.env.USER_EMAIL || 'user@example.com',
313
- process.env.USER_PASSWORD || 'user123'
314
- );
315
- }
316
-
317
- // Assertions
318
- async assertErrorMessage(expectedMessage: string) {
319
- await expect(this.errorMessage).toContainText(expectedMessage);
320
- }
321
-
322
- async assertLoginSuccessful() {
323
- await this.page.waitForURL(/\/(dashboard|home)/);
324
- }
325
-
326
- async assertOnLoginPage() {
327
- await expect(this.page).toHaveURL(/\/login/);
328
- }
329
- }
330
- ```
331
-
332
- **tests/e2e/pages/DashboardPage.ts**:
333
- ```typescript
334
- import { Page, Locator, expect } from '@playwright/test';
335
- import { BasePage } from './BasePage';
336
-
337
- export class DashboardPage extends BasePage {
338
- readonly welcomeMessage: Locator;
339
- readonly userMenu: Locator;
340
- readonly logoutButton: Locator;
341
- readonly notificationBadge: Locator;
342
- readonly searchInput: Locator;
343
-
344
- constructor(page: Page) {
345
- super(page);
346
- this.welcomeMessage = page.locator('h1');
347
- this.userMenu = page.locator('[data-testid="user-menu"]');
348
- this.logoutButton = page.locator('button:has-text("Logout")');
349
- this.notificationBadge = page.locator('[data-testid="notification-badge"]');
350
- this.searchInput = page.locator('input[placeholder="Search..."]');
351
- }
352
-
353
- async navigate() {
354
- await this.goto('/dashboard');
355
- }
356
-
357
- async logout() {
358
- await this.userMenu.click();
359
- await this.logoutButton.click();
360
- }
361
-
362
- async search(query: string) {
363
- await this.searchInput.fill(query);
364
- await this.searchInput.press('Enter');
365
- }
366
-
367
- async getNotificationCount(): Promise<number> {
368
- const text = await this.notificationBadge.textContent();
369
- return parseInt(text || '0', 10);
370
- }
371
-
372
- async assertWelcomeMessage(username: string) {
373
- await expect(this.welcomeMessage).toContainText(`Welcome, ${username}`);
374
- }
375
-
376
- async assertOnDashboard() {
377
- await expect(this.page).toHaveURL(/\/dashboard/);
378
- }
379
- }
380
- ```
381
-
382
- ### 4. Custom Fixtures
383
-
384
- **tests/e2e/fixtures/auth.fixture.ts**:
385
- ```typescript
386
- import { test as base } from '@playwright/test';
387
- import { LoginPage } from '../pages/LoginPage';
388
- import { DashboardPage } from '../pages/DashboardPage';
389
-
390
- type AuthFixtures = {
391
- authenticatedPage: Page;
392
- loginPage: LoginPage;
393
- dashboardPage: DashboardPage;
394
- };
395
-
396
- export const test = base.extend<AuthFixtures>({
397
- authenticatedPage: async ({ page }, use) => {
398
- const loginPage = new LoginPage(page);
399
- await loginPage.navigate();
400
- await loginPage.loginAsUser();
401
- await use(page);
402
- },
403
-
404
- loginPage: async ({ page }, use) => {
405
- const loginPage = new LoginPage(page);
406
- await use(loginPage);
407
- },
408
-
409
- dashboardPage: async ({ page }, use) => {
410
- const dashboardPage = new DashboardPage(page);
411
- await use(dashboardPage);
412
- },
413
- });
414
-
415
- export { expect } from '@playwright/test';
416
- ```
417
-
418
- **tests/e2e/fixtures/api.fixture.ts**:
419
- ```typescript
420
- import { test as base, APIRequestContext } from '@playwright/test';
421
-
422
- type ApiFixtures = {
423
- apiContext: APIRequestContext;
424
- };
425
-
426
- export const test = base.extend<ApiFixtures>({
427
- apiContext: async ({ playwright }, use) => {
428
- const context = await playwright.request.newContext({
429
- baseURL: process.env.API_BASE_URL || 'http://localhost:3000/api',
430
- extraHTTPHeaders: {
431
- 'Accept': 'application/json',
432
- 'Content-Type': 'application/json',
433
- },
434
- });
435
- await use(context);
436
- await context.dispose();
437
- },
438
- });
439
-
440
- export { expect } from '@playwright/test';
441
- ```
442
-
443
- ### 5. Global Setup and Teardown
444
-
445
- **tests/e2e/global-setup.ts**:
446
- ```typescript
447
- import { chromium, FullConfig } from '@playwright/test';
448
- import path from 'path';
449
- import fs from 'fs';
450
-
451
- async function globalSetup(config: FullConfig) {
452
- const { baseURL, storageState } = config.projects[0].use;
453
-
454
- // Create auth directory
455
- const authDir = path.dirname(storageState as string);
456
- if (!fs.existsSync(authDir)) {
457
- fs.mkdirSync(authDir, { recursive: true });
458
- }
459
-
460
- // Launch browser and authenticate
461
- const browser = await chromium.launch();
462
- const context = await browser.newContext();
463
- const page = await context.newPage();
464
-
465
- try {
466
- // Navigate to login
467
- await page.goto(`${baseURL}/login`);
468
-
469
- // Perform login
470
- await page.fill('input[name="email"]', process.env.USER_EMAIL || 'test@example.com');
471
- await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password123');
472
- await page.click('button[type="submit"]');
473
-
474
- // Wait for successful login
475
- await page.waitForURL(/\/(dashboard|home)/);
476
-
477
- // Save authentication state
478
- await context.storageState({ path: storageState as string });
479
-
480
- console.log('✓ Global setup: Authentication completed');
481
- } catch (error) {
482
- console.error('✗ Global setup: Authentication failed', error);
483
- throw error;
484
- } finally {
485
- await browser.close();
486
- }
487
- }
488
-
489
- export default globalSetup;
490
- ```
491
-
492
- **tests/e2e/global-teardown.ts**:
493
- ```typescript
494
- import { FullConfig } from '@playwright/test';
495
- import fs from 'fs';
496
- import path from 'path';
497
-
498
- async function globalTeardown(config: FullConfig) {
499
- try {
500
- // Clean up auth state
501
- const authDir = 'playwright/.auth';
502
- if (fs.existsSync(authDir)) {
503
- fs.rmSync(authDir, { recursive: true });
504
- console.log('✓ Global teardown: Cleaned auth state');
505
- }
506
-
507
- // Clean up test artifacts if not in CI
508
- if (!process.env.CI) {
509
- const artifactDirs = ['test-results', 'playwright-report'];
510
- artifactDirs.forEach(dir => {
511
- if (fs.existsSync(dir)) {
512
- const files = fs.readdirSync(dir);
513
- console.log(`✓ Global teardown: ${dir} contains ${files.length} files`);
514
- }
515
- });
516
- }
517
- } catch (error) {
518
- console.error('✗ Global teardown: Cleanup failed', error);
519
- }
520
- }
521
-
522
- export default globalTeardown;
523
- ```
524
-
525
- ### 6. Authentication Setup Tests
526
-
527
- **tests/e2e/auth.setup.ts**:
528
- ```typescript
529
- import { test as setup, expect } from '@playwright/test';
530
- import path from 'path';
531
-
532
- const authFile = path.join(__dirname, '../../playwright/.auth/user.json');
533
-
534
- setup('authenticate', async ({ page }) => {
535
- // Navigate to login page
536
- await page.goto('/login');
537
-
538
- // Perform login
539
- await page.fill('input[name="email"]', process.env.USER_EMAIL || 'test@example.com');
540
- await page.fill('input[name="password"]', process.env.USER_PASSWORD || 'password123');
541
- await page.click('button[type="submit"]');
542
-
543
- // Wait for successful navigation
544
- await page.waitForURL(/\/(dashboard|home)/);
545
-
546
- // Verify authentication succeeded
547
- await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
548
-
549
- // Save signed-in state
550
- await page.context().storageState({ path: authFile });
551
- });
552
-
553
- setup('admin authenticate', async ({ page }) => {
554
- await page.goto('/login');
555
-
556
- await page.fill('input[name="email"]', process.env.ADMIN_EMAIL || 'admin@example.com');
557
- await page.fill('input[name="password"]', process.env.ADMIN_PASSWORD || 'admin123');
558
- await page.click('button[type="submit"]');
559
-
560
- await page.waitForURL(/\/(dashboard|admin)/);
561
- await expect(page.locator('[data-testid="admin-menu"]')).toBeVisible();
562
-
563
- await page.context().storageState({
564
- path: path.join(__dirname, '../../playwright/.auth/admin.json')
565
- });
566
- });
567
- ```
568
-
569
- ### 7. Test Utilities
570
-
571
- **tests/e2e/utils/test-helpers.ts**:
572
- ```typescript
573
- import { Page, expect } from '@playwright/test';
574
-
575
- export class TestHelpers {
576
- static async waitForApiResponse(page: Page, urlPattern: string | RegExp, timeout = 5000) {
577
- return await page.waitForResponse(
578
- response => {
579
- const url = response.url();
580
- const matches = typeof urlPattern === 'string'
581
- ? url.includes(urlPattern)
582
- : urlPattern.test(url);
583
- return matches && response.status() === 200;
584
- },
585
- { timeout }
586
- );
587
- }
588
-
589
- static async interceptApi(page: Page, urlPattern: string | RegExp, mockResponse: any) {
590
- await page.route(urlPattern, route => {
591
- route.fulfill({
592
- status: 200,
593
- contentType: 'application/json',
594
- body: JSON.stringify(mockResponse),
595
- });
596
- });
597
- }
598
-
599
- static async blockRequests(page: Page, resourceTypes: string[]) {
600
- await page.route('**/*', route => {
601
- if (resourceTypes.includes(route.request().resourceType())) {
602
- route.abort();
603
- } else {
604
- route.continue();
605
- }
606
- });
607
- }
608
-
609
- static async mockGeolocation(page: Page, latitude: number, longitude: number) {
610
- await page.context().setGeolocation({ latitude, longitude });
611
- await page.context().grantPermissions(['geolocation']);
612
- }
613
-
614
- static async fillForm(page: Page, formData: Record<string, string>) {
615
- for (const [name, value] of Object.entries(formData)) {
616
- await page.fill(`[name="${name}"]`, value);
617
- }
618
- }
619
-
620
- static async selectDropdown(page: Page, selector: string, value: string) {
621
- await page.selectOption(selector, value);
622
- }
623
-
624
- static async uploadFile(page: Page, selector: string, filePath: string) {
625
- await page.setInputFiles(selector, filePath);
626
- }
627
-
628
- static async scrollToElement(page: Page, selector: string) {
629
- await page.locator(selector).scrollIntoViewIfNeeded();
630
- }
631
-
632
- static async assertAccessibility(page: Page) {
633
- // Basic accessibility checks
634
- const violations = await page.evaluate(() => {
635
- const issues: string[] = [];
636
-
637
- // Check for alt text on images
638
- document.querySelectorAll('img').forEach(img => {
639
- if (!img.alt) {
640
- issues.push(`Image missing alt text: ${img.src}`);
641
- }
642
- });
643
-
644
- // Check for form labels
645
- document.querySelectorAll('input, textarea, select').forEach(input => {
646
- const id = input.getAttribute('id');
647
- if (id && !document.querySelector(`label[for="${id}"]`)) {
648
- issues.push(`Form element missing label: ${id}`);
649
- }
650
- });
651
-
652
- return issues;
653
- });
654
-
655
- expect(violations).toHaveLength(0);
656
- }
657
- }
658
- ```
659
-
660
- **tests/e2e/utils/mock-data.ts**:
661
- ```typescript
662
- export const mockUsers = [
663
- {
664
- id: '1',
665
- email: 'john@example.com',
666
- name: 'John Doe',
667
- role: 'user',
668
- },
669
- {
670
- id: '2',
671
- email: 'admin@example.com',
672
- name: 'Admin User',
673
- role: 'admin',
674
- },
675
- ];
676
-
677
- export const mockProducts = [
678
- {
679
- id: '1',
680
- name: 'Product A',
681
- price: 29.99,
682
- inStock: true,
683
- },
684
- {
685
- id: '2',
686
- name: 'Product B',
687
- price: 49.99,
688
- inStock: false,
689
- },
690
- ];
691
-
692
- export function createMockApiResponse(data: any, delay = 0) {
693
- return {
694
- body: JSON.stringify({ data, success: true }),
695
- status: 200,
696
- headers: { 'Content-Type': 'application/json' },
697
- delay,
698
- };
699
- }
700
- ```
701
-
702
- ### 8. Example E2E Tests
703
-
704
- **tests/e2e/auth/login.spec.ts**:
705
- ```typescript
706
- import { test, expect } from '@playwright/test';
707
- import { LoginPage } from '../pages/LoginPage';
708
- import { DashboardPage } from '../pages/DashboardPage';
709
-
710
- test.describe('Login Flow', () => {
711
- let loginPage: LoginPage;
712
- let dashboardPage: DashboardPage;
713
-
714
- test.beforeEach(async ({ page }) => {
715
- loginPage = new LoginPage(page);
716
- dashboardPage = new DashboardPage(page);
717
- await loginPage.navigate();
718
- });
719
-
720
- test('should login with valid credentials', async ({ page }) => {
721
- await loginPage.login('test@example.com', 'password123');
722
- await dashboardPage.assertOnDashboard();
723
- await dashboardPage.assertWelcomeMessage('Test User');
724
- });
725
-
726
- test('should show error for invalid credentials', async () => {
727
- await loginPage.login('wrong@example.com', 'wrongpassword');
728
- await loginPage.assertErrorMessage('Invalid credentials');
729
- await loginPage.assertOnLoginPage();
730
- });
731
-
732
- test('should validate empty fields', async () => {
733
- await loginPage.loginButton.click();
734
-
735
- await expect(loginPage.emailInput).toHaveAttribute('aria-invalid', 'true');
736
- await expect(loginPage.passwordInput).toHaveAttribute('aria-invalid', 'true');
737
- });
738
-
739
- test('should remember user when checkbox checked', async ({ page }) => {
740
- await loginPage.login('test@example.com', 'password123', true);
741
- await dashboardPage.assertOnDashboard();
742
-
743
- const cookies = await page.context().cookies();
744
- const rememberMeCookie = cookies.find(c => c.name === 'rememberMe');
745
- expect(rememberMeCookie).toBeDefined();
746
- });
747
-
748
- test('should navigate to forgot password', async ({ page }) => {
749
- await loginPage.forgotPasswordLink.click();
750
- await expect(page).toHaveURL('/forgot-password');
751
- });
752
- });
753
- ```
754
-
755
- **tests/e2e/api/users.spec.ts**:
756
- ```typescript
757
- import { test, expect } from '../fixtures/api.fixture';
758
-
759
- test.describe('User API', () => {
760
- test('should fetch users list', async ({ apiContext }) => {
761
- const response = await apiContext.get('/users');
762
- expect(response.ok()).toBeTruthy();
763
-
764
- const data = await response.json();
765
- expect(data).toHaveProperty('users');
766
- expect(Array.isArray(data.users)).toBeTruthy();
767
- });
768
-
769
- test('should create new user', async ({ apiContext }) => {
770
- const newUser = {
771
- email: 'newuser@example.com',
772
- name: 'New User',
773
- role: 'user',
774
- };
775
-
776
- const response = await apiContext.post('/users', { data: newUser });
777
- expect(response.ok()).toBeTruthy();
778
-
779
- const data = await response.json();
780
- expect(data.user).toMatchObject(newUser);
781
- expect(data.user.id).toBeDefined();
782
- });
783
-
784
- test('should update user', async ({ apiContext }) => {
785
- const updates = { name: 'Updated Name' };
786
-
787
- const response = await apiContext.patch('/users/1', { data: updates });
788
- expect(response.ok()).toBeTruthy();
789
-
790
- const data = await response.json();
791
- expect(data.user.name).toBe(updates.name);
792
- });
793
-
794
- test('should delete user', async ({ apiContext }) => {
795
- const response = await apiContext.delete('/users/1');
796
- expect(response.ok()).toBeTruthy();
797
-
798
- // Verify deletion
799
- const getResponse = await apiContext.get('/users/1');
800
- expect(getResponse.status()).toBe(404);
801
- });
802
-
803
- test('should handle authentication errors', async ({ apiContext }) => {
804
- const response = await apiContext.get('/users/protected', {
805
- headers: { 'Authorization': 'Bearer invalid-token' },
806
- });
807
- expect(response.status()).toBe(401);
808
- });
809
- });
810
- ```
811
-
812
- ### 9. Visual Regression Testing
813
-
814
- **tests/e2e/visual/homepage.spec.ts**:
815
- ```typescript
816
- import { test, expect } from '@playwright/test';
817
-
818
- test.describe('Visual Regression', () => {
819
- test('homepage should match snapshot', async ({ page }) => {
820
- await page.goto('/');
821
- await page.waitForLoadState('networkidle');
822
-
823
- await expect(page).toHaveScreenshot('homepage.png', {
824
- fullPage: true,
825
- maxDiffPixels: 100,
826
- });
827
- });
828
-
829
- test('login page should match snapshot', async ({ page }) => {
830
- await page.goto('/login');
831
-
832
- await expect(page).toHaveScreenshot('login.png', {
833
- mask: [page.locator('.timestamp')], // Mask dynamic content
834
- });
835
- });
836
-
837
- test('mobile viewport should match snapshot', async ({ page }) => {
838
- await page.setViewportSize({ width: 375, height: 667 });
839
- await page.goto('/');
840
-
841
- await expect(page).toHaveScreenshot('homepage-mobile.png');
842
- });
843
- });
844
- ```
845
-
846
- ### 10. Performance Testing
847
-
848
- **tests/e2e/performance/metrics.spec.ts**:
849
- ```typescript
850
- import { test, expect } from '@playwright/test';
851
-
852
- test.describe('Performance Metrics', () => {
853
- test('should meet Core Web Vitals thresholds', async ({ page }) => {
854
- await page.goto('/');
855
-
856
- const metrics = await page.evaluate(() => {
857
- return new Promise((resolve) => {
858
- new PerformanceObserver((list) => {
859
- const entries = list.getEntries();
860
- const values = entries.reduce((acc, entry) => {
861
- acc[entry.name] = entry.value;
862
- return acc;
863
- }, {} as Record<string, number>);
864
- resolve(values);
865
- }).observe({ entryTypes: ['measure', 'navigation'] });
866
- });
867
- });
868
-
869
- // Verify performance metrics
870
- expect(metrics['first-contentful-paint']).toBeLessThan(1800); // FCP < 1.8s
871
- expect(metrics['largest-contentful-paint']).toBeLessThan(2500); // LCP < 2.5s
872
- });
873
-
874
- test('should load page within 3 seconds', async ({ page }) => {
875
- const startTime = Date.now();
876
- await page.goto('/');
877
- await page.waitForLoadState('load');
878
- const loadTime = Date.now() - startTime;
879
-
880
- expect(loadTime).toBeLessThan(3000);
881
- });
882
-
883
- test('should have minimal bundle size', async ({ page }) => {
884
- const response = await page.goto('/');
885
- const transferSize = await response?.request().sizes().then(s => s.responseBodySize);
886
-
887
- expect(transferSize).toBeLessThan(500000); // < 500KB
888
- });
889
- });
890
- ```
891
-
892
- ### 11. CI/CD Integration
893
-
894
- **GitHub Actions (.github/workflows/e2e.yml)**:
895
- ```yaml
896
- name: E2E Tests
897
-
898
- on:
899
- push:
900
- branches: [main, develop]
901
- pull_request:
902
- branches: [main]
903
-
904
- jobs:
905
- test:
906
- timeout-minutes: 60
907
- runs-on: ubuntu-latest
908
- strategy:
909
- matrix:
910
- browser: [chromium, firefox, webkit]
911
-
912
- steps:
913
- - uses: actions/checkout@v4
914
-
915
- - uses: actions/setup-node@v4
916
- with:
917
- node-version: '20'
918
- cache: 'npm'
919
-
920
- - name: Install dependencies
921
- run: npm ci
922
-
923
- - name: Install Playwright Browsers
924
- run: npx playwright install --with-deps ${{ matrix.browser }}
925
-
926
- - name: Run Playwright tests
927
- run: npx playwright test --project=${{ matrix.browser }}
928
- env:
929
- BASE_URL: http://localhost:3000
930
- USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
931
- USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
932
-
933
- - name: Upload test results
934
- if: always()
935
- uses: actions/upload-artifact@v3
936
- with:
937
- name: playwright-report-${{ matrix.browser }}
938
- path: playwright-report/
939
- retention-days: 30
940
-
941
- - name: Upload test videos
942
- if: failure()
943
- uses: actions/upload-artifact@v3
944
- with:
945
- name: test-videos-${{ matrix.browser }}
946
- path: test-results/**/video.webm
947
- retention-days: 7
948
-
949
- test-sharded:
950
- timeout-minutes: 60
951
- runs-on: ubuntu-latest
952
- strategy:
953
- matrix:
954
- shardIndex: [1, 2, 3, 4]
955
- shardTotal: [4]
956
-
957
- steps:
958
- - uses: actions/checkout@v4
959
- - uses: actions/setup-node@v4
960
- with:
961
- node-version: '20'
962
-
963
- - name: Install dependencies
964
- run: npm ci
965
-
966
- - name: Install Playwright
967
- run: npx playwright install --with-deps
968
-
969
- - name: Run sharded tests
970
- run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
971
-
972
- - name: Upload blob report
973
- uses: actions/upload-artifact@v3
974
- with:
975
- name: blob-report-${{ matrix.shardIndex }}
976
- path: blob-report
977
- retention-days: 1
978
-
979
- merge-reports:
980
- if: always()
981
- needs: [test-sharded]
982
- runs-on: ubuntu-latest
983
-
984
- steps:
985
- - uses: actions/checkout@v4
986
- - uses: actions/setup-node@v4
987
-
988
- - name: Download all reports
989
- uses: actions/download-artifact@v3
990
- with:
991
- path: all-blob-reports
992
-
993
- - name: Merge reports
994
- run: npx playwright merge-reports --reporter html ./all-blob-reports
995
-
996
- - name: Upload merged report
997
- uses: actions/upload-artifact@v3
998
- with:
999
- name: playwright-report
1000
- path: playwright-report/
1001
- ```
1002
-
1003
- ### 12. Package Dependencies
1004
-
1005
- ```json
1006
- {
1007
- "devDependencies": {
1008
- "@playwright/test": "^1.40.0",
1009
- "dotenv": "^16.3.1",
1010
- "playwright": "^1.40.0"
1011
- },
1012
- "scripts": {
1013
- "test:e2e": "playwright test",
1014
- "test:e2e:ui": "playwright test --ui",
1015
- "test:e2e:debug": "playwright test --debug",
1016
- "test:e2e:headed": "playwright test --headed",
1017
- "test:e2e:chromium": "playwright test --project=chromium",
1018
- "test:e2e:firefox": "playwright test --project=firefox",
1019
- "test:e2e:webkit": "playwright test --project=webkit",
1020
- "test:e2e:mobile": "playwright test --project=mobile-chrome --project=mobile-safari",
1021
- "test:e2e:report": "playwright show-report",
1022
- "test:e2e:codegen": "playwright codegen"
1023
- }
1024
- }
1025
- ```
1026
-
1027
- ### 13. Environment Configuration
1028
-
1029
- **.env.example**:
1030
- ```bash
1031
- # Base URLs
1032
- BASE_URL=http://localhost:3000
1033
- API_BASE_URL=http://localhost:3000/api
1034
-
1035
- # Test credentials
1036
- USER_EMAIL=test@example.com
1037
- USER_PASSWORD=password123
1038
- ADMIN_EMAIL=admin@example.com
1039
- ADMIN_PASSWORD=admin123
1040
-
1041
- # CI/CD
1042
- CI=false
1043
- PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=false
1044
- ```
1045
-
1046
- ### 14. Best Practices
1047
-
1048
- **Test Organization**:
1049
- - Group tests by feature/domain
1050
- - Use descriptive test names
1051
- - One assertion per test (when possible)
1052
- - Use page objects for reusability
1053
- - Keep tests independent
1054
-
1055
- **Reliability**:
1056
- - Use auto-waiting features
1057
- - Avoid hard-coded waits (`page.waitForTimeout`)
1058
- - Use data-testid attributes
1059
- - Retry flaky tests automatically
1060
- - Clean up test data
1061
-
1062
- **Performance**:
1063
- - Run tests in parallel
1064
- - Use test sharding for large suites
1065
- - Reuse authentication state
1066
- - Block unnecessary resources
1067
- - Use fast selectors (data-testid > text)
1068
-
1069
- **Debugging**:
1070
- - Use Playwright Inspector (`--debug`)
1071
- - Record traces on failure
1072
- - Take screenshots on failure
1073
- - Use UI mode for interactive debugging
1074
- - Check network logs
1075
-
1076
- ## Workflow
1077
-
1078
- 1. Ask about application type and E2E requirements
1079
- 2. Install Playwright and dependencies
1080
- 3. Create playwright.config.ts with all browsers
1081
- 4. Set up page object model structure
1082
- 5. Create custom fixtures for auth and API
1083
- 6. Implement global setup/teardown
1084
- 7. Create authentication setup tests
1085
- 8. Generate test utilities and helpers
1086
- 9. Write example E2E test suite
1087
- 10. Configure CI/CD pipeline
1088
- 11. Set up visual regression testing
1089
- 12. Add performance testing
1090
- 13. Provide debugging and maintenance guide
1091
-
1092
- ## When to Use
1093
-
1094
- - Setting up E2E testing from scratch
1095
- - Migrating from Selenium/Cypress to Playwright
1096
- - Adding cross-browser testing
1097
- - Implementing visual regression testing
1098
- - Setting up CI/CD for E2E tests
1099
- - Adding performance testing
1100
- - Creating page object model architecture
1101
- - Implementing authentication testing
1102
-
1103
- Set up production-ready Playwright E2E testing with comprehensive coverage!