@nhonh/qabot 1.1.0 → 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/package.json +1 -1
- package/src/cli/commands/test.js +11 -0
- package/src/core/constants.js +1 -1
- package/src/e2e/e2e-generator.js +11 -7
- package/src/e2e/e2e-prompts.js +81 -58
- package/src/e2e/playwright-setup.js +2 -2
package/package.json
CHANGED
package/src/cli/commands/test.js
CHANGED
|
@@ -153,11 +153,22 @@ async function runTest(feature, options) {
|
|
|
153
153
|
|
|
154
154
|
const route = featureConfig?.route || guessRoute(feature);
|
|
155
155
|
|
|
156
|
+
let routesContext = "";
|
|
157
|
+
const routeFiles = await findFiles(
|
|
158
|
+
projectDir,
|
|
159
|
+
"src/routes/*.{js,jsx,ts,tsx}",
|
|
160
|
+
);
|
|
161
|
+
if (routeFiles.length > 0) {
|
|
162
|
+
const routeContent = await safeReadFile(routeFiles[0]);
|
|
163
|
+
if (routeContent) routesContext = routeContent.slice(0, 3000);
|
|
164
|
+
}
|
|
165
|
+
|
|
156
166
|
try {
|
|
157
167
|
const spec = await generateE2ESpec(ai, feature, {
|
|
158
168
|
baseUrl,
|
|
159
169
|
sourceCode,
|
|
160
170
|
route,
|
|
171
|
+
routes: routesContext,
|
|
161
172
|
authProvider: config.auth?.provider || "none",
|
|
162
173
|
useCases,
|
|
163
174
|
});
|
package/src/core/constants.js
CHANGED
package/src/e2e/e2e-generator.js
CHANGED
|
@@ -33,17 +33,21 @@ ${code}
|
|
|
33
33
|
## CRITICAL: Architecture rules (DO NOT CHANGE)
|
|
34
34
|
1. MUST use \`test.describe.serial\` — all tests share ONE browser session
|
|
35
35
|
2. MUST use \`let page\` at describe scope — shared across all tests
|
|
36
|
-
3. \`test.beforeAll\` creates page
|
|
36
|
+
3. \`test.beforeAll\` creates page via \`browser.newPage()\` ONLY
|
|
37
37
|
4. \`test.afterAll\` closes page ONCE
|
|
38
38
|
5. Each \`test()\` does NOT take \`{ page }\` argument — uses shared \`page\`
|
|
39
|
+
6. Tests are SEQUENTIAL JOURNEY — TC-01 opens landing, TC-02 logs in, TC-03+ navigate and test
|
|
40
|
+
7. Login happens inside a test (TC-02), NOT in beforeAll
|
|
39
41
|
|
|
40
42
|
## Common fixes
|
|
41
|
-
1. Wrong selector — use getByRole, getByText, getByTestId
|
|
42
|
-
2. Timing — add waitForLoadState("networkidle") or waitForTimeout(2000)
|
|
43
|
-
3. Element not visible —
|
|
44
|
-
4. Navigation — use
|
|
45
|
-
5. Auth —
|
|
46
|
-
6. Timeout — increase timeout
|
|
43
|
+
1. Wrong selector — use getByRole, getByText, getByTestId, or .first() for multiple matches
|
|
44
|
+
2. Timing — add waitForLoadState("networkidle") or waitForTimeout(2000) after navigation
|
|
45
|
+
3. Element not visible — scroll into view, wait for animation, check if inside modal/iframe
|
|
46
|
+
4. Navigation — use FULL URL (e.g. "${context.baseUrl}/refer-a-friend") not relative paths
|
|
47
|
+
5. Auth — if login fails, wrap in try/catch and skip dependent tests gracefully
|
|
48
|
+
6. Timeout — increase to { timeout: 15000 } or { timeout: 30000 } for slow pages
|
|
49
|
+
7. Multiple matches — use .first() or more specific parent: page.locator(".section").getByText()
|
|
50
|
+
8. Page changed — after clicking navigation links, await page.waitForURL() before assertions
|
|
47
51
|
|
|
48
52
|
Return the COMPLETE fixed test file. No markdown fences.`;
|
|
49
53
|
|
package/src/e2e/e2e-prompts.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export function buildE2EPrompt(featureName, context) {
|
|
2
2
|
const sourceSection = context.sourceCode
|
|
3
|
-
? `\n## Source Code
|
|
3
|
+
? `\n## Source Code (analyze navigation, hooks, API calls, user actions, conditions)\n\`\`\`\n${context.sourceCode.slice(0, 10000)}\n\`\`\`\n`
|
|
4
4
|
: "";
|
|
5
5
|
|
|
6
|
-
const routeSection = context.
|
|
7
|
-
? `\n##
|
|
8
|
-
:
|
|
6
|
+
const routeSection = context.routes
|
|
7
|
+
? `\n## All Application Routes\n${context.routes}\n`
|
|
8
|
+
: context.route
|
|
9
|
+
? `\n## Feature Route: ${context.route}\n`
|
|
10
|
+
: "";
|
|
9
11
|
|
|
10
12
|
const useCaseSection = context.useCases?.length
|
|
11
13
|
? `\n## QA Use Cases\n${context.useCases.map((uc) => `### ${uc.scenario}\n${uc.steps.map((s, i) => `${i + 1}. ${s}`).join("\n")}`).join("\n\n")}\n`
|
|
@@ -14,81 +16,102 @@ export function buildE2EPrompt(featureName, context) {
|
|
|
14
16
|
const baseUrl = context.baseUrl || "http://localhost:3000";
|
|
15
17
|
const route = context.route || "/" + featureName;
|
|
16
18
|
|
|
17
|
-
return `You are an expert
|
|
19
|
+
return `You are an expert QA automation engineer designing REAL user journey tests.
|
|
18
20
|
|
|
19
21
|
## Feature: ${featureName}
|
|
20
22
|
## Base URL: ${baseUrl}
|
|
21
|
-
## Page Route: ${route}
|
|
22
23
|
## Auth: ${context.authProvider || "none"}
|
|
23
|
-
${sourceSection}${useCaseSection}
|
|
24
|
+
${routeSection}${sourceSection}${useCaseSection}
|
|
24
25
|
|
|
25
|
-
## CRITICAL
|
|
26
|
+
## CRITICAL: This is a JOURNEY-BASED test, not a page screenshot test.
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
You MUST test REAL USER FLOWS across multiple pages. A user does NOT just open one page — they:
|
|
29
|
+
1. Land on the app → see landing/home page
|
|
30
|
+
2. Sign in or are already signed in
|
|
31
|
+
3. Navigate to the feature via menu/link/URL
|
|
32
|
+
4. Interact with the feature (click buttons, fill forms, read data)
|
|
33
|
+
5. Feature may redirect them to other pages (FAQ, account, modals)
|
|
34
|
+
6. Come back, verify state changed
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
## ARCHITECTURE — Single Browser, Serial Tests
|
|
30
37
|
|
|
31
|
-
\`\`\`
|
|
38
|
+
\`\`\`javascript
|
|
32
39
|
const { test, expect } = require("@playwright/test");
|
|
33
40
|
const { login } = require("../helpers/auth.js");
|
|
34
41
|
|
|
35
|
-
test.describe.serial("${featureName}", () => {
|
|
42
|
+
test.describe.serial("${featureName} - User Journey", () => {
|
|
36
43
|
let page;
|
|
37
44
|
|
|
38
45
|
test.beforeAll(async ({ browser }) => {
|
|
39
46
|
page = await browser.newPage();
|
|
40
|
-
await login(page, "${baseUrl}");
|
|
41
|
-
await page.goto("${baseUrl}${route}");
|
|
42
|
-
await page.waitForLoadState("networkidle");
|
|
43
|
-
await page.screenshot({ path: "e2e/screenshots/${featureName}-00-initial.png", fullPage: true });
|
|
44
47
|
});
|
|
45
48
|
|
|
46
49
|
test.afterAll(async () => {
|
|
47
|
-
await page.close();
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("TC-01: page loads and displays main content", async () => {
|
|
51
|
-
// use the shared 'page' variable — NOT the argument
|
|
52
|
-
await expect(page).toHaveURL(/${featureName.replace(/-/g, "[-/]")}/i);
|
|
53
|
-
// verify main content visible
|
|
54
|
-
await page.screenshot({ path: "e2e/screenshots/${featureName}-01-loaded.png", fullPage: true });
|
|
50
|
+
if (page) await page.close();
|
|
55
51
|
});
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
53
|
+
// Tests use shared 'page' — same browser, same session, same cookies
|
|
54
|
+
// Each test() does NOT take { page } argument
|
|
60
55
|
});
|
|
61
56
|
\`\`\`
|
|
62
57
|
|
|
63
|
-
##
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
58
|
+
## GENERATE 10-15 TEST CASES as a SEQUENTIAL USER JOURNEY:
|
|
59
|
+
|
|
60
|
+
### Phase 1: Entry & Authentication (TC-01 to TC-03)
|
|
61
|
+
- TC-01: Open app landing page → verify it loads → screenshot
|
|
62
|
+
- TC-02: Sign in flow → click sign in → fill credentials → submit → verify redirect to lobby/home
|
|
63
|
+
- TC-03: Verify authenticated state → user menu visible, correct username/avatar
|
|
64
|
+
|
|
65
|
+
### Phase 2: Navigation to Feature (TC-04 to TC-05)
|
|
66
|
+
- TC-04: Navigate to "${featureName}" → via menu, link, or direct URL "${route}" → verify page loads
|
|
67
|
+
- TC-05: Verify feature page layout → key sections, headers, data loaded (not empty/loading)
|
|
68
|
+
|
|
69
|
+
### Phase 3: Core Feature Interactions (TC-06 to TC-10)
|
|
70
|
+
READ THE SOURCE CODE and generate tests for the ACTUAL user interactions:
|
|
71
|
+
- What buttons does the page have? Test clicking them.
|
|
72
|
+
- What data does the page display? Verify it shows correctly.
|
|
73
|
+
- What actions can the user take? (share, copy, claim, submit, toggle, select)
|
|
74
|
+
- What modals/popups appear? Verify they open and close.
|
|
75
|
+
- What state changes happen? (loading → loaded, button text changes, counters update)
|
|
76
|
+
|
|
77
|
+
### Phase 4: Cross-Page Navigation (TC-11 to TC-12)
|
|
78
|
+
- Does the feature link to other pages? (FAQ, account, game details, etc.)
|
|
79
|
+
- Test navigating AWAY and coming BACK — verify state is preserved or reset correctly
|
|
80
|
+
- Test browser back button behavior
|
|
81
|
+
|
|
82
|
+
### Phase 5: Edge Cases & Cleanup (TC-13 to TC-15)
|
|
83
|
+
- Test with different viewport (mobile: 375x667)
|
|
84
|
+
- Test error states if possible (disconnect, slow network)
|
|
85
|
+
- Verify page after refresh (F5) — does state persist?
|
|
86
|
+
|
|
87
|
+
## PLAYWRIGHT RULES:
|
|
88
|
+
1. Use \`test.describe.serial\` — all tests share ONE page
|
|
89
|
+
2. \`let page\` at describe scope — shared across ALL tests
|
|
90
|
+
3. \`test.beforeAll\` → \`browser.newPage()\` ONLY (login happens in TC-02)
|
|
91
|
+
4. Each \`test()\` does NOT take \`{ page }\` argument
|
|
92
|
+
5. Use \`page.goto("${baseUrl}${route}")\` with FULL URL for navigation
|
|
93
|
+
6. After navigation: \`await page.waitForLoadState("networkidle")\`
|
|
94
|
+
7. Screenshot EVERY test: \`await page.screenshot({ path: "e2e/screenshots/${featureName}-TC{XX}.png", fullPage: true })\`
|
|
95
|
+
8. Selectors priority: \`getByRole()\` > \`getByText()\` > \`getByTestId()\` > \`locator()\`
|
|
96
|
+
9. For slow elements: \`{ timeout: 15000 }\`
|
|
97
|
+
10. For navigation between pages, always verify URL changed: \`expect(page).toHaveURL()\`
|
|
98
|
+
11. After clicking links/buttons that navigate: \`await page.waitForLoadState("networkidle")\`
|
|
99
|
+
12. For modals: \`await expect(page.getByRole("dialog")).toBeVisible()\`
|
|
100
|
+
13. Use \`try/catch\` for optional elements that may not exist (feature flags, A/B tests)
|
|
101
|
+
14. If auth is "${context.authProvider}":
|
|
102
|
+
\`\`\`
|
|
103
|
+
const { login } = require("../helpers/auth.js");
|
|
104
|
+
// In TC-02:
|
|
105
|
+
await login(page, "${baseUrl}");
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
## IMPORTANT:
|
|
109
|
+
- Tests are SEQUENTIAL — TC-02 login result carries to TC-03, TC-04, etc.
|
|
110
|
+
- Do NOT navigate to "${route}" in beforeAll — let TC-01 to TC-04 handle the journey
|
|
111
|
+
- TC-01 starts at "${baseUrl}/" (landing page)
|
|
112
|
+
- Generate MEANINGFUL assertions, not just "page exists"
|
|
113
|
+
- Each test must VERIFY something specific with expect()
|
|
114
|
+
|
|
115
|
+
Return ONLY JavaScript code. No markdown fences. No explanation.
|
|
116
|
+
Generate a COMPLETE spec file with 10-15 tests.`;
|
|
94
117
|
}
|
|
@@ -70,8 +70,8 @@ module.exports = defineConfig({
|
|
|
70
70
|
fullyParallel: false,
|
|
71
71
|
retries: 0,
|
|
72
72
|
workers: 1,
|
|
73
|
-
timeout:
|
|
74
|
-
expect: { timeout:
|
|
73
|
+
timeout: 90000,
|
|
74
|
+
expect: { timeout: 15000 },
|
|
75
75
|
reporter: [
|
|
76
76
|
["line"],
|
|
77
77
|
["json", { outputFile: "../qabot-reports/playwright/results.json" }],
|