digiqagent 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +29 -0
- package/README.md +197 -0
- package/agents/code-reviewer.md +48 -0
- package/bin/cli.js +210 -0
- package/package.json +41 -0
- package/skills/playwright-test-generator/SKILL.md +563 -0
- package/skills/playwright-test-generator/interaction-test-patterns.md +987 -0
- package/skills/playwright-test-generator/page-object-patterns.md +833 -0
- package/skills/postman-collection-generator/SKILL.md +310 -0
- package/skills/postman-collection-generator/collection-patterns.md +493 -0
- package/skills/postman-test-suite-generator/SKILL.md +653 -0
- package/skills/postman-test-suite-generator/test-scenario-patterns.md +612 -0
- package/skills/receiving-code-review/SKILL.md +213 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +146 -0
- package/skills/review-prompts/code-quality-reviewer-prompt.md +26 -0
- package/skills/review-prompts/spec-reviewer-prompt.md +61 -0
- package/skills/swagger-generator/SKILL.md +238 -0
- package/skills/swagger-generator/openapi-patterns.md +667 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +296 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +371 -0
- package/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/skills/verification-before-completion/SKILL.md +139 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: playwright-test-generator
|
|
3
|
+
description: Use when generating Playwright end-to-end test scripts for Angular or React frontend applications by analyzing components, click events, forms, routing, modals, and user interactions
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Playwright Test Generator
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Analyze a frontend codebase — Angular or React — and produce a complete suite of Playwright end-to-end tests using the Page Object Model pattern. Tests cover every route, every form, every click handler, every modal, and every interactive element with both positive and negative scenarios.
|
|
11
|
+
|
|
12
|
+
**Core principle:** If a user can interact with it, there must be a test for it. If it can fail, there must be a test proving the failure is handled.
|
|
13
|
+
|
|
14
|
+
## When to Use
|
|
15
|
+
|
|
16
|
+
- Frontend project needs E2E test coverage
|
|
17
|
+
- New Angular or React application with no Playwright tests
|
|
18
|
+
- Existing application with incomplete or outdated E2E tests
|
|
19
|
+
- QA handoff — provide runnable Playwright scripts
|
|
20
|
+
- Pre-release verification — structured UI test pass
|
|
21
|
+
|
|
22
|
+
## Step 0 — Detect Project Type
|
|
23
|
+
|
|
24
|
+
**This step is mandatory. Do it first, before anything else.**
|
|
25
|
+
|
|
26
|
+
Examine the project root to determine the frontend framework:
|
|
27
|
+
|
|
28
|
+
| Signal | Framework |
|
|
29
|
+
|--------|-----------|
|
|
30
|
+
| `angular.json` or `nx.json` with Angular projects | Angular |
|
|
31
|
+
| `@angular/core` in `package.json` dependencies | Angular |
|
|
32
|
+
| `react` and `react-dom` in `package.json` dependencies | React |
|
|
33
|
+
| `next` in `package.json` dependencies | React (Next.js) |
|
|
34
|
+
| `gatsby` in `package.json` dependencies | React (Gatsby) |
|
|
35
|
+
| `.jsx` / `.tsx` files containing JSX syntax | React |
|
|
36
|
+
| `*.component.ts` + `*.component.html` file pairs | Angular |
|
|
37
|
+
|
|
38
|
+
Also detect the **component library** in use — this determines selectors:
|
|
39
|
+
|
|
40
|
+
| Library | Framework | Selector Patterns |
|
|
41
|
+
|---------|-----------|-------------------|
|
|
42
|
+
| `@angular/material` | Angular | `mat-input`, `mat-select`, `mat-dialog-container`, `mat-table`, `mat-paginator`, `mat-tab-group` |
|
|
43
|
+
| `ng-zorro-antd` | Angular | `nz-input`, `nz-select`, `nz-modal`, `nz-table`, `nz-pagination` |
|
|
44
|
+
| `primeng` | Angular | `p-inputtext`, `p-dropdown`, `p-dialog`, `p-table`, `p-paginator` |
|
|
45
|
+
| `@mui/material` | React | `MuiTextField`, `MuiSelect`, `MuiDialog`, `MuiDataGrid`, `MuiPagination` |
|
|
46
|
+
| `antd` | React | `ant-input`, `ant-select`, `ant-modal`, `ant-table`, `ant-pagination` |
|
|
47
|
+
| `@chakra-ui/react` | React | Chakra uses standard HTML with `data-*` attrs |
|
|
48
|
+
| None / custom | Either | Standard HTML elements |
|
|
49
|
+
|
|
50
|
+
**If the project is not Angular or React, STOP.** Inform the developer this skill supports Angular and React only. Suggest alternatives for Vue, Svelte, etc.
|
|
51
|
+
|
|
52
|
+
## Step 1 — Map Pages and Routes
|
|
53
|
+
|
|
54
|
+
Build a complete sitemap from the routing configuration.
|
|
55
|
+
|
|
56
|
+
### Angular
|
|
57
|
+
|
|
58
|
+
Scan for routing modules:
|
|
59
|
+
- `app-routing.module.ts` or `app.routes.ts` (standalone)
|
|
60
|
+
- Lazy-loaded feature routing modules
|
|
61
|
+
- `RouterModule.forRoot(...)` and `RouterModule.forChild(...)`
|
|
62
|
+
- Route guards (`canActivate`, `canDeactivate`) — note which routes are protected
|
|
63
|
+
|
|
64
|
+
Extract:
|
|
65
|
+
```
|
|
66
|
+
/ → HomeComponent (public)
|
|
67
|
+
/login → LoginComponent (public)
|
|
68
|
+
/dashboard → DashboardComponent (auth guard)
|
|
69
|
+
/users → UserListComponent (auth guard)
|
|
70
|
+
/users/:id → UserDetailComponent (auth guard)
|
|
71
|
+
/users/new → UserFormComponent (auth + role guard)
|
|
72
|
+
/settings → SettingsComponent (auth guard)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### React
|
|
76
|
+
|
|
77
|
+
Scan for route definitions:
|
|
78
|
+
- `<Route>` elements in React Router (`react-router-dom`)
|
|
79
|
+
- `<BrowserRouter>`, `<Routes>`, `<Route>`
|
|
80
|
+
- Next.js: `app/` directory structure (App Router) or `pages/` directory (Pages Router)
|
|
81
|
+
- Protected route wrapper components
|
|
82
|
+
- `useNavigate`, `useLocation`, `useParams` usage
|
|
83
|
+
|
|
84
|
+
Extract the same route → component → guard mapping.
|
|
85
|
+
|
|
86
|
+
## Step 2 — Discover Components and Interactions
|
|
87
|
+
|
|
88
|
+
For each page/route, scan its component tree and catalog every interactive element.
|
|
89
|
+
|
|
90
|
+
### Angular Component Scanning
|
|
91
|
+
|
|
92
|
+
Scan `*.component.html` templates and `*.component.ts` class files:
|
|
93
|
+
|
|
94
|
+
| Template Pattern | Interaction Type | Test Action |
|
|
95
|
+
|-----------------|-----------------|-------------|
|
|
96
|
+
| `(click)="handler()"` | Click event | `click()` |
|
|
97
|
+
| `(dblclick)="handler()"` | Double-click | `dblclick()` |
|
|
98
|
+
| `(submit)="handler()"` | Form submission | Fill + submit |
|
|
99
|
+
| `[(ngModel)]="field"` | Two-way binding input | `fill()` |
|
|
100
|
+
| `[formGroup]` / `formControlName` | Reactive form field | `fill()` + validation |
|
|
101
|
+
| `routerLink="/path"` | Navigation link | `click()` + assert URL |
|
|
102
|
+
| `[routerLink]="['/path', id]"` | Dynamic navigation | `click()` + assert URL |
|
|
103
|
+
| `*ngIf="condition"` | Conditional display | Assert visible/hidden |
|
|
104
|
+
| `*ngFor="let item of items"` | List rendering | Assert count, content |
|
|
105
|
+
| `<mat-dialog>` / `MatDialog.open()` | Modal dialog | Open, verify, close |
|
|
106
|
+
| `<mat-menu>` | Dropdown menu | Open, select, close |
|
|
107
|
+
| `<mat-tab-group>` | Tab navigation | Click tabs, verify content |
|
|
108
|
+
| `<mat-table>` | Data table | Sort, paginate, row actions |
|
|
109
|
+
| `<mat-select>` | Select dropdown | Open, pick option |
|
|
110
|
+
| `<mat-checkbox>` / `<mat-radio>` | Toggle inputs | Check/uncheck |
|
|
111
|
+
| `<mat-datepicker>` | Date picker | Open, select date |
|
|
112
|
+
| `<input type="file">` | File upload | Set input files |
|
|
113
|
+
| `<mat-snack-bar>` / toast | Notification | Assert appears + content |
|
|
114
|
+
|
|
115
|
+
### React Component Scanning
|
|
116
|
+
|
|
117
|
+
Scan `*.tsx` / `*.jsx` files:
|
|
118
|
+
|
|
119
|
+
| Code Pattern | Interaction Type | Test Action |
|
|
120
|
+
|-------------|-----------------|-------------|
|
|
121
|
+
| `onClick={handler}` | Click event | `click()` |
|
|
122
|
+
| `onDoubleClick={handler}` | Double-click | `dblclick()` |
|
|
123
|
+
| `onSubmit={handler}` | Form submission | Fill + submit |
|
|
124
|
+
| `onChange={handler}` on `<input>` | Input change | `fill()` |
|
|
125
|
+
| `useForm()` / `<Formik>` / `react-hook-form` | Form with validation | Fill + validate |
|
|
126
|
+
| `<Link to="/path">` | Navigation link | `click()` + assert URL |
|
|
127
|
+
| `useNavigate()` | Programmatic navigation | Trigger + assert URL |
|
|
128
|
+
| `useState` controlling modal/drawer | Modal/drawer toggle | Open, verify, close |
|
|
129
|
+
| `<Dialog>` / `<Modal>` | Modal dialog | Open, verify, close |
|
|
130
|
+
| `<Drawer>` | Side drawer | Open, verify, close |
|
|
131
|
+
| `<Tabs>` / `<Tab>` | Tab navigation | Click tabs, verify content |
|
|
132
|
+
| `<Select>` / `<Autocomplete>` | Select dropdown | Open, pick option |
|
|
133
|
+
| `<Checkbox>` / `<Switch>` | Toggle inputs | Check/uncheck |
|
|
134
|
+
| `<DataGrid>` / `<Table>` | Data table | Sort, paginate, row actions |
|
|
135
|
+
| `<input type="file">` | File upload | Set input files |
|
|
136
|
+
| `<Snackbar>` / `toast()` | Notification | Assert appears + content |
|
|
137
|
+
| `{condition && <Component>}` | Conditional render | Assert visible/hidden |
|
|
138
|
+
| `{items.map(item => ...)}` | List rendering | Assert count, content |
|
|
139
|
+
|
|
140
|
+
### Interaction Catalog Output
|
|
141
|
+
|
|
142
|
+
For each component, produce a structured list:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
Page: /users
|
|
146
|
+
Component: UserListComponent
|
|
147
|
+
Interactions:
|
|
148
|
+
- CLICK: "Add User" button → navigates to /users/new
|
|
149
|
+
- CLICK: Edit icon per row → navigates to /users/:id
|
|
150
|
+
- CLICK: Delete icon per row → opens confirm dialog
|
|
151
|
+
- TABLE: sortable columns (Name, Email, Created)
|
|
152
|
+
- TABLE: pagination (page size selector, next/prev)
|
|
153
|
+
- SEARCH: filter input → filters table rows
|
|
154
|
+
- MODAL: delete confirmation → confirm/cancel
|
|
155
|
+
- EMPTY STATE: "No users found" when list empty
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Step 3 — Build Test Plan Per Page
|
|
159
|
+
|
|
160
|
+
For each page, define positive and negative scenarios.
|
|
161
|
+
|
|
162
|
+
### Positive Scenarios (Happy Path)
|
|
163
|
+
|
|
164
|
+
Every page must have:
|
|
165
|
+
- **Page load** — correct URL, title, key elements visible
|
|
166
|
+
- **Content display** — expected text, images, data rendered
|
|
167
|
+
- **Navigation** — links lead to correct destinations
|
|
168
|
+
- **Forms** — valid data submission succeeds, success feedback shown
|
|
169
|
+
- **Tables** — data displayed, sorting works, pagination works
|
|
170
|
+
- **Modals** — open with correct content, actions execute, close properly
|
|
171
|
+
- **Search/filter** — produces expected filtered results
|
|
172
|
+
|
|
173
|
+
### Negative Scenarios (Error/Edge Cases)
|
|
174
|
+
|
|
175
|
+
Every page with forms must have:
|
|
176
|
+
- **Empty required fields** — submit with each required field blank, one at a time, verify field-specific error
|
|
177
|
+
- **Invalid format** — wrong email format, phone format, date format
|
|
178
|
+
- **Boundary values** — too short (min length), too long (max length), out of range numbers
|
|
179
|
+
- **Special characters** — SQL injection attempts, XSS strings, Unicode edge cases
|
|
180
|
+
- **Double submit** — rapid double-click on submit button doesn't create duplicates
|
|
181
|
+
|
|
182
|
+
Every page with auth requirements must have:
|
|
183
|
+
- **Unauthenticated access** — redirect to login
|
|
184
|
+
- **Insufficient permissions** — forbidden message or hidden elements
|
|
185
|
+
|
|
186
|
+
Every page with data must have:
|
|
187
|
+
- **Empty state** — no data displays appropriate message/illustration
|
|
188
|
+
- **Error state** — API failure shows error message, retry option
|
|
189
|
+
- **Loading state** — skeleton/spinner during data fetch
|
|
190
|
+
|
|
191
|
+
## Step 4 — Generate Playwright Test Files
|
|
192
|
+
|
|
193
|
+
### File Structure
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
e2e/
|
|
197
|
+
playwright.config.ts
|
|
198
|
+
pages/
|
|
199
|
+
base.page.ts
|
|
200
|
+
login.page.ts
|
|
201
|
+
dashboard.page.ts
|
|
202
|
+
user-list.page.ts
|
|
203
|
+
user-form.page.ts
|
|
204
|
+
tests/
|
|
205
|
+
auth/
|
|
206
|
+
login.spec.ts
|
|
207
|
+
logout.spec.ts
|
|
208
|
+
users/
|
|
209
|
+
user-list.spec.ts
|
|
210
|
+
user-form.spec.ts
|
|
211
|
+
user-delete.spec.ts
|
|
212
|
+
navigation/
|
|
213
|
+
routing.spec.ts
|
|
214
|
+
protected-routes.spec.ts
|
|
215
|
+
responsive/
|
|
216
|
+
mobile-layout.spec.ts
|
|
217
|
+
tablet-layout.spec.ts
|
|
218
|
+
accessibility/
|
|
219
|
+
keyboard-navigation.spec.ts
|
|
220
|
+
aria-labels.spec.ts
|
|
221
|
+
fixtures/
|
|
222
|
+
test-data.ts
|
|
223
|
+
auth.setup.ts
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Naming Conventions
|
|
227
|
+
|
|
228
|
+
| Element | Convention | Example |
|
|
229
|
+
|---------|-----------|---------|
|
|
230
|
+
| Test files | `kebab-case.spec.ts` | `user-list.spec.ts` |
|
|
231
|
+
| Page objects | `kebab-case.page.ts` | `user-list.page.ts` |
|
|
232
|
+
| POM class | PascalCase + `Page` | `UserListPage` |
|
|
233
|
+
| Test describe | Feature name | `'User List'` |
|
|
234
|
+
| Test name | Action + expected result | `'should display validation error when email is empty'` |
|
|
235
|
+
| Fixtures | `kebab-case.ts` | `test-data.ts` |
|
|
236
|
+
|
|
237
|
+
### Locator Strategy
|
|
238
|
+
|
|
239
|
+
Use locators in this priority order. Never skip to CSS selectors without trying higher-priority options:
|
|
240
|
+
|
|
241
|
+
| Priority | Method | When to Use | Example |
|
|
242
|
+
|----------|--------|-------------|---------|
|
|
243
|
+
| 1 | `data-testid` | Best — stable, decoupled from UI | `page.getByTestId('submit-btn')` |
|
|
244
|
+
| 2 | Role + name | Accessible elements | `page.getByRole('button', { name: 'Submit' })` |
|
|
245
|
+
| 3 | Label | Form inputs | `page.getByLabel('Email')` |
|
|
246
|
+
| 4 | Placeholder | Inputs without labels | `page.getByPlaceholder('Enter email')` |
|
|
247
|
+
| 5 | Text | Static text content | `page.getByText('Welcome back')` |
|
|
248
|
+
| 6 | CSS selector | Last resort | `page.locator('.custom-widget')` |
|
|
249
|
+
|
|
250
|
+
**If the codebase lacks `data-testid` attributes**, note this in the output and recommend adding them. Generate tests using role/label/text locators, but include a comment suggesting `data-testid` for stability.
|
|
251
|
+
|
|
252
|
+
### Positive Test Example
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { test, expect } from '@playwright/test';
|
|
256
|
+
import { LoginPage } from '../pages/login.page';
|
|
257
|
+
|
|
258
|
+
test.describe('Login', () => {
|
|
259
|
+
let loginPage: LoginPage;
|
|
260
|
+
|
|
261
|
+
test.beforeEach(async ({ page }) => {
|
|
262
|
+
loginPage = new LoginPage(page);
|
|
263
|
+
await loginPage.goto();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test('should login with valid credentials', async ({ page }) => {
|
|
267
|
+
await loginPage.fillEmail('admin@example.com');
|
|
268
|
+
await loginPage.fillPassword('SecureP@ss123');
|
|
269
|
+
await loginPage.clickSubmit();
|
|
270
|
+
|
|
271
|
+
await expect(page).toHaveURL(/\/dashboard/);
|
|
272
|
+
await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('should display welcome message after login', async ({ page }) => {
|
|
276
|
+
await loginPage.login('admin@example.com', 'SecureP@ss123');
|
|
277
|
+
await expect(page.getByText(/welcome/i)).toBeVisible();
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Negative Test Example
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
test.describe('Login - Validation', () => {
|
|
286
|
+
test('should show error when email is empty', async ({ page }) => {
|
|
287
|
+
const loginPage = new LoginPage(page);
|
|
288
|
+
await loginPage.goto();
|
|
289
|
+
await loginPage.fillPassword('SecureP@ss123');
|
|
290
|
+
await loginPage.clickSubmit();
|
|
291
|
+
|
|
292
|
+
await expect(loginPage.emailError).toBeVisible();
|
|
293
|
+
await expect(loginPage.emailError).toContainText(/required|email/i);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test('should show error for invalid email format', async ({ page }) => {
|
|
297
|
+
const loginPage = new LoginPage(page);
|
|
298
|
+
await loginPage.goto();
|
|
299
|
+
await loginPage.fillEmail('not-an-email');
|
|
300
|
+
await loginPage.fillPassword('SecureP@ss123');
|
|
301
|
+
await loginPage.clickSubmit();
|
|
302
|
+
|
|
303
|
+
await expect(loginPage.emailError).toBeVisible();
|
|
304
|
+
await expect(loginPage.emailError).toContainText(/valid|format|email/i);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('should show error for incorrect credentials', async ({ page }) => {
|
|
308
|
+
const loginPage = new LoginPage(page);
|
|
309
|
+
await loginPage.goto();
|
|
310
|
+
await loginPage.login('wrong@example.com', 'WrongPass123');
|
|
311
|
+
|
|
312
|
+
await expect(loginPage.generalError).toBeVisible();
|
|
313
|
+
await expect(loginPage.generalError).toContainText(/invalid|incorrect|failed/i);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('should not submit form when required fields are empty', async ({ page }) => {
|
|
317
|
+
const loginPage = new LoginPage(page);
|
|
318
|
+
await loginPage.goto();
|
|
319
|
+
await loginPage.clickSubmit();
|
|
320
|
+
|
|
321
|
+
await expect(page).toHaveURL(/\/login/);
|
|
322
|
+
await expect(loginPage.emailError).toBeVisible();
|
|
323
|
+
await expect(loginPage.passwordError).toBeVisible();
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Responsive Test Example
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import { test, expect, devices } from '@playwright/test';
|
|
332
|
+
|
|
333
|
+
test.describe('Responsive - Mobile', () => {
|
|
334
|
+
test.use({ ...devices['iPhone 13'] });
|
|
335
|
+
|
|
336
|
+
test('should show hamburger menu on mobile', async ({ page }) => {
|
|
337
|
+
await page.goto('/');
|
|
338
|
+
await expect(page.getByRole('button', { name: /menu/i })).toBeVisible();
|
|
339
|
+
await expect(page.getByRole('navigation')).not.toBeVisible();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
test('should open sidebar on hamburger click', async ({ page }) => {
|
|
343
|
+
await page.goto('/');
|
|
344
|
+
await page.getByRole('button', { name: /menu/i }).click();
|
|
345
|
+
await expect(page.getByRole('navigation')).toBeVisible();
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
test.describe('Responsive - Desktop', () => {
|
|
350
|
+
test.use({ viewport: { width: 1440, height: 900 } });
|
|
351
|
+
|
|
352
|
+
test('should show sidebar navigation on desktop', async ({ page }) => {
|
|
353
|
+
await page.goto('/dashboard');
|
|
354
|
+
await expect(page.getByRole('navigation')).toBeVisible();
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Accessibility Test Example
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { test, expect } from '@playwright/test';
|
|
363
|
+
import AxeBuilder from '@axe-core/playwright';
|
|
364
|
+
|
|
365
|
+
test.describe('Accessibility', () => {
|
|
366
|
+
test('login page has no accessibility violations', async ({ page }) => {
|
|
367
|
+
await page.goto('/login');
|
|
368
|
+
const results = await new AxeBuilder({ page }).analyze();
|
|
369
|
+
expect(results.violations).toEqual([]);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
test('form inputs are keyboard navigable', async ({ page }) => {
|
|
373
|
+
await page.goto('/login');
|
|
374
|
+
await page.keyboard.press('Tab');
|
|
375
|
+
await expect(page.getByLabel('Email')).toBeFocused();
|
|
376
|
+
await page.keyboard.press('Tab');
|
|
377
|
+
await expect(page.getByLabel('Password')).toBeFocused();
|
|
378
|
+
await page.keyboard.press('Tab');
|
|
379
|
+
await expect(page.getByRole('button', { name: /submit|login/i })).toBeFocused();
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test('modal can be closed with Escape key', async ({ page }) => {
|
|
383
|
+
await page.goto('/users');
|
|
384
|
+
await page.getByRole('button', { name: /delete/i }).first().click();
|
|
385
|
+
await expect(page.getByRole('dialog')).toBeVisible();
|
|
386
|
+
await page.keyboard.press('Escape');
|
|
387
|
+
await expect(page.getByRole('dialog')).not.toBeVisible();
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Step 5 — Generate Page Objects
|
|
393
|
+
|
|
394
|
+
Create a Page Object Model class for every page. See @page-object-patterns.md for complete patterns.
|
|
395
|
+
|
|
396
|
+
### Base Page
|
|
397
|
+
|
|
398
|
+
Every POM extends a base class:
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { Page, Locator } from '@playwright/test';
|
|
402
|
+
|
|
403
|
+
export class BasePage {
|
|
404
|
+
constructor(protected page: Page) {}
|
|
405
|
+
|
|
406
|
+
async goto(path: string) {
|
|
407
|
+
await this.page.goto(path);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async getTitle(): Promise<string> {
|
|
411
|
+
return this.page.title();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
get loadingSpinner(): Locator {
|
|
415
|
+
return this.page.getByRole('progressbar');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async waitForLoad() {
|
|
419
|
+
await this.loadingSpinner.waitFor({ state: 'hidden', timeout: 10000 });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
get toastMessage(): Locator {
|
|
423
|
+
return this.page.getByRole('alert');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Page Object Rules
|
|
429
|
+
|
|
430
|
+
- One POM per page/route
|
|
431
|
+
- Locators as getter properties (not methods)
|
|
432
|
+
- Actions as async methods (fill, click, submit)
|
|
433
|
+
- Assertions stay in test files, not in POMs
|
|
434
|
+
- Composite actions for common flows (e.g., `login(email, password)`)
|
|
435
|
+
- No test logic in POMs — they are pure page interaction wrappers
|
|
436
|
+
|
|
437
|
+
## Step 6 — Generate Playwright Config
|
|
438
|
+
|
|
439
|
+
If `playwright.config.ts` does not already exist, generate one:
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
443
|
+
|
|
444
|
+
export default defineConfig({
|
|
445
|
+
testDir: './e2e/tests',
|
|
446
|
+
fullyParallel: true,
|
|
447
|
+
forbidOnly: !!process.env.CI,
|
|
448
|
+
retries: process.env.CI ? 2 : 0,
|
|
449
|
+
workers: process.env.CI ? 1 : undefined,
|
|
450
|
+
reporter: [
|
|
451
|
+
['html', { open: 'never' }],
|
|
452
|
+
['list']
|
|
453
|
+
],
|
|
454
|
+
use: {
|
|
455
|
+
baseURL: 'http://localhost:4200', // Angular default; use 3000 for React
|
|
456
|
+
trace: 'on-first-retry',
|
|
457
|
+
screenshot: 'only-on-failure',
|
|
458
|
+
video: 'retain-on-failure',
|
|
459
|
+
},
|
|
460
|
+
projects: [
|
|
461
|
+
{
|
|
462
|
+
name: 'chromium',
|
|
463
|
+
use: { ...devices['Desktop Chrome'] },
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: 'firefox',
|
|
467
|
+
use: { ...devices['Desktop Firefox'] },
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
name: 'webkit',
|
|
471
|
+
use: { ...devices['Desktop Safari'] },
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: 'mobile-chrome',
|
|
475
|
+
use: { ...devices['Pixel 5'] },
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
name: 'mobile-safari',
|
|
479
|
+
use: { ...devices['iPhone 13'] },
|
|
480
|
+
},
|
|
481
|
+
],
|
|
482
|
+
webServer: {
|
|
483
|
+
command: 'npm start', // Adjust per project
|
|
484
|
+
url: 'http://localhost:4200',
|
|
485
|
+
reuseExistingServer: !process.env.CI,
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
Adjust `baseURL`, `webServer.command`, and `webServer.url` based on the detected framework:
|
|
491
|
+
- **Angular:** port `4200`, command `ng serve`
|
|
492
|
+
- **React (CRA):** port `3000`, command `react-scripts start`
|
|
493
|
+
- **React (Vite):** port `5173`, command `vite`
|
|
494
|
+
- **Next.js:** port `3000`, command `next dev`
|
|
495
|
+
|
|
496
|
+
## Step 7 — Verify
|
|
497
|
+
|
|
498
|
+
Before claiming test generation is complete:
|
|
499
|
+
|
|
500
|
+
### Coverage Verification
|
|
501
|
+
|
|
502
|
+
- [ ] Every route from the router config has at least one test file
|
|
503
|
+
- [ ] Every form has positive tests (valid submission) and negative tests (each validation rule)
|
|
504
|
+
- [ ] Every click handler has a test verifying the expected outcome
|
|
505
|
+
- [ ] Every modal/dialog has open, content verification, and close tests (button, overlay, Escape)
|
|
506
|
+
- [ ] Every table has sort, paginate, and empty state tests
|
|
507
|
+
- [ ] Every dropdown/select has open, select option, and close tests
|
|
508
|
+
- [ ] Auth-protected routes have unauthenticated redirect tests
|
|
509
|
+
- [ ] Role-based features have permission tests
|
|
510
|
+
|
|
511
|
+
### Quality Verification
|
|
512
|
+
|
|
513
|
+
- [ ] No hardcoded `waitForTimeout()` calls — use proper Playwright auto-waiting
|
|
514
|
+
- [ ] No flaky CSS-only selectors when role/label/testid alternatives exist
|
|
515
|
+
- [ ] Every `test()` has at least one `expect()` assertion
|
|
516
|
+
- [ ] Page objects cover all page interactions (no raw locators in test files)
|
|
517
|
+
- [ ] Test names describe the scenario: `should [action] when [condition]`
|
|
518
|
+
- [ ] Test data uses realistic values, not `test` or `foo`
|
|
519
|
+
|
|
520
|
+
### Structural Verification
|
|
521
|
+
|
|
522
|
+
- [ ] `playwright.config.ts` generated with correct baseURL and webServer
|
|
523
|
+
- [ ] Page objects follow the base class pattern
|
|
524
|
+
- [ ] Tests organized by feature in subdirectories
|
|
525
|
+
- [ ] Responsive tests use device presets, not arbitrary viewport sizes
|
|
526
|
+
- [ ] Accessibility tests use `@axe-core/playwright` for automated checks
|
|
527
|
+
|
|
528
|
+
Apply `digiqagent:verification-before-completion` — evidence before claims.
|
|
529
|
+
|
|
530
|
+
## Red Flags — STOP and Fix
|
|
531
|
+
|
|
532
|
+
| Problem | Fix |
|
|
533
|
+
|---------|-----|
|
|
534
|
+
| Using `page.waitForTimeout(5000)` | Replace with `await expect(locator).toBeVisible()` or proper auto-wait |
|
|
535
|
+
| Using `page.locator('#app > div:nth-child(3) > button')` | Use `page.getByRole('button', { name: '...' })` or `data-testid` |
|
|
536
|
+
| Test has no assertions | Add `expect()` calls — a test without assertions proves nothing |
|
|
537
|
+
| Assertions in page objects | Move assertions to test files; POMs are interaction wrappers only |
|
|
538
|
+
| All tests in one massive file | Split by feature/page into separate `.spec.ts` files |
|
|
539
|
+
| Form has only positive tests | Add negative tests for every validation rule |
|
|
540
|
+
| Auth routes have no guard tests | Add unauthenticated redirect test |
|
|
541
|
+
| No responsive tests | Add mobile/tablet viewport tests for layout changes |
|
|
542
|
+
| No accessibility tests | Add keyboard navigation and `@axe-core` tests |
|
|
543
|
+
| Hardcoded test data in tests | Extract to fixtures or `test-data.ts` |
|
|
544
|
+
| Using `page.evaluate()` for simple interactions | Use Playwright's built-in actions (`click`, `fill`, `check`) |
|
|
545
|
+
| Tests depend on execution order | Each test should be independent; use `beforeEach` for setup |
|
|
546
|
+
| No POM — raw locators everywhere | Generate page objects; tests should read like user stories |
|
|
547
|
+
|
|
548
|
+
## Common Patterns
|
|
549
|
+
|
|
550
|
+
See @page-object-patterns.md for Page Object Model reference:
|
|
551
|
+
- Angular Material component POMs
|
|
552
|
+
- React MUI component POMs
|
|
553
|
+
- Login, form, table, and modal POM examples
|
|
554
|
+
|
|
555
|
+
See @interaction-test-patterns.md for test code patterns:
|
|
556
|
+
- Forms, clicks, navigation, modals, dropdowns, tables
|
|
557
|
+
- Responsive, accessibility, auth flows, error handling
|
|
558
|
+
|
|
559
|
+
## Integration with Other Skills
|
|
560
|
+
|
|
561
|
+
- Use `digiqagent:verification-before-completion` before claiming tests are complete
|
|
562
|
+
- Use `digiqagent:test-driven-development` if writing tests for new features being built
|
|
563
|
+
- Use `digiqagent:systematic-debugging` when Playwright tests fail unexpectedly
|