@nhonh/qabot 1.0.1 → 1.1.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nhonh/qabot",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "AI-powered universal QA automation tool. Import any project, AI analyzes and runs tests across all layers.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -77,7 +77,7 @@ async function runTest(feature, options) {
77
77
  try {
78
78
  await ensurePlaywright(projectDir);
79
79
  await ensureE2EStructure(projectDir);
80
- await writePlaywrightConfig(projectDir, config);
80
+ await writePlaywrightConfig(projectDir, config, baseUrl);
81
81
  await writeAuthHelper(projectDir, config);
82
82
  spinner.succeed(G(" Playwright ready"));
83
83
  } catch (err) {
@@ -277,6 +277,7 @@ function runPlaywrightStreaming(projectDir, specFile, options, baseUrl) {
277
277
  `--config=${configPath}`,
278
278
  "--project=chromium",
279
279
  "--reporter=line",
280
+ "--timeout=60000",
280
281
  );
281
282
  if (options.headed) args.push("--headed");
282
283
 
@@ -1,4 +1,4 @@
1
- export const VERSION = "1.0.1";
1
+ export const VERSION = "1.1.0";
2
2
  export const TOOL_NAME = "qabot";
3
3
 
4
4
  export const PROJECT_TYPES = [
@@ -30,12 +30,20 @@ ${code}
30
30
  - Base URL: ${context.baseUrl || "http://localhost:3000"}
31
31
  - Auth: ${context.authProvider || "none"}
32
32
 
33
- ## Common Playwright fixes
34
- 1. Wrong selectoruse getByRole, getByText, getByTestId instead of CSS selectors
35
- 2. Timingadd waitForLoadState("networkidle") or waitForSelector
36
- 3. Element not visible scroll into view or wait for animation
37
- 4. Navigation ensure page.goto uses correct path
38
- 5. Auth ensure login completed before testing
33
+ ## CRITICAL: Architecture rules (DO NOT CHANGE)
34
+ 1. MUST use \`test.describe.serial\` all tests share ONE browser session
35
+ 2. MUST use \`let page\` at describe scope shared across all tests
36
+ 3. \`test.beforeAll\` creates page + login + navigate ONCE
37
+ 4. \`test.afterAll\` closes page ONCE
38
+ 5. Each \`test()\` does NOT take \`{ page }\` argument — uses shared \`page\`
39
+
40
+ ## Common fixes
41
+ 1. Wrong selector — use getByRole, getByText, getByTestId
42
+ 2. Timing — add waitForLoadState("networkidle") or waitForTimeout(2000)
43
+ 3. Element not visible — add .first() for multiple matches, or more specific selector
44
+ 4. Navigation — use full URL with baseURL prefix
45
+ 5. Auth — ensure login completes before assertions
46
+ 6. Timeout — increase timeout for slow elements: { timeout: 15000 }
39
47
 
40
48
  Return the COMPLETE fixed test file. No markdown fences.`;
41
49
 
@@ -1,6 +1,6 @@
1
1
  export function buildE2EPrompt(featureName, context) {
2
2
  const sourceSection = context.sourceCode
3
- ? `\n## Source Code Analysis\nUse this to understand the page structure, components, routes, buttons, forms, modals, and data flow:\n\`\`\`\n${context.sourceCode.slice(0, 8000)}\n\`\`\`\n`
3
+ ? `\n## Source Code Analysis\n\`\`\`\n${context.sourceCode.slice(0, 8000)}\n\`\`\`\n`
4
4
  : "";
5
5
 
6
6
  const routeSection = context.route
@@ -8,60 +8,87 @@ export function buildE2EPrompt(featureName, context) {
8
8
  : "";
9
9
 
10
10
  const useCaseSection = context.useCases?.length
11
- ? `\n## QA Use Cases (from QA team)\n${context.useCases.map((uc) => `### ${uc.scenario}\n${uc.steps.map((s, i) => `${i + 1}. ${s}`).join("\n")}`).join("\n\n")}\n`
11
+ ? `\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`
12
12
  : "";
13
13
 
14
- return `You are an expert QA automation engineer. Write comprehensive Playwright E2E tests.
14
+ const baseUrl = context.baseUrl || "http://localhost:3000";
15
+ const route = context.route || "/" + featureName;
16
+
17
+ return `You are an expert Playwright E2E automation engineer.
15
18
 
16
19
  ## Feature: ${featureName}
17
- ## Base URL: ${context.baseUrl || "http://localhost:3000"}
18
- ## Auth: ${context.authProvider || "none"} (use auth helper if needed)
19
- ${routeSection}${sourceSection}${useCaseSection}
20
-
21
- ## REQUIREMENTS — Generate AT LEAST 8 test cases covering:
22
-
23
- ### Must Include (P0 — Critical):
24
- 1. Page Load & Layout verify page loads, key sections visible, no console errors
25
- 2. Navigation — verify URL is correct, breadcrumbs/tabs work
26
- 3. Primary User Action — the main thing a user does on this page (click button, submit form, etc)
27
- 4. Data Display — verify data renders correctly (lists, cards, tables, amounts)
28
-
29
- ### Should Include (P1 Important):
30
- 5. Secondary Actions other clickable elements, links, toggles
31
- 6. Error States — what happens when something goes wrong (empty data, network error)
32
- 7. Loading States — skeleton/spinner shows while data loads
33
- 8. Responsive/Mobile — if applicable, test viewport changes
34
-
35
- ### Nice to Have (P2 — Edge Cases):
36
- 9. Edge Cases — empty states, boundary values, special characters
37
- 10. Authentication Guards — verify redirects when not logged in
38
-
39
- ## PLAYWRIGHT RULES:
40
- 1. Use \`const { test, expect } = require("@playwright/test");\`
41
- 2. ALWAYS start with \`test.describe("${featureName}", () => { ... })\`
42
- 3. Use \`test.beforeEach\` for common setup (login + navigate)
43
- 4. Use accessible selectors IN THIS ORDER of preference:
44
- - \`page.getByRole("button", { name: /text/i })\`
45
- - \`page.getByText(/text/i)\`
46
- - \`page.getByTestId("id")\`
47
- - \`page.locator("css-selector")\` (LAST resort)
48
- 5. After every navigation: \`await page.waitForLoadState("networkidle")\`
49
- 6. Take screenshot at EVERY test: \`await page.screenshot({ path: "e2e/screenshots/${featureName}-{testname}.png", fullPage: true })\`
50
- 7. Use \`expect(locator).toBeVisible()\` not \`toBeTruthy()\`
51
- 8. Use \`{ timeout: 15000 }\` for slow-loading elements
52
- 9. Handle auth:
53
- \`\`\`
54
- const { login } = require("../helpers/auth.js");
55
- test.beforeEach(async ({ page, baseURL }) => {
56
- await login(page, baseURL);
57
- await page.goto("${context.route || "/" + featureName}");
58
- await page.waitForLoadState("networkidle");
59
- });
60
- \`\`\`
61
- 10. Each test MUST have a clear assertion with \`expect()\`
62
- 11. Add \`test.slow()\` for tests that need extra time
63
-
64
- ## OUTPUT FORMAT:
65
- Return ONLY JavaScript code. No markdown fences. No explanation.
66
- Generate a COMPLETE runnable spec file with 8-12 tests.`;
20
+ ## Base URL: ${baseUrl}
21
+ ## Page Route: ${route}
22
+ ## Auth: ${context.authProvider || "none"}
23
+ ${sourceSection}${useCaseSection}
24
+
25
+ ## CRITICAL ARCHITECTURE — ONE BROWSER SESSION
26
+
27
+ All tests run in a SINGLE browser session. The browser opens ONCE, logs in ONCE, then navigates between tests WITHOUT closing.
28
+
29
+ Use this EXACT structure:
30
+
31
+ \`\`\`
32
+ const { test, expect } = require("@playwright/test");
33
+ const { login } = require("../helpers/auth.js");
34
+
35
+ test.describe.serial("${featureName}", () => {
36
+ let page;
37
+
38
+ test.beforeAll(async ({ browser }) => {
39
+ 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
+ });
45
+
46
+ 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 });
55
+ });
56
+
57
+ test("TC-02: next test case...", async () => {
58
+ // continue using shared 'page' same browser, same session
59
+ });
60
+ });
61
+ \`\`\`
62
+
63
+ ## IMPORTANT RULES:
64
+ 1. Use \`test.describe.serial\` tests run IN ORDER on the SAME page
65
+ 2. Use \`let page\` declared at describe level shared across ALL tests
66
+ 3. \`test.beforeAll\` creates page + login + navigate ONCE
67
+ 4. \`test.afterAll\` closes page ONCE
68
+ 5. Each \`test()\` does NOT take \`{ page }\` argument — uses the shared \`page\` variable
69
+ 6. Take screenshot in EVERY test with path: \`e2e/screenshots/${featureName}-{number}-{name}.png\`
70
+ 7. Use \`page.getByRole()\`, \`page.getByText()\`, \`page.getByTestId()\` selectors
71
+ 8. After navigation within tests: \`await page.waitForLoadState("networkidle")\`
72
+ 9. Use \`{ timeout: 15000 }\` for slow-loading elements
73
+ 10. Prefix each test name with TC-XX: for tracking
74
+
75
+ ## GENERATE 8-12 TEST CASES:
76
+
77
+ ### P0 Critical (must have):
78
+ - TC-01: Page loads, main content visible, URL correct
79
+ - TC-02: Key data displays correctly (lists, cards, amounts)
80
+ - TC-03: Primary user action (main button/form/interaction)
81
+ - TC-04: Navigation within the feature works
82
+
83
+ ### P1 Important:
84
+ - TC-05: Secondary actions (links, toggles, modals)
85
+ - TC-06: Different UI states (loading, empty, error)
86
+ - TC-07: Form validation if applicable
87
+ - TC-08: Responsive check (viewport resize)
88
+
89
+ ### P2 Edge cases:
90
+ - TC-09: Edge cases relevant to the feature
91
+ - TC-10: Back navigation / breadcrumbs
92
+
93
+ Return ONLY JavaScript code. No markdown fences. No explanation.`;
67
94
  }
@@ -43,9 +43,12 @@ export async function ensureE2EStructure(projectDir) {
43
43
  }
44
44
  }
45
45
 
46
- export async function writePlaywrightConfig(projectDir, config) {
46
+ export async function writePlaywrightConfig(
47
+ projectDir,
48
+ config,
49
+ overrideBaseUrl,
50
+ ) {
47
51
  const configPath = path.join(projectDir, "e2e", "playwright.config.js");
48
- if (await fileExists(configPath)) return configPath;
49
52
 
50
53
  const envUrls = {};
51
54
  if (config.environments) {
@@ -54,6 +57,10 @@ export async function writePlaywrightConfig(projectDir, config) {
54
57
  }
55
58
  }
56
59
 
60
+ const baseUrlExpr = overrideBaseUrl
61
+ ? `"${overrideBaseUrl}"`
62
+ : `ENV_URLS[process.env.E2E_ENV || "default"] || "http://localhost:3000"`;
63
+
57
64
  const content = `const { defineConfig, devices } = require("@playwright/test");
58
65
 
59
66
  const ENV_URLS = ${JSON.stringify(envUrls, null, 2)};
@@ -61,19 +68,25 @@ const ENV_URLS = ${JSON.stringify(envUrls, null, 2)};
61
68
  module.exports = defineConfig({
62
69
  testDir: "./tests",
63
70
  fullyParallel: false,
64
- retries: 1,
71
+ retries: 0,
65
72
  workers: 1,
73
+ timeout: 60000,
74
+ expect: { timeout: 10000 },
66
75
  reporter: [
67
- ["html", { open: "never", outputFolder: "../qabot-reports/playwright" }],
76
+ ["line"],
68
77
  ["json", { outputFile: "../qabot-reports/playwright/results.json" }],
69
78
  ],
70
79
  use: {
71
- baseURL: ENV_URLS[process.env.E2E_ENV || "default"] || "http://localhost:3000",
80
+ baseURL: ${baseUrlExpr},
72
81
  trace: "on-first-retry",
73
82
  screenshot: "on",
83
+ video: "retain-on-failure",
74
84
  ignoreHTTPSErrors: true,
75
- actionTimeout: 10000,
85
+ actionTimeout: 15000,
76
86
  navigationTimeout: 30000,
87
+ launchOptions: {
88
+ slowMo: 100,
89
+ },
77
90
  },
78
91
  projects: [
79
92
  {
@@ -90,7 +103,6 @@ module.exports = defineConfig({
90
103
 
91
104
  export async function writeAuthHelper(projectDir, config) {
92
105
  const helperPath = path.join(projectDir, "e2e", "helpers", "auth.js");
93
- if (await fileExists(helperPath)) return;
94
106
 
95
107
  const authProvider = config.auth?.provider || "none";
96
108
  const content = `const { expect } = require("@playwright/test");
@@ -100,8 +112,8 @@ async function login(page, baseURL) {
100
112
  const password = process.env.E2E_TEST_PASSWORD || "";
101
113
 
102
114
  if (!email || !password) {
103
- console.warn("E2E_TEST_EMAIL or E2E_TEST_PASSWORD not set — skipping login");
104
- return;
115
+ console.warn("[QABot] E2E_TEST_EMAIL or E2E_TEST_PASSWORD not set — skipping login");
116
+ return false;
105
117
  }
106
118
 
107
119
  await page.goto(baseURL || "/");
@@ -127,14 +139,16 @@ async function login(page, baseURL) {
127
139
  const submitBtn = page.getByRole("button", { name: /continue|log in|sign in|submit/i });
128
140
  await submitBtn.click();
129
141
  await page.waitForLoadState("networkidle");
142
+ await page.waitForTimeout(2000);
130
143
  }`
131
144
  : `
132
- // Generic login — customize for your auth provider
133
145
  await page.getByLabel(/email/i).fill(email);
134
146
  await page.getByLabel(/password/i).fill(password);
135
147
  await page.getByRole("button", { name: /sign in|log in|submit/i }).click();
136
148
  await page.waitForLoadState("networkidle");`
137
149
  }
150
+
151
+ return true;
138
152
  }
139
153
 
140
154
  module.exports = { login };