specweave 0.23.18 → 0.24.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/.claude-plugin/marketplace.json +93 -49
- package/CLAUDE.md +137 -4
- package/dist/src/cli/helpers/ado-area-path-mapper.d.ts +89 -0
- package/dist/src/cli/helpers/ado-area-path-mapper.d.ts.map +1 -0
- package/dist/src/cli/helpers/ado-area-path-mapper.js +213 -0
- package/dist/src/cli/helpers/ado-area-path-mapper.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts +29 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js +109 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.js +2 -0
- package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
- package/dist/src/cli/helpers/smart-filter.d.ts +83 -0
- package/dist/src/cli/helpers/smart-filter.d.ts.map +1 -0
- package/dist/src/cli/helpers/smart-filter.js +265 -0
- package/dist/src/cli/helpers/smart-filter.js.map +1 -0
- package/dist/src/core/qa/quality-gate-decider.d.ts +1 -1
- package/dist/src/core/qa/quality-gate-decider.js +2 -2
- package/dist/src/core/qa/quality-gate-decider.js.map +1 -1
- package/dist/src/core/qa/risk-calculator.d.ts +2 -2
- package/dist/src/core/qa/risk-calculator.js +2 -2
- package/dist/src/core/validators/ac-presence-validator.d.ts +56 -0
- package/dist/src/core/validators/ac-presence-validator.d.ts.map +1 -0
- package/dist/src/core/validators/ac-presence-validator.js +149 -0
- package/dist/src/core/validators/ac-presence-validator.js.map +1 -0
- package/dist/src/integrations/ado/area-path-mapper.d.ts +137 -0
- package/dist/src/integrations/ado/area-path-mapper.d.ts.map +1 -0
- package/dist/src/integrations/ado/area-path-mapper.js +267 -0
- package/dist/src/integrations/ado/area-path-mapper.js.map +1 -0
- package/dist/src/integrations/jira/filter-processor.d.ts +126 -0
- package/dist/src/integrations/jira/filter-processor.d.ts.map +1 -0
- package/dist/src/integrations/jira/filter-processor.js +207 -0
- package/dist/src/integrations/jira/filter-processor.js.map +1 -0
- package/dist/src/integrations/jira/jira-client.d.ts +13 -0
- package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
- package/dist/src/integrations/jira/jira-client.js +33 -0
- package/dist/src/integrations/jira/jira-client.js.map +1 -1
- package/dist/src/utils/ac-embedder.d.ts +63 -0
- package/dist/src/utils/ac-embedder.d.ts.map +1 -0
- package/dist/src/utils/ac-embedder.js +217 -0
- package/dist/src/utils/ac-embedder.js.map +1 -0
- package/dist/src/utils/env-manager.d.ts +86 -0
- package/dist/src/utils/env-manager.d.ts.map +1 -0
- package/dist/src/utils/env-manager.js +188 -0
- package/dist/src/utils/env-manager.js.map +1 -0
- package/package.json +1 -1
- package/plugins/specweave/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave/agents/AGENTS-INDEX.md +1 -1
- package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +9 -9
- package/plugins/specweave/commands/specweave-do.md +37 -0
- package/plugins/specweave/commands/specweave-done.md +159 -0
- package/plugins/specweave/commands/specweave-embed-acs.md +446 -0
- package/plugins/specweave/commands/specweave-next.md +148 -3
- package/plugins/specweave/commands/specweave-qa.md +2 -2
- package/plugins/specweave/hooks/pre-increment-start.sh +168 -0
- package/plugins/specweave/skills/SKILLS-INDEX.md +1 -1
- package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-ado/commands/specweave-ado-import-projects.md +331 -0
- package/plugins/specweave-alternatives/.claude-plugin/plugin.json +10 -0
- package/plugins/specweave-alternatives/commands/alternatives-analyze.md +336 -0
- package/plugins/specweave-alternatives/skills/architecture-alternatives/SKILL.md +651 -0
- package/plugins/specweave-alternatives/skills/bmad-method/SKILL.md +420 -0
- package/plugins/specweave-alternatives/skills/spec-kit-expert/SKILL.md +487 -0
- package/plugins/specweave-backend/commands/api-scaffold.md +80 -0
- package/plugins/specweave-backend/commands/crud-generate.md +109 -0
- package/plugins/specweave-backend/commands/migration-generate.md +139 -0
- package/plugins/specweave-confluent/commands/connector-deploy.md +154 -0
- package/plugins/specweave-confluent/commands/ksqldb-query.md +179 -0
- package/plugins/specweave-confluent/commands/schema-register.md +123 -0
- package/plugins/specweave-core/.claude-plugin/plugin.json +21 -0
- package/plugins/specweave-core/commands/architecture-review.md +288 -0
- package/plugins/specweave-core/commands/code-review.md +213 -0
- package/plugins/specweave-core/commands/refactor-plan.md +249 -0
- package/plugins/specweave-core/skills/code-quality/SKILL.md +157 -0
- package/plugins/specweave-core/skills/design-patterns/SKILL.md +244 -0
- package/plugins/specweave-core/skills/software-architecture/SKILL.md +83 -0
- package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave-cost-optimizer/commands/cost-analyze.md +360 -0
- package/plugins/specweave-cost-optimizer/commands/cost-optimize.md +480 -0
- package/plugins/specweave-cost-optimizer/skills/aws-cost-expert/SKILL.md +416 -0
- package/plugins/specweave-cost-optimizer/skills/cloud-pricing/SKILL.md +325 -0
- package/plugins/specweave-cost-optimizer/skills/cost-optimization/SKILL.md +337 -0
- package/plugins/specweave-diagrams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-diagrams/commands/diagrams-generate.md +168 -0
- package/plugins/specweave-docs/.claude-plugin/plugin.json +10 -0
- package/plugins/specweave-docs/commands/docs-generate.md +441 -0
- package/plugins/specweave-docs/commands/docs-init.md +334 -0
- package/plugins/specweave-docs/skills/docusaurus/SKILL.md +581 -0
- package/plugins/specweave-docs/skills/spec-driven-brainstorming/SKILL.md +689 -0
- package/plugins/specweave-docs/skills/technical-writing/SKILL.md +1039 -0
- package/plugins/specweave-docs-preview/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-figma/.claude-plugin/plugin.json +23 -0
- package/plugins/specweave-figma/commands/figma-import.md +690 -0
- package/plugins/specweave-figma/commands/figma-to-react.md +834 -0
- package/plugins/specweave-figma/commands/figma-tokens.md +815 -0
- package/plugins/specweave-frontend/.claude-plugin/plugin.json +21 -0
- package/plugins/specweave-frontend/agents/frontend-architect/AGENT.md +387 -0
- package/plugins/specweave-frontend/agents/frontend-architect/README.md +385 -0
- package/plugins/specweave-frontend/agents/frontend-architect/examples.md +590 -0
- package/plugins/specweave-frontend/agents/frontend-architect/templates/component-template.tsx +152 -0
- package/plugins/specweave-frontend/agents/frontend-architect/templates/hook-template.ts +311 -0
- package/plugins/specweave-frontend/agents/frontend-architect/templates/page-template.tsx +228 -0
- package/plugins/specweave-frontend/commands/component-generate.md +510 -0
- package/plugins/specweave-frontend/commands/design-system-init.md +494 -0
- package/plugins/specweave-frontend/commands/frontend-scaffold.md +207 -0
- package/plugins/specweave-frontend/commands/nextjs-setup.md +396 -0
- package/plugins/specweave-frontend/skills/design-system-architect/SKILL.md +278 -0
- package/plugins/specweave-frontend/skills/frontend/SKILL.md +420 -0
- package/plugins/specweave-frontend/skills/nextjs/SKILL.md +546 -0
- package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +194 -0
- package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-jira/commands/import-projects.js +183 -0
- package/plugins/specweave-jira/commands/import-projects.md +97 -0
- package/plugins/specweave-jira/commands/import-projects.ts +288 -0
- package/plugins/specweave-jira/commands/specweave-jira-import-projects.md +298 -0
- package/plugins/specweave-kafka/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-kubernetes/commands/cluster-setup.md +262 -0
- package/plugins/specweave-kubernetes/commands/deployment-generate.md +242 -0
- package/plugins/specweave-kubernetes/commands/helm-scaffold.md +333 -0
- package/plugins/specweave-ml/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-mobile/commands/app-scaffold.md +233 -0
- package/plugins/specweave-mobile/commands/build-config.md +256 -0
- package/plugins/specweave-mobile/commands/screen-generate.md +289 -0
- package/plugins/specweave-n8n/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-plugin-dev/.claude-plugin/plugin.json +13 -12
- package/plugins/specweave-plugin-dev/commands/plugin-create.md +333 -0
- package/plugins/specweave-plugin-dev/commands/plugin-publish.md +339 -0
- package/plugins/specweave-plugin-dev/commands/plugin-test.md +293 -0
- package/plugins/specweave-plugin-dev/skills/claude-sdk/SKILL.md +162 -0
- package/plugins/specweave-plugin-dev/skills/marketplace-publishing/SKILL.md +263 -0
- package/plugins/specweave-plugin-dev/skills/plugin-development/SKILL.md +316 -0
- package/plugins/specweave-release/.claude-plugin/plugin.json +1 -1
- package/plugins/specweave-release/commands/specweave-release-npm.md +110 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +168 -0
- package/plugins/specweave-testing/.claude-plugin/plugin.json +21 -0
- package/plugins/specweave-testing/agents/qa-engineer/AGENT.md +797 -0
- package/plugins/specweave-testing/agents/qa-engineer/README.md +443 -0
- package/plugins/specweave-testing/agents/qa-engineer/templates/playwright-e2e-test.ts +470 -0
- package/plugins/specweave-testing/agents/qa-engineer/templates/test-data-factory.ts +507 -0
- package/plugins/specweave-testing/agents/qa-engineer/templates/vitest-unit-test.ts +400 -0
- package/plugins/specweave-testing/agents/qa-engineer/test-strategies.md +726 -0
- package/plugins/specweave-testing/commands/e2e-setup.md +1081 -0
- package/plugins/specweave-testing/commands/test-coverage.md +979 -0
- package/plugins/specweave-testing/commands/test-generate.md +1156 -0
- package/plugins/specweave-testing/commands/test-init.md +409 -0
- package/plugins/specweave-testing/skills/e2e-playwright/SKILL.md +769 -0
- package/plugins/specweave-testing/skills/tdd-expert/SKILL.md +934 -0
- package/plugins/specweave-testing/skills/unit-testing-expert/SKILL.md +1011 -0
- package/plugins/specweave-tooling/.claude-plugin/plugin.json +22 -0
- package/plugins/specweave-tooling/commands/specweave-tooling-skill-create.md +691 -0
- package/plugins/specweave-tooling/commands/specweave-tooling-skill-package.md +751 -0
- package/plugins/specweave-tooling/commands/specweave-tooling-skill-validate.md +858 -0
- package/plugins/specweave-ui/.claude-plugin/plugin.json +10 -0
- package/plugins/specweave-ui/commands/ui-automate.md +199 -0
- package/plugins/specweave-ui/commands/ui-inspect.md +70 -0
- package/plugins/specweave-ui/skills/browser-automation/SKILL.md +314 -0
- package/plugins/specweave-ui/skills/ui-testing/SKILL.md +716 -0
- package/plugins/specweave-ui/skills/visual-regression/SKILL.md +728 -0
- package/plugins/specweave/commands/check-hooks.md +0 -257
- package/plugins/specweave/commands/specweave-archive-increments.md +0 -82
- package/plugins/specweave-plugin-dev/skills/plugin-expert/SKILL.md +0 -1231
- /package/plugins/specweave/{agents/code-reviewer.md → skills/code-reviewer/SKILL.md} +0 -0
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-testing
|
|
3
|
+
description: UI and end-to-end testing expert using Playwright Test, Cypress, Testing Library, and component testing frameworks. Covers test strategies, page object models, accessibility testing, CI integration, and test flakiness mitigation. Activates for UI testing, E2E testing, Playwright Test, Cypress, Testing Library, component tests, integration tests, accessibility testing, test flakiness, page object model, CI testing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# UI Testing Skill
|
|
7
|
+
|
|
8
|
+
Expert in UI and end-to-end (E2E) testing using modern frameworks like Playwright Test, Cypress, and Testing Library. Specializes in writing reliable, maintainable tests that catch bugs before production.
|
|
9
|
+
|
|
10
|
+
## Core Testing Frameworks
|
|
11
|
+
|
|
12
|
+
### 1. Playwright Test (Recommended for E2E)
|
|
13
|
+
|
|
14
|
+
**Why Playwright?**
|
|
15
|
+
- Multi-browser support (Chromium, Firefox, WebKit)
|
|
16
|
+
- Auto-wait (no manual `waitFor` needed)
|
|
17
|
+
- Fast parallel execution
|
|
18
|
+
- Built-in assertions and fixtures
|
|
19
|
+
- Network interception and mocking
|
|
20
|
+
|
|
21
|
+
#### Basic Test Structure
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { test, expect } from '@playwright/test';
|
|
25
|
+
|
|
26
|
+
test.describe('User Authentication', () => {
|
|
27
|
+
test('should login with valid credentials', async ({ page }) => {
|
|
28
|
+
// Navigate
|
|
29
|
+
await page.goto('https://example.com/login');
|
|
30
|
+
|
|
31
|
+
// Interact
|
|
32
|
+
await page.fill('input[name="email"]', 'user@example.com');
|
|
33
|
+
await page.fill('input[name="password"]', 'SecurePass123!');
|
|
34
|
+
await page.click('button[type="submit"]');
|
|
35
|
+
|
|
36
|
+
// Assert
|
|
37
|
+
await expect(page).toHaveURL('https://example.com/dashboard');
|
|
38
|
+
await expect(page.locator('h1')).toHaveText('Welcome, User');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('should show error with invalid credentials', async ({ page }) => {
|
|
42
|
+
await page.goto('https://example.com/login');
|
|
43
|
+
|
|
44
|
+
await page.fill('input[name="email"]', 'wrong@example.com');
|
|
45
|
+
await page.fill('input[name="password"]', 'WrongPass');
|
|
46
|
+
await page.click('button[type="submit"]');
|
|
47
|
+
|
|
48
|
+
// Wait for error message
|
|
49
|
+
const errorMessage = page.locator('.error-message');
|
|
50
|
+
await expect(errorMessage).toBeVisible();
|
|
51
|
+
await expect(errorMessage).toHaveText('Invalid credentials');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### Fixtures (Reusable Setup)
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
// fixtures.ts
|
|
60
|
+
import { test as base } from '@playwright/test';
|
|
61
|
+
|
|
62
|
+
type Fixtures = {
|
|
63
|
+
authenticatedPage: Page;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const test = base.extend<Fixtures>({
|
|
67
|
+
authenticatedPage: async ({ page }, use) => {
|
|
68
|
+
// Login before each test
|
|
69
|
+
await page.goto('https://example.com/login');
|
|
70
|
+
await page.fill('input[name="email"]', 'user@example.com');
|
|
71
|
+
await page.fill('input[name="password"]', 'SecurePass123!');
|
|
72
|
+
await page.click('button[type="submit"]');
|
|
73
|
+
await page.waitForURL('**/dashboard');
|
|
74
|
+
|
|
75
|
+
// Use the authenticated page
|
|
76
|
+
await use(page);
|
|
77
|
+
|
|
78
|
+
// Cleanup (logout)
|
|
79
|
+
await page.click('[data-testid="logout"]');
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// tests/dashboard.spec.ts
|
|
84
|
+
import { test, expect } from './fixtures';
|
|
85
|
+
|
|
86
|
+
test('should display user dashboard', async ({ authenticatedPage }) => {
|
|
87
|
+
// Already logged in via fixture
|
|
88
|
+
await expect(authenticatedPage.locator('h1')).toHaveText('Dashboard');
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Page Object Model (POM)
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
// pages/LoginPage.ts
|
|
96
|
+
export class LoginPage {
|
|
97
|
+
constructor(private page: Page) {}
|
|
98
|
+
|
|
99
|
+
async goto() {
|
|
100
|
+
await this.page.goto('https://example.com/login');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async login(email: string, password: string) {
|
|
104
|
+
await this.page.fill('input[name="email"]', email);
|
|
105
|
+
await this.page.fill('input[name="password"]', password);
|
|
106
|
+
await this.page.click('button[type="submit"]');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async getErrorMessage() {
|
|
110
|
+
const errorElement = this.page.locator('.error-message');
|
|
111
|
+
return errorElement.textContent();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async isLoginButtonDisabled() {
|
|
115
|
+
return this.page.locator('button[type="submit"]').isDisabled();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// tests/login.spec.ts
|
|
120
|
+
import { LoginPage } from '../pages/LoginPage';
|
|
121
|
+
|
|
122
|
+
test('login with POM', async ({ page }) => {
|
|
123
|
+
const loginPage = new LoginPage(page);
|
|
124
|
+
|
|
125
|
+
await loginPage.goto();
|
|
126
|
+
await loginPage.login('user@example.com', 'SecurePass123!');
|
|
127
|
+
|
|
128
|
+
await expect(page).toHaveURL('**/dashboard');
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 2. Cypress (Alternative for E2E)
|
|
133
|
+
|
|
134
|
+
**Why Cypress?**
|
|
135
|
+
- Developer-friendly API
|
|
136
|
+
- Real-time reloading
|
|
137
|
+
- Time-travel debugging
|
|
138
|
+
- Screenshot/video recording
|
|
139
|
+
- Stubbing and mocking built-in
|
|
140
|
+
|
|
141
|
+
#### Basic Test
|
|
142
|
+
|
|
143
|
+
```javascript
|
|
144
|
+
describe('User Authentication', () => {
|
|
145
|
+
it('should login with valid credentials', () => {
|
|
146
|
+
cy.visit('/login');
|
|
147
|
+
|
|
148
|
+
cy.get('input[name="email"]').type('user@example.com');
|
|
149
|
+
cy.get('input[name="password"]').type('SecurePass123!');
|
|
150
|
+
cy.get('button[type="submit"]').click();
|
|
151
|
+
|
|
152
|
+
cy.url().should('include', '/dashboard');
|
|
153
|
+
cy.get('h1').should('have.text', 'Welcome, User');
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('should show error with invalid credentials', () => {
|
|
157
|
+
cy.visit('/login');
|
|
158
|
+
|
|
159
|
+
cy.get('input[name="email"]').type('wrong@example.com');
|
|
160
|
+
cy.get('input[name="password"]').type('WrongPass');
|
|
161
|
+
cy.get('button[type="submit"]').click();
|
|
162
|
+
|
|
163
|
+
cy.get('.error-message')
|
|
164
|
+
.should('be.visible')
|
|
165
|
+
.and('have.text', 'Invalid credentials');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Custom Commands (Reusable Actions)
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
// cypress/support/commands.js
|
|
174
|
+
Cypress.Commands.add('login', (email, password) => {
|
|
175
|
+
cy.visit('/login');
|
|
176
|
+
cy.get('input[name="email"]').type(email);
|
|
177
|
+
cy.get('input[name="password"]').type(password);
|
|
178
|
+
cy.get('button[type="submit"]').click();
|
|
179
|
+
cy.url().should('include', '/dashboard');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Usage in tests
|
|
183
|
+
it('should display dashboard for logged-in user', () => {
|
|
184
|
+
cy.login('user@example.com', 'SecurePass123!');
|
|
185
|
+
cy.get('h1').should('have.text', 'Dashboard');
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### API Mocking with Intercept
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
it('should display mocked user data', () => {
|
|
193
|
+
cy.intercept('GET', '/api/user', {
|
|
194
|
+
statusCode: 200,
|
|
195
|
+
body: {
|
|
196
|
+
id: 1,
|
|
197
|
+
name: 'Mock User',
|
|
198
|
+
email: 'mock@example.com',
|
|
199
|
+
},
|
|
200
|
+
}).as('getUser');
|
|
201
|
+
|
|
202
|
+
cy.visit('/profile');
|
|
203
|
+
|
|
204
|
+
cy.wait('@getUser');
|
|
205
|
+
cy.get('.user-name').should('have.text', 'Mock User');
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 3. React Testing Library (Component Tests)
|
|
210
|
+
|
|
211
|
+
**Why Testing Library?**
|
|
212
|
+
- User-centric queries (accessibility-first)
|
|
213
|
+
- Encourages best practices (testing behavior, not implementation)
|
|
214
|
+
- Works with React, Vue, Svelte, Angular
|
|
215
|
+
|
|
216
|
+
#### Component Test Example
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
220
|
+
import { LoginForm } from './LoginForm';
|
|
221
|
+
|
|
222
|
+
describe('LoginForm', () => {
|
|
223
|
+
it('should render email and password inputs', () => {
|
|
224
|
+
render(<LoginForm />);
|
|
225
|
+
|
|
226
|
+
expect(screen.getByLabelText('Email')).toBeInTheDocument();
|
|
227
|
+
expect(screen.getByLabelText('Password')).toBeInTheDocument();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should call onSubmit with email and password', async () => {
|
|
231
|
+
const handleSubmit = vi.fn();
|
|
232
|
+
render(<LoginForm onSubmit={handleSubmit} />);
|
|
233
|
+
|
|
234
|
+
// Type into inputs
|
|
235
|
+
fireEvent.change(screen.getByLabelText('Email'), {
|
|
236
|
+
target: { value: 'user@example.com' },
|
|
237
|
+
});
|
|
238
|
+
fireEvent.change(screen.getByLabelText('Password'), {
|
|
239
|
+
target: { value: 'SecurePass123!' },
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Submit form
|
|
243
|
+
fireEvent.click(screen.getByRole('button', { name: /login/i }));
|
|
244
|
+
|
|
245
|
+
// Verify callback
|
|
246
|
+
expect(handleSubmit).toHaveBeenCalledWith({
|
|
247
|
+
email: 'user@example.com',
|
|
248
|
+
password: 'SecurePass123!',
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should show validation error for invalid email', async () => {
|
|
253
|
+
render(<LoginForm />);
|
|
254
|
+
|
|
255
|
+
fireEvent.change(screen.getByLabelText('Email'), {
|
|
256
|
+
target: { value: 'invalid-email' },
|
|
257
|
+
});
|
|
258
|
+
fireEvent.blur(screen.getByLabelText('Email'));
|
|
259
|
+
|
|
260
|
+
expect(await screen.findByText('Invalid email format')).toBeInTheDocument();
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### User-Centric Queries (Preferred)
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// ✅ GOOD: Accessible queries (user-facing)
|
|
269
|
+
screen.getByRole('button', { name: /submit/i });
|
|
270
|
+
screen.getByLabelText('Email');
|
|
271
|
+
screen.getByPlaceholderText('Enter your email');
|
|
272
|
+
screen.getByText('Welcome');
|
|
273
|
+
|
|
274
|
+
// ❌ BAD: Implementation-detail queries (fragile)
|
|
275
|
+
screen.getByClassName('btn-primary'); // Changes when CSS changes
|
|
276
|
+
screen.getByTestId('submit-button'); // Not user-facing
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## Test Strategies
|
|
280
|
+
|
|
281
|
+
### 1. Testing Pyramid
|
|
282
|
+
|
|
283
|
+
```
|
|
284
|
+
/\
|
|
285
|
+
/ \ E2E (10%)
|
|
286
|
+
/____\
|
|
287
|
+
/ \ Integration (30%)
|
|
288
|
+
/________\
|
|
289
|
+
/ \ Unit (60%)
|
|
290
|
+
/____________\
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
**Unit Tests** (60%):
|
|
294
|
+
- Individual components in isolation
|
|
295
|
+
- Fast, cheap, many tests
|
|
296
|
+
- Mock external dependencies
|
|
297
|
+
|
|
298
|
+
**Integration Tests** (30%):
|
|
299
|
+
- Multiple components working together
|
|
300
|
+
- API integration, data flow
|
|
301
|
+
- Moderate speed, moderate cost
|
|
302
|
+
|
|
303
|
+
**E2E Tests** (10%):
|
|
304
|
+
- Full user journeys (login → checkout)
|
|
305
|
+
- Slowest, most expensive
|
|
306
|
+
- Critical paths only
|
|
307
|
+
|
|
308
|
+
### 2. Test Coverage Strategy
|
|
309
|
+
|
|
310
|
+
**What to Test**:
|
|
311
|
+
- ✅ Happy paths (core user flows)
|
|
312
|
+
- ✅ Error states (validation, API failures)
|
|
313
|
+
- ✅ Edge cases (empty states, max limits)
|
|
314
|
+
- ✅ Accessibility (keyboard navigation, screen readers)
|
|
315
|
+
- ✅ Regression bugs (add test for each bug fix)
|
|
316
|
+
|
|
317
|
+
**What NOT to Test**:
|
|
318
|
+
- ❌ Third-party libraries (assume they work)
|
|
319
|
+
- ❌ Implementation details (internal state, CSS classes)
|
|
320
|
+
- ❌ Trivial code (getters, setters)
|
|
321
|
+
|
|
322
|
+
### 3. Flakiness Mitigation
|
|
323
|
+
|
|
324
|
+
**Common Causes of Flaky Tests**:
|
|
325
|
+
|
|
326
|
+
1. **Race Conditions**
|
|
327
|
+
|
|
328
|
+
❌ **Bad**:
|
|
329
|
+
```typescript
|
|
330
|
+
await page.click('button');
|
|
331
|
+
const text = await page.textContent('.result'); // May fail!
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
✅ **Good**:
|
|
335
|
+
```typescript
|
|
336
|
+
await page.click('button');
|
|
337
|
+
await page.waitForSelector('.result'); // Wait for element
|
|
338
|
+
const text = await page.textContent('.result');
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
2. **Non-Deterministic Data**
|
|
342
|
+
|
|
343
|
+
❌ **Bad**:
|
|
344
|
+
```typescript
|
|
345
|
+
expect(page.locator('.user')).toHaveCount(5); // Depends on database state
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
✅ **Good**:
|
|
349
|
+
```typescript
|
|
350
|
+
// Mock API to return deterministic data
|
|
351
|
+
await page.route('**/api/users', (route) =>
|
|
352
|
+
route.fulfill({
|
|
353
|
+
body: JSON.stringify([{ id: 1, name: 'User 1' }, { id: 2, name: 'User 2' }]),
|
|
354
|
+
})
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
expect(page.locator('.user')).toHaveCount(2); // Predictable
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
3. **Timing Issues**
|
|
361
|
+
|
|
362
|
+
❌ **Bad**:
|
|
363
|
+
```typescript
|
|
364
|
+
await page.waitForTimeout(3000); // Arbitrary wait
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
✅ **Good**:
|
|
368
|
+
```typescript
|
|
369
|
+
await page.waitForSelector('.loaded'); // Wait for specific condition
|
|
370
|
+
await page.waitForLoadState('networkidle'); // Wait for network idle
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
4. **Test Interdependence**
|
|
374
|
+
|
|
375
|
+
❌ **Bad**:
|
|
376
|
+
```typescript
|
|
377
|
+
test('create user', async () => {
|
|
378
|
+
// Creates user in DB
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test('login user', async () => {
|
|
382
|
+
// Depends on previous test creating user
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
✅ **Good**:
|
|
387
|
+
```typescript
|
|
388
|
+
test.beforeEach(async () => {
|
|
389
|
+
// Each test creates its own user
|
|
390
|
+
await createTestUser();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test.afterEach(async () => {
|
|
394
|
+
await cleanupTestUsers();
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Accessibility Testing
|
|
399
|
+
|
|
400
|
+
### 1. Automated Accessibility Tests (axe-core)
|
|
401
|
+
|
|
402
|
+
```typescript
|
|
403
|
+
import { test, expect } from '@playwright/test';
|
|
404
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
405
|
+
|
|
406
|
+
test('should have no accessibility violations', async ({ page }) => {
|
|
407
|
+
await page.goto('https://example.com');
|
|
408
|
+
|
|
409
|
+
const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
|
|
410
|
+
|
|
411
|
+
expect(accessibilityScanResults.violations).toEqual([]);
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 2. Keyboard Navigation
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
test('should navigate form with keyboard', async ({ page }) => {
|
|
419
|
+
await page.goto('/form');
|
|
420
|
+
|
|
421
|
+
// Tab through form fields
|
|
422
|
+
await page.keyboard.press('Tab');
|
|
423
|
+
await expect(page.locator('input[name="email"]')).toBeFocused();
|
|
424
|
+
|
|
425
|
+
await page.keyboard.press('Tab');
|
|
426
|
+
await expect(page.locator('input[name="password"]')).toBeFocused();
|
|
427
|
+
|
|
428
|
+
await page.keyboard.press('Tab');
|
|
429
|
+
await expect(page.locator('button[type="submit"]')).toBeFocused();
|
|
430
|
+
|
|
431
|
+
// Submit with Enter
|
|
432
|
+
await page.keyboard.press('Enter');
|
|
433
|
+
await expect(page).toHaveURL('**/dashboard');
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### 3. Screen Reader Testing (aria-label, roles)
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
test('should have proper ARIA labels', async ({ page }) => {
|
|
441
|
+
await page.goto('/login');
|
|
442
|
+
|
|
443
|
+
// Verify accessible names
|
|
444
|
+
await expect(page.getByRole('textbox', { name: 'Email' })).toBeVisible();
|
|
445
|
+
await expect(page.getByRole('textbox', { name: 'Password' })).toBeVisible();
|
|
446
|
+
await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
|
|
447
|
+
|
|
448
|
+
// Verify error announcements (aria-live)
|
|
449
|
+
await page.fill('input[name="email"]', 'invalid-email');
|
|
450
|
+
await page.click('button[type="submit"]');
|
|
451
|
+
|
|
452
|
+
const errorRegion = page.locator('[role="alert"]');
|
|
453
|
+
await expect(errorRegion).toHaveText('Invalid email format');
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## CI/CD Integration
|
|
458
|
+
|
|
459
|
+
### 1. GitHub Actions (Playwright)
|
|
460
|
+
|
|
461
|
+
```yaml
|
|
462
|
+
name: E2E Tests
|
|
463
|
+
|
|
464
|
+
on:
|
|
465
|
+
push:
|
|
466
|
+
branches: [main, develop]
|
|
467
|
+
pull_request:
|
|
468
|
+
|
|
469
|
+
jobs:
|
|
470
|
+
test:
|
|
471
|
+
runs-on: ubuntu-latest
|
|
472
|
+
steps:
|
|
473
|
+
- uses: actions/checkout@v3
|
|
474
|
+
- uses: actions/setup-node@v3
|
|
475
|
+
with:
|
|
476
|
+
node-version: 18
|
|
477
|
+
|
|
478
|
+
- name: Install dependencies
|
|
479
|
+
run: npm ci
|
|
480
|
+
|
|
481
|
+
- name: Install Playwright browsers
|
|
482
|
+
run: npx playwright install --with-deps
|
|
483
|
+
|
|
484
|
+
- name: Run Playwright tests
|
|
485
|
+
run: npx playwright test
|
|
486
|
+
|
|
487
|
+
- name: Upload test results
|
|
488
|
+
if: always()
|
|
489
|
+
uses: actions/upload-artifact@v3
|
|
490
|
+
with:
|
|
491
|
+
name: playwright-report
|
|
492
|
+
path: playwright-report/
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### 2. Parallel Execution
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
// playwright.config.ts
|
|
499
|
+
export default defineConfig({
|
|
500
|
+
workers: process.env.CI ? 2 : undefined, // Parallel in CI
|
|
501
|
+
fullyParallel: true,
|
|
502
|
+
retries: process.env.CI ? 2 : 0, // Retry flaky tests in CI
|
|
503
|
+
reporter: process.env.CI ? 'github' : 'html',
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### 3. Sharding (Large Test Suites)
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
# Split tests across 4 machines
|
|
511
|
+
npx playwright test --shard=1/4
|
|
512
|
+
npx playwright test --shard=2/4
|
|
513
|
+
npx playwright test --shard=3/4
|
|
514
|
+
npx playwright test --shard=4/4
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## Best Practices
|
|
518
|
+
|
|
519
|
+
### 1. Use Data Attributes for Stable Selectors
|
|
520
|
+
|
|
521
|
+
```html
|
|
522
|
+
<!-- ✅ GOOD: Stable selector -->
|
|
523
|
+
<button data-testid="submit-button">Submit</button>
|
|
524
|
+
|
|
525
|
+
<!-- ❌ BAD: Fragile selectors -->
|
|
526
|
+
<button class="btn btn-primary">Submit</button> <!-- CSS changes break tests -->
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
// Test
|
|
531
|
+
await page.click('[data-testid="submit-button"]');
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### 2. Test User Behavior, Not Implementation
|
|
535
|
+
|
|
536
|
+
❌ **Bad**:
|
|
537
|
+
```typescript
|
|
538
|
+
// Testing internal state
|
|
539
|
+
expect(component.state.isLoading).toBe(true);
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
✅ **Good**:
|
|
543
|
+
```typescript
|
|
544
|
+
// Testing visible UI
|
|
545
|
+
expect(screen.getByText('Loading...')).toBeInTheDocument();
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### 3. Keep Tests Independent
|
|
549
|
+
|
|
550
|
+
```typescript
|
|
551
|
+
// ✅ GOOD: Each test is independent
|
|
552
|
+
test.beforeEach(async ({ page }) => {
|
|
553
|
+
await page.goto('/');
|
|
554
|
+
await login(page, 'user@example.com', 'password');
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test('test 1', async ({ page }) => {
|
|
558
|
+
// Fresh state
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test('test 2', async ({ page }) => {
|
|
562
|
+
// Fresh state
|
|
563
|
+
});
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### 4. Use Meaningful Assertions
|
|
567
|
+
|
|
568
|
+
❌ **Bad**:
|
|
569
|
+
```typescript
|
|
570
|
+
expect(true).toBe(true); // Useless assertion
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
✅ **Good**:
|
|
574
|
+
```typescript
|
|
575
|
+
await expect(page.locator('.success-message')).toHaveText(
|
|
576
|
+
'Order placed successfully'
|
|
577
|
+
);
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
### 5. Avoid Hard-Coded Waits
|
|
581
|
+
|
|
582
|
+
❌ **Bad**:
|
|
583
|
+
```typescript
|
|
584
|
+
await page.waitForTimeout(5000); // Slow, brittle
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
✅ **Good**:
|
|
588
|
+
```typescript
|
|
589
|
+
await page.waitForSelector('.results'); // Wait for specific element
|
|
590
|
+
await expect(page.locator('.results')).toBeVisible(); // Built-in wait
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
## Debugging Tests
|
|
594
|
+
|
|
595
|
+
### 1. Headed Mode (See Browser)
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
npx playwright test --headed
|
|
599
|
+
npx playwright test --headed --debug # Pause on each step
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
### 2. Screenshot on Failure
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
test.afterEach(async ({ page }, testInfo) => {
|
|
606
|
+
if (testInfo.status !== 'passed') {
|
|
607
|
+
await page.screenshot({ path: `failure-${testInfo.title}.png` });
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### 3. Trace Viewer (Time-Travel Debugging)
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
// playwright.config.ts
|
|
616
|
+
export default defineConfig({
|
|
617
|
+
use: {
|
|
618
|
+
trace: 'on-first-retry', // Record trace on retry
|
|
619
|
+
},
|
|
620
|
+
});
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
# View trace
|
|
625
|
+
npx playwright show-trace trace.zip
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### 4. Console Logs
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
page.on('console', (msg) => console.log('Browser log:', msg.text()));
|
|
632
|
+
page.on('pageerror', (error) => console.error('Page error:', error));
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
## Common Patterns
|
|
636
|
+
|
|
637
|
+
### 1. Testing Forms
|
|
638
|
+
|
|
639
|
+
```typescript
|
|
640
|
+
test('should validate form fields', async ({ page }) => {
|
|
641
|
+
await page.goto('/form');
|
|
642
|
+
|
|
643
|
+
// Empty submission (validation)
|
|
644
|
+
await page.click('button[type="submit"]');
|
|
645
|
+
await expect(page.locator('.email-error')).toHaveText('Email is required');
|
|
646
|
+
|
|
647
|
+
// Invalid email
|
|
648
|
+
await page.fill('input[name="email"]', 'invalid');
|
|
649
|
+
await page.click('button[type="submit"]');
|
|
650
|
+
await expect(page.locator('.email-error')).toHaveText('Invalid email format');
|
|
651
|
+
|
|
652
|
+
// Valid submission
|
|
653
|
+
await page.fill('input[name="email"]', 'user@example.com');
|
|
654
|
+
await page.fill('input[name="password"]', 'SecurePass123!');
|
|
655
|
+
await page.click('button[type="submit"]');
|
|
656
|
+
await expect(page).toHaveURL('**/success');
|
|
657
|
+
});
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
### 2. Testing Modals
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
test('should open and close modal', async ({ page }) => {
|
|
664
|
+
await page.goto('/');
|
|
665
|
+
|
|
666
|
+
// Open modal
|
|
667
|
+
await page.click('[data-testid="open-modal"]');
|
|
668
|
+
await expect(page.locator('.modal')).toBeVisible();
|
|
669
|
+
|
|
670
|
+
// Close with X button
|
|
671
|
+
await page.click('.modal .close-button');
|
|
672
|
+
await expect(page.locator('.modal')).not.toBeVisible();
|
|
673
|
+
|
|
674
|
+
// Open again, close with Escape
|
|
675
|
+
await page.click('[data-testid="open-modal"]');
|
|
676
|
+
await page.keyboard.press('Escape');
|
|
677
|
+
await expect(page.locator('.modal')).not.toBeVisible();
|
|
678
|
+
});
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
### 3. Testing Drag and Drop
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
test('should drag and drop items', async ({ page }) => {
|
|
685
|
+
await page.goto('/kanban');
|
|
686
|
+
|
|
687
|
+
const todoItem = page.locator('[data-testid="item-1"]');
|
|
688
|
+
const doneColumn = page.locator('[data-testid="column-done"]');
|
|
689
|
+
|
|
690
|
+
// Drag item from TODO to DONE
|
|
691
|
+
await todoItem.dragTo(doneColumn);
|
|
692
|
+
|
|
693
|
+
// Verify item moved
|
|
694
|
+
await expect(doneColumn.locator('[data-testid="item-1"]')).toBeVisible();
|
|
695
|
+
});
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
## Resources
|
|
699
|
+
|
|
700
|
+
- [Playwright Documentation](https://playwright.dev/)
|
|
701
|
+
- [Cypress Documentation](https://docs.cypress.io/)
|
|
702
|
+
- [Testing Library](https://testing-library.com/)
|
|
703
|
+
- [Web Content Accessibility Guidelines (WCAG)](https://www.w3.org/WAI/WCAG21/quickref/)
|
|
704
|
+
|
|
705
|
+
## Activation Keywords
|
|
706
|
+
|
|
707
|
+
Ask me about:
|
|
708
|
+
- "How to write E2E tests with Playwright"
|
|
709
|
+
- "Cypress test examples"
|
|
710
|
+
- "React Testing Library best practices"
|
|
711
|
+
- "Page Object Model for UI tests"
|
|
712
|
+
- "Accessibility testing with axe-core"
|
|
713
|
+
- "How to fix flaky tests"
|
|
714
|
+
- "CI/CD integration for UI tests"
|
|
715
|
+
- "Debugging Playwright tests"
|
|
716
|
+
- "Test automation strategies"
|