ima-claude 2.9.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.
- package/LICENSE +21 -0
- package/README.md +463 -0
- package/dist/cli.js +1064 -0
- package/package.json +49 -0
- package/platforms/claude/adapter.ts +115 -0
- package/platforms/junie/adapter.ts +254 -0
- package/platforms/junie/agents-template.md +113 -0
- package/platforms/junie/hook-translations.md +84 -0
- package/platforms/shared/detector.ts +27 -0
- package/platforms/shared/installer.ts +202 -0
- package/platforms/shared/types.ts +78 -0
- package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
- package/plugins/ima-claude/agents/explorer.md +30 -0
- package/plugins/ima-claude/agents/implementer.md +30 -0
- package/plugins/ima-claude/agents/memory.md +42 -0
- package/plugins/ima-claude/agents/reviewer.md +53 -0
- package/plugins/ima-claude/agents/tester.md +33 -0
- package/plugins/ima-claude/agents/wp-developer.md +46 -0
- package/plugins/ima-claude/hooks/README.md +145 -0
- package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
- package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
- package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
- package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
- package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
- package/plugins/ima-claude/hooks/docs_organization.py +104 -0
- package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
- package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
- package/plugins/ima-claude/hooks/hook_logger.py +69 -0
- package/plugins/ima-claude/hooks/hooks.json +239 -0
- package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
- package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
- package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
- package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
- package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
- package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
- package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
- package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
- package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
- package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
- package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
- package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
- package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
- package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
- package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
- package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
- package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
- package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
- package/plugins/ima-claude/personalities/README.md +45 -0
- package/plugins/ima-claude/personalities/enable-40k.md +69 -0
- package/plugins/ima-claude/personalities/enable-templars.md +69 -0
- package/plugins/ima-claude/skills/.research-summary.md +340 -0
- package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
- package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
- package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
- package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
- package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
- package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
- package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
- package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
- package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
- package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
- package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
- package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
- package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
- package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
- package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
- package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
- package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
- package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
- package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
- package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
- package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
- package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
- package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
- package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
- package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
- package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
- package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
- package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
- package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
- package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
- package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
- package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
- package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
- package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
- package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
- package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
- package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
- package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
- package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
- package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
- package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
- package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
- package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
- package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
- package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
- package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
- package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
- package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
- package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
- package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
- package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
- package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
- package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
- package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
- package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
- package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
- package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
- package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
- package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
- package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
- package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
- package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
- package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
- package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
- package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
- package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
- package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
- package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
- package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
- package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: "playwright"
|
|
3
|
+
description: "End-to-end testing and QA automation with Playwright + TypeScript. Test strategy, locators, fixtures, POM, assertions, network mocking, visual regression, accessibility, and CI/CD. Use when: writing E2E tests, creating page objects, setting up test fixtures, mocking API responses, visual regression testing, accessibility audits, configuring Playwright projects, debugging flaky tests, or when user mentions Playwright, E2E, end-to-end, browser testing, test automation, QA automation, getByRole, locator, toBeVisible, toHaveScreenshot, or page.route."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Playwright + QA Automation
|
|
7
|
+
|
|
8
|
+
E2E testing with Playwright and TypeScript. Combines QA strategy (what/why to test) with Playwright implementation (how to test).
|
|
9
|
+
|
|
10
|
+
## QA Strategy Layer
|
|
11
|
+
|
|
12
|
+
### Test Pyramid for E2E
|
|
13
|
+
|
|
14
|
+
E2E tests are expensive. Use them strategically:
|
|
15
|
+
|
|
16
|
+
- **Test critical user journeys** — login, checkout, signup, core workflows
|
|
17
|
+
- **Don't duplicate unit test coverage** — if business logic is tested in unit tests, don't re-test it through the UI
|
|
18
|
+
- **Test integration points** — where frontend meets backend, where data flows between systems
|
|
19
|
+
- **Test what users actually do** — real workflows, not implementation details
|
|
20
|
+
|
|
21
|
+
### What Makes a Good E2E Test
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// GOOD: Tests a real user journey with meaningful assertions
|
|
25
|
+
test('user completes checkout flow', async ({ page }) => {
|
|
26
|
+
await page.goto('/products')
|
|
27
|
+
await page.getByRole('button', { name: 'Add to cart' }).first().click()
|
|
28
|
+
await page.getByRole('link', { name: 'Cart' }).click()
|
|
29
|
+
await page.getByRole('button', { name: 'Checkout' }).click()
|
|
30
|
+
await expect(page.getByText('Order confirmed')).toBeVisible()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// BAD: Tests implementation details, brittle selectors
|
|
34
|
+
test('checkout works', async ({ page }) => {
|
|
35
|
+
await page.goto('/products')
|
|
36
|
+
await page.click('#product-123 > .btn-primary') // brittle
|
|
37
|
+
await page.waitForTimeout(2000) // never do this
|
|
38
|
+
expect(await page.locator('.cart-count').innerText()).toBe('1') // manual assertion
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Test Independence
|
|
43
|
+
|
|
44
|
+
Each test must be self-contained:
|
|
45
|
+
- Set up its own data (via API calls, not UI when possible)
|
|
46
|
+
- Clean up after itself (or use fresh browser contexts — Playwright's default)
|
|
47
|
+
- Never depend on another test's state or execution order
|
|
48
|
+
|
|
49
|
+
## Locator Strategy
|
|
50
|
+
|
|
51
|
+
**Priority order** (most resilient to least):
|
|
52
|
+
|
|
53
|
+
| Priority | Locator | Example | Why |
|
|
54
|
+
|----------|---------|---------|-----|
|
|
55
|
+
| 1 | Role | `getByRole('button', { name: 'Submit' })` | Semantic, accessible |
|
|
56
|
+
| 2 | Label | `getByLabel('Email address')` | User-facing text |
|
|
57
|
+
| 3 | Placeholder | `getByPlaceholder('Enter email')` | User-facing text |
|
|
58
|
+
| 4 | Text | `getByText('Welcome back')` | User-visible content |
|
|
59
|
+
| 5 | Test ID | `getByTestId('submit-btn')` | Stable, explicit contract |
|
|
60
|
+
| 6 | CSS/XPath | `locator('.btn-primary')` | Last resort only |
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// Prefer semantic locators
|
|
64
|
+
await page.getByRole('button', { name: 'Save changes' }).click()
|
|
65
|
+
await page.getByLabel('Email').fill('user@example.com')
|
|
66
|
+
|
|
67
|
+
// Use test IDs when no semantic option exists
|
|
68
|
+
await page.getByTestId('chart-container').isVisible()
|
|
69
|
+
|
|
70
|
+
// Scoped locators for disambiguation
|
|
71
|
+
const dialog = page.getByRole('dialog')
|
|
72
|
+
await dialog.getByRole('button', { name: 'Confirm' }).click()
|
|
73
|
+
|
|
74
|
+
// Filter locators for lists
|
|
75
|
+
await page.getByRole('listitem').filter({ hasText: 'Product A' }).click()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Assertions
|
|
79
|
+
|
|
80
|
+
**Always use web-first assertions** — they auto-wait and retry.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// CORRECT: Web-first assertions (auto-wait + retry)
|
|
84
|
+
await expect(page.getByText('Success')).toBeVisible()
|
|
85
|
+
await expect(page.getByRole('heading')).toHaveText('Dashboard')
|
|
86
|
+
await expect(page).toHaveURL(/\/dashboard/)
|
|
87
|
+
await expect(page).toHaveTitle('My App - Dashboard')
|
|
88
|
+
|
|
89
|
+
// WRONG: Manual assertions (race conditions, flaky)
|
|
90
|
+
expect(await page.getByText('Success').isVisible()).toBe(true)
|
|
91
|
+
const text = await page.locator('h1').innerText()
|
|
92
|
+
expect(text).toBe('Dashboard')
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Key Assertions
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// Visibility
|
|
99
|
+
await expect(locator).toBeVisible()
|
|
100
|
+
await expect(locator).toBeHidden()
|
|
101
|
+
|
|
102
|
+
// Text content
|
|
103
|
+
await expect(locator).toHaveText('exact text')
|
|
104
|
+
await expect(locator).toContainText('partial')
|
|
105
|
+
|
|
106
|
+
// Input state
|
|
107
|
+
await expect(locator).toHaveValue('test@example.com')
|
|
108
|
+
await expect(locator).toBeChecked()
|
|
109
|
+
await expect(locator).toBeDisabled()
|
|
110
|
+
|
|
111
|
+
// Count
|
|
112
|
+
await expect(page.getByRole('listitem')).toHaveCount(3)
|
|
113
|
+
|
|
114
|
+
// Page-level
|
|
115
|
+
await expect(page).toHaveURL(/dashboard/)
|
|
116
|
+
await expect(page).toHaveTitle(/Dashboard/)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Page Object Model
|
|
120
|
+
|
|
121
|
+
Encapsulate page interactions in classes. Tests read like user stories.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// pages/LoginPage.ts
|
|
125
|
+
import { type Page, type Locator, expect } from '@playwright/test'
|
|
126
|
+
|
|
127
|
+
export class LoginPage {
|
|
128
|
+
readonly emailInput: Locator
|
|
129
|
+
readonly passwordInput: Locator
|
|
130
|
+
readonly submitButton: Locator
|
|
131
|
+
readonly errorMessage: Locator
|
|
132
|
+
|
|
133
|
+
constructor(private readonly page: Page) {
|
|
134
|
+
this.emailInput = page.getByLabel('Email')
|
|
135
|
+
this.passwordInput = page.getByLabel('Password')
|
|
136
|
+
this.submitButton = page.getByRole('button', { name: 'Sign in' })
|
|
137
|
+
this.errorMessage = page.getByRole('alert')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async goto() {
|
|
141
|
+
await this.page.goto('/login')
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async login(email: string, password: string) {
|
|
145
|
+
await this.emailInput.fill(email)
|
|
146
|
+
await this.passwordInput.fill(password)
|
|
147
|
+
await this.submitButton.click()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async expectError(message: string) {
|
|
151
|
+
await expect(this.errorMessage).toContainText(message)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Register Page Objects as Fixtures
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// fixtures.ts
|
|
160
|
+
import { test as base } from '@playwright/test'
|
|
161
|
+
import { LoginPage } from './pages/LoginPage'
|
|
162
|
+
import { DashboardPage } from './pages/DashboardPage'
|
|
163
|
+
|
|
164
|
+
type Fixtures = {
|
|
165
|
+
loginPage: LoginPage
|
|
166
|
+
dashboardPage: DashboardPage
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const test = base.extend<Fixtures>({
|
|
170
|
+
loginPage: async ({ page }, use) => {
|
|
171
|
+
await use(new LoginPage(page))
|
|
172
|
+
},
|
|
173
|
+
dashboardPage: async ({ page }, use) => {
|
|
174
|
+
await use(new DashboardPage(page))
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
export { expect } from '@playwright/test'
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// tests/login.spec.ts
|
|
183
|
+
import { test, expect } from '../fixtures'
|
|
184
|
+
|
|
185
|
+
test('successful login redirects to dashboard', async ({ loginPage, dashboardPage }) => {
|
|
186
|
+
await loginPage.goto()
|
|
187
|
+
await loginPage.login('user@example.com', 'password123')
|
|
188
|
+
await expect(dashboardPage.heading).toBeVisible()
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('invalid credentials show error', async ({ loginPage }) => {
|
|
192
|
+
await loginPage.goto()
|
|
193
|
+
await loginPage.login('user@example.com', 'wrong')
|
|
194
|
+
await loginPage.expectError('Invalid credentials')
|
|
195
|
+
})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Custom Fixtures
|
|
199
|
+
|
|
200
|
+
Fixtures provide reusable setup/teardown. Use them instead of beforeEach/afterEach.
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
// fixtures.ts — authenticated user fixture
|
|
204
|
+
export const test = base.extend<{
|
|
205
|
+
authenticatedPage: Page
|
|
206
|
+
}>({
|
|
207
|
+
authenticatedPage: async ({ browser }, use) => {
|
|
208
|
+
const context = await browser.newContext({
|
|
209
|
+
storageState: 'auth/user.json' // pre-saved auth state
|
|
210
|
+
})
|
|
211
|
+
const page = await context.newPage()
|
|
212
|
+
await use(page)
|
|
213
|
+
await context.close()
|
|
214
|
+
},
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Save Auth State (Global Setup)
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// global-setup.ts
|
|
222
|
+
import { chromium, type FullConfig } from '@playwright/test'
|
|
223
|
+
|
|
224
|
+
export default async function globalSetup(config: FullConfig) {
|
|
225
|
+
const browser = await chromium.launch()
|
|
226
|
+
const page = await browser.newPage()
|
|
227
|
+
await page.goto('/login')
|
|
228
|
+
await page.getByLabel('Email').fill(process.env.TEST_USER!)
|
|
229
|
+
await page.getByLabel('Password').fill(process.env.TEST_PASSWORD!)
|
|
230
|
+
await page.getByRole('button', { name: 'Sign in' }).click()
|
|
231
|
+
await page.waitForURL('/dashboard')
|
|
232
|
+
await page.context().storageState({ path: 'auth/user.json' })
|
|
233
|
+
await browser.close()
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Project Structure
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
tests/
|
|
241
|
+
├── e2e/
|
|
242
|
+
│ ├── auth/
|
|
243
|
+
│ │ ├── login.spec.ts
|
|
244
|
+
│ │ └── signup.spec.ts
|
|
245
|
+
│ ├── checkout/
|
|
246
|
+
│ │ └── purchase-flow.spec.ts
|
|
247
|
+
│ └── dashboard/
|
|
248
|
+
│ └── widgets.spec.ts
|
|
249
|
+
├── pages/
|
|
250
|
+
│ ├── LoginPage.ts
|
|
251
|
+
│ ├── DashboardPage.ts
|
|
252
|
+
│ └── CheckoutPage.ts
|
|
253
|
+
├── components/
|
|
254
|
+
│ ├── Modal.ts
|
|
255
|
+
│ ├── DataTable.ts
|
|
256
|
+
│ └── Navigation.ts
|
|
257
|
+
├── fixtures.ts
|
|
258
|
+
├── global-setup.ts
|
|
259
|
+
└── playwright.config.ts
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Guidelines**:
|
|
263
|
+
- One page object per page/major section
|
|
264
|
+
- Shared components (modals, tables, nav) get their own classes in `components/`
|
|
265
|
+
- Tests grouped by feature area
|
|
266
|
+
- Single `fixtures.ts` exports the extended `test` object
|
|
267
|
+
|
|
268
|
+
## Configuration
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// playwright.config.ts
|
|
272
|
+
import { defineConfig, devices } from '@playwright/test'
|
|
273
|
+
|
|
274
|
+
export default defineConfig({
|
|
275
|
+
testDir: './tests/e2e',
|
|
276
|
+
fullyParallel: true,
|
|
277
|
+
forbidOnly: !!process.env.CI,
|
|
278
|
+
retries: process.env.CI ? 2 : 0,
|
|
279
|
+
workers: process.env.CI ? 1 : undefined,
|
|
280
|
+
reporter: process.env.CI ? 'blob' : 'html',
|
|
281
|
+
globalSetup: './tests/global-setup.ts',
|
|
282
|
+
|
|
283
|
+
use: {
|
|
284
|
+
baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
|
|
285
|
+
trace: 'on-first-retry',
|
|
286
|
+
screenshot: 'only-on-failure',
|
|
287
|
+
video: 'retain-on-failure',
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
projects: [
|
|
291
|
+
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
292
|
+
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
|
|
293
|
+
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
|
|
294
|
+
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
|
|
295
|
+
{ name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
|
|
296
|
+
],
|
|
297
|
+
|
|
298
|
+
webServer: {
|
|
299
|
+
command: 'npm run dev',
|
|
300
|
+
url: 'http://localhost:3000',
|
|
301
|
+
reuseExistingServer: !process.env.CI,
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Network Mocking (Quick Reference)
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// Mock an API endpoint
|
|
310
|
+
await page.route('**/api/users', route => route.fulfill({
|
|
311
|
+
status: 200,
|
|
312
|
+
contentType: 'application/json',
|
|
313
|
+
body: JSON.stringify([{ id: 1, name: 'Test User' }]),
|
|
314
|
+
}))
|
|
315
|
+
|
|
316
|
+
// Modify a real response
|
|
317
|
+
await page.route('**/api/products', async route => {
|
|
318
|
+
const response = await route.fetch()
|
|
319
|
+
const json = await response.json()
|
|
320
|
+
json.push({ id: 999, name: 'Test Product' })
|
|
321
|
+
await route.fulfill({ response, json })
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
// Block resources (speed up tests)
|
|
325
|
+
await page.route('**/*.{png,jpg,gif}', route => route.abort())
|
|
326
|
+
|
|
327
|
+
// Add auth headers
|
|
328
|
+
await page.route('**/api/**', route => {
|
|
329
|
+
route.continue({
|
|
330
|
+
headers: { ...route.request().headers(), Authorization: 'Bearer test-token' },
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
For advanced mocking patterns (HAR recording, API-first setup, error simulation), see [references/network-mocking.md](references/network-mocking.md).
|
|
336
|
+
|
|
337
|
+
## Visual Regression (Quick Reference)
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// Full page screenshot comparison
|
|
341
|
+
await expect(page).toHaveScreenshot()
|
|
342
|
+
|
|
343
|
+
// Element-level comparison
|
|
344
|
+
await expect(page.getByTestId('chart')).toHaveScreenshot('chart.png')
|
|
345
|
+
|
|
346
|
+
// With tolerance for minor rendering differences
|
|
347
|
+
await expect(page).toHaveScreenshot({ maxDiffPixelRatio: 0.01 })
|
|
348
|
+
|
|
349
|
+
// Update baselines: npx playwright test --update-snapshots
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
For deterministic screenshots, animation handling, and CI considerations, see [references/visual-regression.md](references/visual-regression.md).
|
|
353
|
+
|
|
354
|
+
## Debugging
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
# Run with UI mode (interactive debugging)
|
|
358
|
+
npx playwright test --ui
|
|
359
|
+
|
|
360
|
+
# Run with headed browser
|
|
361
|
+
npx playwright test --headed
|
|
362
|
+
|
|
363
|
+
# Run with step-by-step trace viewer
|
|
364
|
+
npx playwright test --trace on
|
|
365
|
+
|
|
366
|
+
# Debug a specific test
|
|
367
|
+
npx playwright test -g "login" --debug
|
|
368
|
+
|
|
369
|
+
# View last test report
|
|
370
|
+
npx playwright show-report
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
// Pause execution for manual inspection
|
|
375
|
+
await page.pause()
|
|
376
|
+
|
|
377
|
+
// Slow down actions for visual debugging
|
|
378
|
+
// In config: use: { launchOptions: { slowMo: 500 } }
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Anti-Patterns
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// NEVER: Hard-coded waits
|
|
385
|
+
await page.waitForTimeout(3000) // Use auto-waiting instead
|
|
386
|
+
|
|
387
|
+
// NEVER: Brittle CSS selectors
|
|
388
|
+
await page.click('#app > div:nth-child(2) > button.btn-primary')
|
|
389
|
+
|
|
390
|
+
// NEVER: Manual assertions without retry
|
|
391
|
+
const text = await page.locator('.status').innerText()
|
|
392
|
+
expect(text).toBe('Ready') // Race condition — use web-first assertions
|
|
393
|
+
|
|
394
|
+
// NEVER: Testing third-party dependencies
|
|
395
|
+
await page.goto('https://external-service.com/verify') // Mock it instead
|
|
396
|
+
|
|
397
|
+
// NEVER: Shared mutable state between tests
|
|
398
|
+
let sharedUser // Each test gets its own state via fixtures
|
|
399
|
+
|
|
400
|
+
// NEVER: Skipping cleanup
|
|
401
|
+
// Use fixtures — they handle teardown automatically
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Linting
|
|
405
|
+
|
|
406
|
+
Use `eslint-plugin-playwright` to catch common mistakes:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
npm install -D eslint-plugin-playwright
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Key rules it catches:
|
|
413
|
+
- Missing `await` on async Playwright calls
|
|
414
|
+
- Using `page.waitForTimeout` (prefer auto-waiting)
|
|
415
|
+
- Manual assertions instead of web-first assertions
|
|
416
|
+
- Using forbidden selectors (nth-child, etc.)
|
|
417
|
+
|
|
418
|
+
## Reference Files
|
|
419
|
+
|
|
420
|
+
### Network Mocking Patterns
|
|
421
|
+
**File**: [references/network-mocking.md](references/network-mocking.md)
|
|
422
|
+
**When**: Mocking APIs, HAR recording, intercepting requests, simulating errors, API-first test setup
|
|
423
|
+
|
|
424
|
+
### Visual Regression Testing
|
|
425
|
+
**File**: [references/visual-regression.md](references/visual-regression.md)
|
|
426
|
+
**When**: Screenshot comparisons, deterministic rendering, CI screenshot strategies, animation handling
|
|
427
|
+
|
|
428
|
+
### Accessibility Testing
|
|
429
|
+
**File**: [references/accessibility-testing.md](references/accessibility-testing.md)
|
|
430
|
+
**When**: WCAG compliance audits, axe-core integration, accessibility fixtures, automated a11y checks
|
|
431
|
+
|
|
432
|
+
### CI/CD Integration
|
|
433
|
+
**File**: [references/ci-cd.md](references/ci-cd.md)
|
|
434
|
+
**When**: GitHub Actions setup, sharding, parallelism, artifact management, Docker, reporting strategies
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Accessibility Testing
|
|
2
|
+
|
|
3
|
+
Automated WCAG compliance checks using Playwright with axe-core.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Setup](#setup)
|
|
8
|
+
- [Basic Scanning](#basic-scanning)
|
|
9
|
+
- [Fixtures for Reusable Config](#fixtures-for-reusable-config)
|
|
10
|
+
- [Targeted Scanning](#targeted-scanning)
|
|
11
|
+
- [Common Violations](#common-violations)
|
|
12
|
+
- [Integration with E2E Tests](#integration-with-e2e-tests)
|
|
13
|
+
|
|
14
|
+
## Setup
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -D @axe-core/playwright
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Basic Scanning
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { test, expect } from '@playwright/test'
|
|
24
|
+
import AxeBuilder from '@axe-core/playwright'
|
|
25
|
+
|
|
26
|
+
test('homepage has no a11y violations', async ({ page }) => {
|
|
27
|
+
await page.goto('/')
|
|
28
|
+
|
|
29
|
+
const results = await new AxeBuilder({ page }).analyze()
|
|
30
|
+
|
|
31
|
+
expect(results.violations).toEqual([])
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Fixtures for Reusable Config
|
|
36
|
+
|
|
37
|
+
Create a shared axe fixture with consistent WCAG rules across all tests.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// fixtures.ts
|
|
41
|
+
import { test as base } from '@playwright/test'
|
|
42
|
+
import AxeBuilder from '@axe-core/playwright'
|
|
43
|
+
|
|
44
|
+
type A11yFixtures = {
|
|
45
|
+
makeAxeBuilder: () => AxeBuilder
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const test = base.extend<A11yFixtures>({
|
|
49
|
+
makeAxeBuilder: async ({ page }, use) => {
|
|
50
|
+
await use(() =>
|
|
51
|
+
new AxeBuilder({ page })
|
|
52
|
+
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
|
|
53
|
+
)
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export { expect } from '@playwright/test'
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// tests/a11y/pages.spec.ts
|
|
62
|
+
import { test, expect } from '../../fixtures'
|
|
63
|
+
|
|
64
|
+
test('login page is accessible', async ({ page, makeAxeBuilder }) => {
|
|
65
|
+
await page.goto('/login')
|
|
66
|
+
const results = await makeAxeBuilder().analyze()
|
|
67
|
+
expect(results.violations).toEqual([])
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test('dashboard is accessible', async ({ page, makeAxeBuilder }) => {
|
|
71
|
+
await page.goto('/dashboard')
|
|
72
|
+
const results = await makeAxeBuilder().analyze()
|
|
73
|
+
expect(results.violations).toEqual([])
|
|
74
|
+
})
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Targeted Scanning
|
|
78
|
+
|
|
79
|
+
### Scan Specific Sections
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Only scan the main content area
|
|
83
|
+
const results = await new AxeBuilder({ page })
|
|
84
|
+
.include('#main-content')
|
|
85
|
+
.analyze()
|
|
86
|
+
|
|
87
|
+
// Scan everything except a known-broken third-party widget
|
|
88
|
+
const results = await new AxeBuilder({ page })
|
|
89
|
+
.exclude('.third-party-chat-widget')
|
|
90
|
+
.analyze()
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Specific Rule Sets
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// Only check color contrast
|
|
97
|
+
const results = await new AxeBuilder({ page })
|
|
98
|
+
.withRules(['color-contrast'])
|
|
99
|
+
.analyze()
|
|
100
|
+
|
|
101
|
+
// Disable specific rules (with justification)
|
|
102
|
+
const results = await new AxeBuilder({ page })
|
|
103
|
+
.disableRules(['color-contrast']) // Third-party component, tracked in JIRA-123
|
|
104
|
+
.analyze()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Common Violations
|
|
108
|
+
|
|
109
|
+
| Violation | Fix | Impact |
|
|
110
|
+
|-----------|-----|--------|
|
|
111
|
+
| Missing alt text | Add `alt` attribute to `<img>` tags | Critical |
|
|
112
|
+
| Missing form labels | Associate `<label>` with inputs via `for`/`id` | Critical |
|
|
113
|
+
| Insufficient contrast | Adjust foreground/background colors to meet 4.5:1 ratio | Serious |
|
|
114
|
+
| Missing landmark regions | Wrap content in `<main>`, `<nav>`, `<header>` | Moderate |
|
|
115
|
+
| Missing page title | Add descriptive `<title>` element | Serious |
|
|
116
|
+
| Duplicate IDs | Ensure all `id` attributes are unique | Moderate |
|
|
117
|
+
| Missing language attribute | Add `lang` attribute to `<html>` | Serious |
|
|
118
|
+
|
|
119
|
+
## Integration with E2E Tests
|
|
120
|
+
|
|
121
|
+
Run accessibility checks as part of existing E2E flows, not just standalone audits.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
test('checkout flow is accessible at each step', async ({ page, makeAxeBuilder }) => {
|
|
125
|
+
// Step 1: Cart page
|
|
126
|
+
await page.goto('/cart')
|
|
127
|
+
let a11y = await makeAxeBuilder().analyze()
|
|
128
|
+
expect(a11y.violations).toEqual([])
|
|
129
|
+
|
|
130
|
+
// Step 2: Shipping form
|
|
131
|
+
await page.getByRole('button', { name: 'Proceed to shipping' }).click()
|
|
132
|
+
a11y = await makeAxeBuilder().analyze()
|
|
133
|
+
expect(a11y.violations).toEqual([])
|
|
134
|
+
|
|
135
|
+
// Step 3: Payment form
|
|
136
|
+
await page.getByRole('button', { name: 'Continue to payment' }).click()
|
|
137
|
+
a11y = await makeAxeBuilder().analyze()
|
|
138
|
+
expect(a11y.violations).toEqual([])
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Dedicated A11y Project
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// playwright.config.ts — separate project for a11y tests
|
|
146
|
+
projects: [
|
|
147
|
+
{
|
|
148
|
+
name: 'accessibility',
|
|
149
|
+
testDir: './tests/a11y',
|
|
150
|
+
use: { ...devices['Desktop Chrome'] },
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
```
|