@ai-qa/workflow 2.0.14 → 2.0.16
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/.env +1 -0
- package/.github/agents/playwright-test-generator.agent.md +22 -7
- package/.github/agents/playwright-test-healer.agent.md +2 -1
- package/.github/agents/playwright-test-planner.agent.md +9 -2
- package/.qa-workflow.json +2 -1
- package/README.md +60 -14
- package/ai-qa-workflow.js +2 -1
- package/install.js +29 -49
- package/opencode.json +8 -8
- package/package.json +11 -2
- package/playwright.config.ts +20 -0
- package/prompts/QAe2eprompt.md +12 -1
- package/scripts/executor.js +59 -4
- package/scripts/utils.js +1 -1
package/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
APPLITOOLS_API_KEY="5kKZ0PuYgV6jObYXU7sE102waJHMWNPy4PYJCgZJPjjdI110"
|
|
@@ -27,27 +27,42 @@ mcp-servers:
|
|
|
27
27
|
|
|
28
28
|
You are a Playwright test generator. Create robust E2E tests from test plans.
|
|
29
29
|
|
|
30
|
+
## Token Efficiency Rules (CRITICAL)
|
|
31
|
+
- **Read `.qa-context/selectors.json` first** — the Planner already captured selectors. Start from those, do NOT blindly re-explore.
|
|
32
|
+
- **Batch verification pass** — before writing tests, navigate ONCE in a single session to quickly verify all selectors from `selectors.json` are still valid. Use lightweight `locator.isVisible()` checks, NOT full snapshots. Fix broken selectors in `selectors.json` immediately.
|
|
33
|
+
- **If framework is dynamic (Ionic/Angular/etc)** — the DOM re-renders frequently. The verification pass is essential. Read `.qa-workflow.json` → `test.stableSelectors` flag: if `false`, always do a verification pass before writing tests.
|
|
34
|
+
- **Snapshot depth ≤ 3** — `browser_snapshot` with depth 3 max for any navigation.
|
|
35
|
+
- **Skip Applitools if not configured** — if `APPLITOOLS_API_KEY` is not set, skip all visual testing code entirely (no `applitools/check` calls).
|
|
36
|
+
|
|
30
37
|
## Before Starting: Read Context
|
|
31
38
|
1. Read `.qa-context/pipeline.json` to see the current story and phase state
|
|
32
|
-
2. Read `.qa-
|
|
33
|
-
3. Read `.qa-context/
|
|
34
|
-
4. Read `.qa-context/
|
|
39
|
+
2. Read `.qa-workflow.json` → check `test.stableSelectors` — if `false`, the app uses dynamic rendering (Ionic/Angular/etc), so always run the verification pass
|
|
40
|
+
3. Read `.qa-context/selectors.json` — **this is your primary selector source**, use it as the starting point
|
|
41
|
+
4. Read `.qa-context/heal-history.json` to see what's been flaky before
|
|
42
|
+
5. Read `.qa-context/auth.json` and `.auth/credentials.json` to get login selectors and credentials
|
|
35
43
|
|
|
36
44
|
## Auth Protocol
|
|
37
45
|
1. If auth is configured, generate an `auth.setup.ts` file using `auth-manager.js`'s `generateSetupCode()` output
|
|
38
46
|
2. Configure `playwright.config.ts` to use `projects` with `dependencies: ['./auth.setup.ts']`
|
|
39
47
|
3. Each test should assume the user is already logged in — no login steps in individual tests
|
|
40
48
|
|
|
49
|
+
## Selector Verification Pass (Token-Efficient)
|
|
50
|
+
Before writing any test files, do a single batch verification session:
|
|
51
|
+
1. Navigate to each page that has selectors in `selectors.json`
|
|
52
|
+
2. For each selector, run `page.locator(selector).isVisible()` — this is fast and lightweight (no snapshot needed)
|
|
53
|
+
3. If a selector fails, use `browser_snapshot` depth 2 to capture current DOM, then find the new selector
|
|
54
|
+
4. Update `selectors.json` with the corrected selectors immediately
|
|
55
|
+
5. This is **ONE session** for all pages — batch all verifications, do not navigate per test file
|
|
56
|
+
|
|
41
57
|
## After Completing: Update Context
|
|
42
58
|
1. Run: `node ai-qa-workflow.js context generate <story-name>` to mark phase complete
|
|
43
|
-
2.
|
|
59
|
+
2. Update all verified/corrected selectors in `.qa-context/selectors.json`
|
|
44
60
|
3. Update `docs/application-context.md` with new stable selectors found
|
|
45
61
|
|
|
46
62
|
For each test:
|
|
47
63
|
1. Read the test plan scenario
|
|
48
|
-
2.
|
|
49
|
-
3.
|
|
50
|
-
4. Generate the test file using `generator_write_test`
|
|
64
|
+
2. Read selectors from `.qa-context/selectors.json` (already verified in the batch pass)
|
|
65
|
+
3. Generate the test file using `generator_write_test`
|
|
51
66
|
|
|
52
67
|
Always prefer: data-testid > aria-label > role > text > xpath (last resort)
|
|
53
68
|
|
|
@@ -47,13 +47,14 @@ You are the Playwright Test Healer. Debug and fix failing tests systematically.
|
|
|
47
47
|
|
|
48
48
|
Protocol (token-efficient):
|
|
49
49
|
1. Run only failing tests with `test_run`
|
|
50
|
-
2. Debug with `test_debug` — examine the error state
|
|
50
|
+
2. Debug with `test_debug` — examine the error state (uses snapshots with depth ≤ 3 automatically)
|
|
51
51
|
3. Classify the failure:
|
|
52
52
|
- Selector broken → propose 1-3 line fix
|
|
53
53
|
- Timing issue → propose adding 1 wait
|
|
54
54
|
- App bug → mark `test.fixme()`, log defect, move on
|
|
55
55
|
4. Max 1 fix attempt per test. If still failing after fix, it's a defect.
|
|
56
56
|
5. Never rewrite entire files — targeted edits only.
|
|
57
|
+
6. No screenshots — reference failures by path only; skip `browser_take_screenshot`.
|
|
57
58
|
|
|
58
59
|
## ⛔ Approval Gate
|
|
59
60
|
Before applying any fix, **STOP** and present your diagnosis to the user:
|
|
@@ -36,6 +36,13 @@ mcp-servers:
|
|
|
36
36
|
|
|
37
37
|
You are an expert web test planner. You explore web apps and create detailed test plans.
|
|
38
38
|
|
|
39
|
+
## Token Efficiency Rules (CRITICAL)
|
|
40
|
+
- **Explore once, plan everything** — navigate the app in ONE continuous session, record all flows and selectors, then stop. Do NOT re-navigate per scenario.
|
|
41
|
+
- **Snapshot depth ≤ 3** — always use `browser_snapshot` with depth 3 max. Full DOM trees (depth 10+) waste 50-100K tokens per snapshot.
|
|
42
|
+
- **No screenshots during exploration** — reference pages by path only; skip `browser_take_screenshot` unless explicitly requested.
|
|
43
|
+
- **Batch critical paths** — group navigation: explore all pages of the same feature in sequence before moving to the next feature.
|
|
44
|
+
- **Skip non-critical pages** — don't explore every sub-page. Focus on: auth, main flows, edge cases. Skip static content, footers, about pages.
|
|
45
|
+
|
|
39
46
|
## Before Starting: Read Context
|
|
40
47
|
1. Read `.qa-context/pipeline.json` to see what's been done and what's pending
|
|
41
48
|
2. Read `.qa-context/selectors.json` to reuse known stable selectors
|
|
@@ -48,14 +55,14 @@ You are an expert web test planner. You explore web apps and create detailed tes
|
|
|
48
55
|
3. After exploring the login page, save the form structure to `.qa-context/auth.json` using `auth-manager.js`
|
|
49
56
|
4. If the user provides credentials, save them to `.auth/credentials.json` using `auth-manager.js`
|
|
50
57
|
|
|
51
|
-
1.
|
|
58
|
+
1. Explore the application in ONE pass — navigate all critical pages, capture selectors, map flows
|
|
52
59
|
2. Map user flows and identify critical paths
|
|
53
60
|
3. Design comprehensive scenarios (happy path, edge cases, error handling)
|
|
54
61
|
4. Save test plan using `planner_save_plan`
|
|
55
62
|
|
|
56
63
|
## After Completing: Update Context
|
|
57
64
|
1. Run: `node ai-qa-workflow.js context plan <story-name>` to mark phase complete
|
|
58
|
-
2.
|
|
65
|
+
2. Save all discovered selectors to `.qa-context/selectors.json` — the Generator will reuse these and skip re-navigation
|
|
59
66
|
3. Read `docs/application-context.md` first, then enrich it with new findings
|
|
60
67
|
|
|
61
68
|
Each scenario must include: title, steps, expected outcomes, success criteria.
|
package/.qa-workflow.json
CHANGED
package/README.md
CHANGED
|
@@ -157,8 +157,16 @@ After installation and running `npm run qa:init`, open the project in your AI ed
|
|
|
157
157
|
|
|
158
158
|
The very first thing you should say to the AI agent:
|
|
159
159
|
|
|
160
|
-
> **"
|
|
160
|
+
> **"Run the environment check and show me the status report"**
|
|
161
161
|
|
|
162
|
+
The AI will check all 10 preconditions and report what's ready ✅ and what's missing ❌.
|
|
163
|
+
Then wait for your instructions.
|
|
164
|
+
|
|
165
|
+
### Option B — Go straight to QA workflow (if you already have a user story)
|
|
166
|
+
|
|
167
|
+
> **"Read router.md and follow the QA workflow for user-story/my-story.md"**
|
|
168
|
+
|
|
169
|
+
The AI will run the environment check (if not done), then proceed through Pla
|
|
162
170
|
(Replace `my-story.md` with the name of your user story file in `user-story/`.)
|
|
163
171
|
|
|
164
172
|
> **📖 Need more prompts?** See `prompting_template.md` for the full conversation script — approval responses, healing prompts, report prompts, and an example session.
|
|
@@ -259,10 +267,11 @@ The AI updates `docs/application-context.md` with:
|
|
|
259
267
|
| Component | Needed for | Install |
|
|
260
268
|
|-----------|-----------|---------|
|
|
261
269
|
| **Node.js 18+** | Running the pipeline | — |
|
|
262
|
-
| **Playwright** | Test execution | `npm install
|
|
270
|
+
| **Playwright** | Test execution | Pre-installed via `npm install` |
|
|
263
271
|
| **Chromium** | Running tests | `npx playwright install chromium` |
|
|
264
|
-
| **Playwright MCP** | AI browser automation | `npm install
|
|
265
|
-
| **Applitools
|
|
272
|
+
| **Playwright MCP** | AI browser automation | Pre-installed via `npm install` |
|
|
273
|
+
| **Applitools Eyes** | Visual testing + MCP | Pre-installed via `npm install` + `APPLITOOLS_API_KEY` |
|
|
274
|
+
| **Allure** | Rich test reports | Pre-installed via `npm install` |
|
|
266
275
|
| **GitHub MCP** | AI creating PRs/issues | `npm install -D @modelcontextprotocol/server-github` + `GITHUB_TOKEN` |
|
|
267
276
|
|
|
268
277
|
### Install into a project
|
|
@@ -282,21 +291,25 @@ node install.js ../my-project --yes
|
|
|
282
291
|
npx @ai-qa/workflow update --yes
|
|
283
292
|
```
|
|
284
293
|
|
|
294
|
+
> **Note:** The installer creates a `.env` template (`APPLITOOLS_API_KEY=""` and `GITHUB_TOKEN=""`...) only on fresh installs. If the `.env` file was not created (e.g., during an update), create it manually in your project root with those two keys.
|
|
295
|
+
|
|
285
296
|
---
|
|
286
297
|
|
|
287
298
|
## Visual Testing (Applitools)
|
|
288
299
|
|
|
289
|
-
The template supports **Applitools
|
|
300
|
+
The template supports **Applitools Eyes** for automated visual testing via two components:
|
|
301
|
+
- `@applitools/mcp` — MCP server for AI-driven visual testing
|
|
302
|
+
- `@applitools/eyes-playwright` — Playwright integration for test-level visual assertions
|
|
303
|
+
|
|
304
|
+
Both are pre-installed in `package.json` — no extra install needed.
|
|
290
305
|
|
|
291
306
|
If `APPLITOOLS_API_KEY` is configured in your environment, the AI agent automatically adds visual checkpoints to critical pages during test generation. It captures screenshots of pages like login, dashboard, and checkout, and compares them against baselines to detect visual regressions.
|
|
292
307
|
|
|
293
308
|
### Setup
|
|
294
309
|
|
|
295
|
-
|
|
296
|
-
# 1. Install Applitools MCP
|
|
297
|
-
npm install -D @applitools/mcp
|
|
310
|
+
Set your API key (get it from https://applitools.com):
|
|
298
311
|
|
|
299
|
-
|
|
312
|
+
```bash
|
|
300
313
|
# Option A: Export in terminal
|
|
301
314
|
export APPLITOOLS_API_KEY=votre_clé_ici
|
|
302
315
|
|
|
@@ -306,6 +319,14 @@ echo "APPLITOOLS_API_KEY=votre_clé_ici" >> .env
|
|
|
306
319
|
|
|
307
320
|
The AI will detect the key during its environment check and use Applitools automatically. If the key is not set, visual testing is skipped entirely — no errors, no blocks.
|
|
308
321
|
|
|
322
|
+
## Allure Reports
|
|
323
|
+
|
|
324
|
+
Allure test reports are pre-configured:
|
|
325
|
+
- `allure-playwright` reporter generates raw results during `npm run qa:execute`
|
|
326
|
+
- The report is auto-generated as HTML after each test run
|
|
327
|
+
- View via `npm run dashboard` or open `allure-report/index.html`
|
|
328
|
+
- Manual regeneration: `npm run qa:report:allure`
|
|
329
|
+
|
|
309
330
|
---
|
|
310
331
|
|
|
311
332
|
## Commands
|
|
@@ -563,8 +584,33 @@ The AI never:
|
|
|
563
584
|
|
|
564
585
|
## Token Efficiency
|
|
565
586
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
587
|
+
The framework is designed to minimize token usage during AI-driven testing. These rules are embedded in the agent definitions and prompts:
|
|
588
|
+
|
|
589
|
+
### Browser Navigation Rules
|
|
590
|
+
- **Planner explores once** — navigates the app in ONE session and saves all selectors to `.qa-context/selectors.json`.
|
|
591
|
+
- **Verification pass (not re-exploration)** — the Generator reads `selectors.json`, then does a single lightweight batch session to verify selectors via `locator.isVisible()` checks (not full snapshots). This catches stale selectors from dynamic frameworks (Ionic, Angular) without re-exploring every page.
|
|
592
|
+
- **`test.stableSelectors` flag** — set to `false` in `.qa-workflow.json` for dynamic frameworks. The Generator always runs the verification pass when this is `false`.
|
|
593
|
+
- **Snapshot depth ≤ 3** — `browser_snapshot` capped at depth 3. Full DOM trees (depth 10+) can consume 50-100K tokens per snapshot.
|
|
594
|
+
- **Batch all navigation** — all page visits happen in one session per phase, never per scenario.
|
|
595
|
+
- **Skip non-critical pages** — static content, footers, about/legal pages are skipped.
|
|
596
|
+
- **No screenshots during exploration** — pages referenced by path only.
|
|
597
|
+
|
|
598
|
+
### Selector Reuse
|
|
599
|
+
- Planner captures selectors once → saved to `.qa-context/selectors.json`
|
|
600
|
+
- Generator runs a **single batch verification pass** — lightweight `isVisible()` checks, not navigation per test
|
|
601
|
+
- Healer reads existing selectors + healing history → avoids re-discovering failed locators
|
|
602
|
+
- Every discovered or corrected selector is saved so no agent ever re-explores the same element
|
|
603
|
+
|
|
604
|
+
### Execution & Healing
|
|
605
|
+
- Max 1 fix attempt per test — no endless retries
|
|
606
|
+
- Failures classified immediately (selector / timing / bug) — no ambiguous loops
|
|
607
|
+
- Targeted 1-3 line edits only — never rewrite entire files
|
|
608
|
+
- `test.fixme()` marks defects — no retrying known bugs
|
|
609
|
+
|
|
610
|
+
### Context Management
|
|
611
|
+
- Error messages truncated to 200 chars
|
|
612
|
+
- Files cached in memory between reads
|
|
613
|
+
- Screenshots referenced by path, not embedded
|
|
614
|
+
- Directory listings done once per pipeline run
|
|
615
|
+
|
|
616
|
+
For details, see the agent files in `.github/agents/` and the `TOKEN & EFFICIENCY RULES` section in `prompts/QAe2eprompt.md`.
|
package/ai-qa-workflow.js
CHANGED
|
@@ -118,7 +118,8 @@ function cmdExecute() {
|
|
|
118
118
|
console.log(`\n Next step: Auto-heal failures:`);
|
|
119
119
|
console.log(` node ai-qa-workflow.js heal ${result.runId}`);
|
|
120
120
|
} else {
|
|
121
|
-
console.log(`\n
|
|
121
|
+
console.log(`\n ✓ Allure report auto-generated: allure-report/index.html`);
|
|
122
|
+
console.log(` Next step: Generate detailed report:`);
|
|
122
123
|
console.log(` node ai-qa-workflow.js report ${result.runId}`);
|
|
123
124
|
}
|
|
124
125
|
}
|
package/install.js
CHANGED
|
@@ -24,6 +24,7 @@ const USER_DIRS = new Set([
|
|
|
24
24
|
'test-results',
|
|
25
25
|
'.qa-context',
|
|
26
26
|
'.auth',
|
|
27
|
+
'.env'
|
|
27
28
|
]);
|
|
28
29
|
|
|
29
30
|
// Template files to copy/update
|
|
@@ -42,6 +43,10 @@ const QA_ITEMS = [
|
|
|
42
43
|
{ src: '.github/copilot-instructions.md', dest: '.github/copilot-instructions.md' },
|
|
43
44
|
{ src: 'router.md', dest: 'router.md' },
|
|
44
45
|
{ src: 'opencode.json', dest: 'opencode.json' },
|
|
46
|
+
{ src: 'playwright.config.ts', dest: 'playwright.config.ts' },
|
|
47
|
+
{ src: 'cli.js', dest: 'cli.js' },
|
|
48
|
+
{ src: 'package.json', dest: 'package.json' },
|
|
49
|
+
{ src: 'install.js', dest: 'install.js' },
|
|
45
50
|
|
|
46
51
|
];
|
|
47
52
|
|
|
@@ -53,23 +58,6 @@ const UPDATE_ITEMS = QA_ITEMS.filter(item => {
|
|
|
53
58
|
|
|
54
59
|
const DIRS_TO_CREATE = ['user-story', 'specs', 'tests', 'test-results', '.qa-context', '.auth', 'templates'];
|
|
55
60
|
|
|
56
|
-
const NPM_SCRIPTS = {
|
|
57
|
-
'qa': 'node ai-qa-workflow.js',
|
|
58
|
-
'qa:init': 'node ai-qa-workflow.js init',
|
|
59
|
-
'qa:plan': 'node ai-qa-workflow.js plan',
|
|
60
|
-
'qa:generate': 'node ai-qa-workflow.js generate',
|
|
61
|
-
'qa:execute': 'node ai-qa-workflow.js execute',
|
|
62
|
-
'qa:heal': 'node ai-qa-workflow.js heal',
|
|
63
|
-
'qa:retry': 'node ai-qa-workflow.js heal',
|
|
64
|
-
'qa:report': 'node ai-qa-workflow.js report',
|
|
65
|
-
'qa:report:allure': 'node ai-qa-workflow.js report:allure',
|
|
66
|
-
'qa:status': 'node ai-qa-workflow.js status',
|
|
67
|
-
'qa:list': 'node ai-qa-workflow.js list',
|
|
68
|
-
'dashboard': 'cd qa-dashboard && npm start',
|
|
69
|
-
'dashboard:dev': 'cd qa-dashboard && npx nodemon app.js',
|
|
70
|
-
'dashboard:stop': 'npx kill-port 4000',
|
|
71
|
-
};
|
|
72
|
-
|
|
73
61
|
const BANNER = `
|
|
74
62
|
╔══════════════════════════════════════════╗
|
|
75
63
|
║ AI QA Pipeline Installer v2.0 ║
|
|
@@ -106,21 +94,6 @@ function countFiles(dir) {
|
|
|
106
94
|
return count;
|
|
107
95
|
}
|
|
108
96
|
|
|
109
|
-
function addNpmScripts(pkgPath, overwrite) {
|
|
110
|
-
if (!fs.existsSync(pkgPath)) return;
|
|
111
|
-
let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
112
|
-
if (!pkg.scripts) pkg.scripts = {};
|
|
113
|
-
let changed = 0;
|
|
114
|
-
for (const [key, val] of Object.entries(NPM_SCRIPTS)) {
|
|
115
|
-
if (!pkg.scripts[key] || overwrite) {
|
|
116
|
-
pkg.scripts[key] = val;
|
|
117
|
-
changed++;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
121
|
-
return changed;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
97
|
function ask(query) {
|
|
125
98
|
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
126
99
|
return new Promise(resolve => rl.question(query, a => { rl.close(); resolve(a.toLowerCase()); }));
|
|
@@ -162,12 +135,17 @@ async function install(targetPath, mode) {
|
|
|
162
135
|
}
|
|
163
136
|
}
|
|
164
137
|
|
|
165
|
-
// 3.
|
|
166
|
-
console.log(`\n ── Step 3:
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
138
|
+
// 3. Create .env template (fresh install only)
|
|
139
|
+
console.log(`\n ── Step 3: Environment ──`);
|
|
140
|
+
if (!isUpdate) {
|
|
141
|
+
const envPath = path.join(targetPath, '.env');
|
|
142
|
+
if (!fs.existsSync(envPath)) {
|
|
143
|
+
fs.writeFileSync(envPath, 'APPLITOOLS_API_KEY=""\nGITHUB_TOKEN=""\n');
|
|
144
|
+
console.log(` ✓ .env (template created)`);
|
|
145
|
+
} else {
|
|
146
|
+
console.log(` • .env (exists, kept as-is)`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
171
149
|
|
|
172
150
|
// 4. Dashboard
|
|
173
151
|
const dashboardSrc = path.join(TEMPLATE_DIR, 'qa-dashboard');
|
|
@@ -211,13 +189,13 @@ async function install(targetPath, mode) {
|
|
|
211
189
|
}
|
|
212
190
|
}
|
|
213
191
|
|
|
214
|
-
// 6. Install
|
|
192
|
+
// 6. Install all dependencies (fresh only)
|
|
215
193
|
if (!isUpdate) {
|
|
216
194
|
console.log(`\n ── Step 6: Dependencies ──`);
|
|
217
195
|
if (!fs.existsSync(path.join(targetPath, 'node_modules', '@playwright'))) {
|
|
218
|
-
console.log(` → Installing
|
|
219
|
-
try { execSync('npm install
|
|
220
|
-
} else console.log(` •
|
|
196
|
+
console.log(` → Installing all dependencies (Playwright, Allure, Applitools)...`);
|
|
197
|
+
try { execSync('npm install', { cwd: targetPath, stdio: 'pipe', timeout: 180000 }); console.log(` ✓ All dependencies installed`); } catch (e) { console.log(` ⚠ npm install failed: npm install`); }
|
|
198
|
+
} else console.log(` • Dependencies already installed`);
|
|
221
199
|
}
|
|
222
200
|
|
|
223
201
|
// Summary
|
|
@@ -233,9 +211,11 @@ async function install(targetPath, mode) {
|
|
|
233
211
|
console.log(` Restart the dashboard if it was running.\n`);
|
|
234
212
|
} else {
|
|
235
213
|
console.log(` Files: ~${totalFiles} scripts + ${dashboardCount} dashboard files`);
|
|
214
|
+
console.log(` Dependencies: @playwright/test, allure-playwright, @applitools/eyes-playwright, @applitools/mcp`);
|
|
236
215
|
console.log(` Next:\n`);
|
|
216
|
+
console.log(` npm install Install all dependencies`);
|
|
237
217
|
console.log(` npm run qa:init Initialize pipeline (config + dirs + auth)`);
|
|
238
|
-
console.log(` npm run qa:execute Run Playwright tests`);
|
|
218
|
+
console.log(` npm run qa:execute Run Playwright tests (auto-generates Allure report)`);
|
|
239
219
|
console.log(` npm run qa:status Check pipeline state`);
|
|
240
220
|
console.log(` npm run dashboard Start dashboard (port 4000)\n`);
|
|
241
221
|
}
|
|
@@ -279,12 +259,12 @@ async function main() {
|
|
|
279
259
|
// Parse flags and target
|
|
280
260
|
const nonFlagArgs = args.filter(a => !a.startsWith('-'));
|
|
281
261
|
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
} else if (args.includes('
|
|
286
|
-
|
|
287
|
-
|
|
262
|
+
if (args.includes('update')) {
|
|
263
|
+
targetPath = process.cwd();
|
|
264
|
+
mode = 'update';
|
|
265
|
+
} else if (IS_SELF || args.includes('init')) {
|
|
266
|
+
targetPath = process.cwd();
|
|
267
|
+
mode = 'install';
|
|
288
268
|
} else if (IS_UPDATE) {
|
|
289
269
|
targetPath = path.resolve(nonFlagArgs[0] || process.cwd());
|
|
290
270
|
mode = 'update';
|
package/opencode.json
CHANGED
|
@@ -14,14 +14,14 @@
|
|
|
14
14
|
"description": "GitHub integration - PRs, issues, commits. Requires GITHUB_TOKEN env var. Install: npm install -D @modelcontextprotocol/server-github"
|
|
15
15
|
},
|
|
16
16
|
"applitools-mcp": {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
"type": "local",
|
|
18
|
+
"command": ["npx", "-y", "@applitools/mcp@latest"],
|
|
19
|
+
"enabled": true,
|
|
20
|
+
"description": "Visual testing via Applitools Eyes - requires APPLITOOLS_API_KEY env var. Installed during npm setup: @applitools/mcp + @applitools/eyes-playwright",
|
|
21
|
+
"env": {
|
|
22
|
+
"APPLITOOLS_API_KEY": "${APPLITOOLS_API_KEY}"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
25
|
},
|
|
26
26
|
"agent": {
|
|
27
27
|
"qa-planner": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-qa/workflow",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.16",
|
|
4
4
|
"description": "AI QA Workflow Template — transforms any AI agent into an autonomous QA engineer. AI explores, plans, generates tests, and heals. Scripts execute and report.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"qa",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"ai-qa-workflow.js",
|
|
24
24
|
"cli.js",
|
|
25
25
|
"install.js",
|
|
26
|
+
"playwright.config.ts",
|
|
26
27
|
"README.md",
|
|
27
28
|
"PROJECT_GUIDE.md",
|
|
28
29
|
"prompting_template.md",
|
|
@@ -32,7 +33,8 @@
|
|
|
32
33
|
"opencode.json",
|
|
33
34
|
".qa-workflow.json",
|
|
34
35
|
"router.md",
|
|
35
|
-
".github/"
|
|
36
|
+
".github/",
|
|
37
|
+
".env"
|
|
36
38
|
],
|
|
37
39
|
"license": "MIT",
|
|
38
40
|
"author": "AI QA Workflow",
|
|
@@ -58,5 +60,12 @@
|
|
|
58
60
|
"dashboard": "cd qa-dashboard && npm start",
|
|
59
61
|
"dashboard:dev": "cd qa-dashboard && npx nodemon app.js",
|
|
60
62
|
"dashboard:stop": "npx kill-port 4000"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@playwright/test": "^1.52.0",
|
|
66
|
+
"allure-playwright": "^3.2.1",
|
|
67
|
+
"allure-commandline": "^2.33.0",
|
|
68
|
+
"@applitools/eyes-playwright": "^1.42.0",
|
|
69
|
+
"@applitools/mcp": "^1.5.0"
|
|
61
70
|
}
|
|
62
71
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: './tests',
|
|
5
|
+
fullyParallel: true,
|
|
6
|
+
forbidOnly: !!process.env.CI,
|
|
7
|
+
retries: process.env.CI ? 2 : 0,
|
|
8
|
+
workers: process.env.CI ? 1 : undefined,
|
|
9
|
+
use: {
|
|
10
|
+
baseURL: process.env.APP_URL || 'http://localhost:3000',
|
|
11
|
+
trace: 'on-first-retry',
|
|
12
|
+
screenshot: 'only-on-failure',
|
|
13
|
+
},
|
|
14
|
+
projects: [
|
|
15
|
+
{
|
|
16
|
+
name: 'chromium',
|
|
17
|
+
use: { ...devices['Desktop Chrome'] },
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
});
|
package/prompts/QAe2eprompt.md
CHANGED
|
@@ -475,6 +475,18 @@ Use this knowledge to improve future test generation and healing.
|
|
|
475
475
|
---------------------------------------------------
|
|
476
476
|
## TOKEN & EFFICIENCY RULES (CRITICAL)
|
|
477
477
|
|
|
478
|
+
### Browser Navigation Rules
|
|
479
|
+
- **Explore once, plan everything** — navigate the app in ONE continuous session per phase. Do NOT re-navigate per scenario.
|
|
480
|
+
- **Snapshot depth ≤ 3** — always use `browser_snapshot` with max depth 3. Full DOM trees (depth 10+) waste 50-100K tokens per snapshot.
|
|
481
|
+
- **Batch all navigation** — group page visits by feature. Visit all pages of a feature in sequence, then move to the next.
|
|
482
|
+
- **Skip non-critical pages** — static content, footers, about pages, legal pages. Focus on auth, main flows, edge cases.
|
|
483
|
+
- **No screenshots during exploration or test logic** — reference pages by path only. Screenshots only on test failure via Playwright config.
|
|
484
|
+
|
|
485
|
+
### Selector Reuse (Biggest Token Saver)
|
|
486
|
+
- **Generator reads `selectors.json` first** — the Planner already captured selectors. The Generator writes tests without re-navigating.
|
|
487
|
+
- **Only navigate if selectors are missing** — if `.qa-context/selectors.json` is empty for a page, navigate ONCE (depth ≤ 3), save selectors, then proceed.
|
|
488
|
+
- **Save every discovered selector** — update `.qa-context/selectors.json` so no agent ever re-discovers the same element.
|
|
489
|
+
|
|
478
490
|
### Execution Rules
|
|
479
491
|
- **MAX 1 healing attempt per test** — never loop more than once
|
|
480
492
|
- **Classify failures immediately**: selector issue → fix; app bug → mark `test.fixme()` + document defect
|
|
@@ -484,7 +496,6 @@ Use this knowledge to improve future test generation and healing.
|
|
|
484
496
|
### Code Generation Rules
|
|
485
497
|
- **Never rewrite entire files** — use targeted edits only (change 1-3 lines max)
|
|
486
498
|
- **Use body text checks**: `page.textContent('body')` instead of fragile element selectors
|
|
487
|
-
- **No screenshots during test logic** — capture only on failure via Playwright config
|
|
488
499
|
- **One assertion per logical check** — no redundant assertions
|
|
489
500
|
- **Skip image-heavy tests** if not critical to the feature
|
|
490
501
|
- **Reuse existing helper functions** — don't create new patterns
|
package/scripts/executor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { DIRS, CONFIG, ensureDir, timestamp, log, writeMarkdown } = require('./utils');
|
|
1
|
+
const { ROOT, DIRS, CONFIG, ensureDir, timestamp, log, writeMarkdown } = require('./utils');
|
|
2
2
|
const context = require('./context-manager');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const fs = require('fs');
|
|
@@ -35,7 +35,12 @@ function executeTests(testName, options = {}) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
const allureAvailable = (() => {
|
|
39
|
+
try { require.resolve('allure-playwright/package.json'); return true; } catch { return false; }
|
|
40
|
+
})();
|
|
41
|
+
const reporters = ['list', 'json'];
|
|
42
|
+
if (allureAvailable) reporters.push('allure-playwright');
|
|
43
|
+
args.push('--reporter', reporters.join(','));
|
|
39
44
|
|
|
40
45
|
if (headed) args.push('--headed');
|
|
41
46
|
if (retries > 0) args.push('--retries', retries.toString());
|
|
@@ -52,7 +57,7 @@ function executeTests(testName, options = {}) {
|
|
|
52
57
|
|
|
53
58
|
try {
|
|
54
59
|
const stdout = execSync(`npx ${args.join(' ')}`, {
|
|
55
|
-
cwd:
|
|
60
|
+
cwd: ROOT,
|
|
56
61
|
encoding: 'utf-8',
|
|
57
62
|
timeout: 300000,
|
|
58
63
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -61,6 +66,8 @@ function executeTests(testName, options = {}) {
|
|
|
61
66
|
|
|
62
67
|
fs.writeFileSync(outputPath, stdout, 'utf-8');
|
|
63
68
|
|
|
69
|
+
const passedTests = extractPassedTests(stdout);
|
|
70
|
+
|
|
64
71
|
const result = {
|
|
65
72
|
runId,
|
|
66
73
|
story: storyName,
|
|
@@ -71,7 +78,7 @@ function executeTests(testName, options = {}) {
|
|
|
71
78
|
timestamp: new Date().toISOString(),
|
|
72
79
|
output: stdout.substring(0, 50000),
|
|
73
80
|
failedTests: [],
|
|
74
|
-
passedTests
|
|
81
|
+
passedTests,
|
|
75
82
|
};
|
|
76
83
|
|
|
77
84
|
writeMarkdown(resultPath, JSON.stringify(result, null, 2));
|
|
@@ -82,6 +89,8 @@ function executeTests(testName, options = {}) {
|
|
|
82
89
|
}
|
|
83
90
|
log('EXECUTOR', `Tests passed (${result.duration}ms)`);
|
|
84
91
|
|
|
92
|
+
generateAllureReport();
|
|
93
|
+
|
|
85
94
|
return result;
|
|
86
95
|
} catch (err) {
|
|
87
96
|
const stderr = err.stderr || '';
|
|
@@ -91,6 +100,7 @@ function executeTests(testName, options = {}) {
|
|
|
91
100
|
fs.writeFileSync(outputPath, combinedOutput, 'utf-8');
|
|
92
101
|
|
|
93
102
|
const failedTests = extractFailedTests(combinedOutput);
|
|
103
|
+
const passedTests = extractPassedTests(combinedOutput);
|
|
94
104
|
|
|
95
105
|
const result = {
|
|
96
106
|
runId,
|
|
@@ -103,6 +113,7 @@ function executeTests(testName, options = {}) {
|
|
|
103
113
|
error: err.message,
|
|
104
114
|
output: combinedOutput.substring(0, 50000),
|
|
105
115
|
failedTests,
|
|
116
|
+
passedTests,
|
|
106
117
|
};
|
|
107
118
|
|
|
108
119
|
writeMarkdown(resultPath, JSON.stringify(result, null, 2));
|
|
@@ -113,6 +124,8 @@ function executeTests(testName, options = {}) {
|
|
|
113
124
|
}
|
|
114
125
|
log('EXECUTOR', `Tests failed (${result.duration}ms) - ${failedTests.length} failure(s)`);
|
|
115
126
|
|
|
127
|
+
generateAllureReport();
|
|
128
|
+
|
|
116
129
|
return result;
|
|
117
130
|
}
|
|
118
131
|
}
|
|
@@ -148,6 +161,48 @@ function extractFailedTests(output) {
|
|
|
148
161
|
return failed;
|
|
149
162
|
}
|
|
150
163
|
|
|
164
|
+
function extractPassedTests(output) {
|
|
165
|
+
const passed = [];
|
|
166
|
+
const lines = output.split('\n');
|
|
167
|
+
|
|
168
|
+
for (const line of lines) {
|
|
169
|
+
const passMatch = line.match(/\s+√\s+\d+\)\s+\[(.+?)\]\s+(.+)/);
|
|
170
|
+
if (passMatch) {
|
|
171
|
+
passed.push({ file: passMatch[1], test: passMatch[2].trim() });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const passSimple = line.match(/\s+√\s+(.+)/);
|
|
176
|
+
if (passSimple && !line.includes('ms')) {
|
|
177
|
+
passed.push({ test: passSimple[1].trim() });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return passed;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function generateAllureReport() {
|
|
185
|
+
const allureResults = DIRS.allureResults;
|
|
186
|
+
const allureReport = path.join(ROOT, 'allure-report');
|
|
187
|
+
|
|
188
|
+
if (!fs.existsSync(allureResults) || fs.readdirSync(allureResults).length === 0) return;
|
|
189
|
+
|
|
190
|
+
log('EXECUTOR', 'Auto-generating Allure report...');
|
|
191
|
+
try {
|
|
192
|
+
const { execSync } = require('child_process');
|
|
193
|
+
execSync(`npx allure generate "${allureResults}" --clean -o "${allureReport}"`, {
|
|
194
|
+
cwd: ROOT,
|
|
195
|
+
encoding: 'utf-8',
|
|
196
|
+
timeout: 60000,
|
|
197
|
+
stdio: 'pipe',
|
|
198
|
+
});
|
|
199
|
+
log('EXECUTOR', `Allure report ready: allure-report/index.html`);
|
|
200
|
+
} catch (err) {
|
|
201
|
+
// Allure CLI not available — non-fatal
|
|
202
|
+
log('EXECUTOR', 'Allure CLI not available, skipping HTML report generation');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
151
206
|
if (require.main === module) {
|
|
152
207
|
const testName = process.argv[2];
|
|
153
208
|
const headed = process.argv.includes('--headed');
|
package/scripts/utils.js
CHANGED
|
@@ -111,7 +111,7 @@ function loadConfig() {
|
|
|
111
111
|
const defaults = {
|
|
112
112
|
project: { name: 'my-project', description: '', url: 'http://localhost:3000', environment: 'Development' },
|
|
113
113
|
browser: { type: 'chromium', cdpPort: 9222, headed: false },
|
|
114
|
-
test: { timeout: 120000, retries: 0, workers: 1 },
|
|
114
|
+
test: { timeout: 120000, retries: 0, workers: 1, stableSelectors: true },
|
|
115
115
|
auth: { user: '', credentials: {} },
|
|
116
116
|
};
|
|
117
117
|
|