qa-intelligence 1.1.4 → 1.1.6

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/README.md CHANGED
@@ -27,9 +27,18 @@ npm install # installs qa-intelligence + @playwright/test (see playwr
27
27
  npx playwright install
28
28
  ```
29
29
 
30
+ For **local runs with AI failure analysis** (generates `ai.txt` in artifacts — needed for `qa-intelligence-diff`), add to `playwright/.env`:
31
+
32
+ ```bash
33
+ AI_ANALYSIS=true
34
+ OPENAI_API_KEY=sk-...
35
+ ```
36
+
37
+ CI sets `AI_ANALYSIS=true` in the workflow; locally you must enable it yourself.
38
+
30
39
  `init` scaffolds:
31
40
 
32
- - `playwright/` — `.env.example`, `package.json`, `tsconfig.json`, `playwright.config.ts`, example test
41
+ - `playwright/` — `.env.example`, `.gitignore`, `package.json`, `tsconfig.json`, `playwright.config.ts`, example test
33
42
  - `.github/workflows/qa-intelligence.yml` — PR diff, history, and comment (skip with `--no-ci`)
34
43
 
35
44
  **Then:** add `OPENAI_API_KEY` to GitHub secrets and set `BASE_URL` in the workflow file.
package/dist/cli/init.js CHANGED
@@ -10,6 +10,7 @@ const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const PLAYWRIGHT_SCAFFOLD = [
12
12
  { template: "playwright/.env.example", dest: "playwright/.env.example" },
13
+ { template: "playwright/.gitignore", dest: "playwright/.gitignore" },
13
14
  { template: "playwright/tsconfig.json", dest: "playwright/tsconfig.json" },
14
15
  {
15
16
  template: "playwright/playwright.config.ts",
@@ -0,0 +1,7 @@
1
+ export interface FailureMeta {
2
+ is_flaky_suspected?: boolean;
3
+ }
4
+ export declare function testKeyFromMetaPath(metaPath: string): string;
5
+ export declare function parseAttemptFromMetaPath(metaPath: string): number;
6
+ /** Pick one meta.json per test: flaky pass attempt, else last failed attempt. */
7
+ export declare function selectMetaFilesForAnalysis(metaPaths: string[], readMeta: (metaPath: string) => FailureMeta | null): string[];
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.testKeyFromMetaPath = testKeyFromMetaPath;
7
+ exports.parseAttemptFromMetaPath = parseAttemptFromMetaPath;
8
+ exports.selectMetaFilesForAnalysis = selectMetaFilesForAnalysis;
9
+ const path_1 = __importDefault(require("path"));
10
+ function testKeyFromMetaPath(metaPath) {
11
+ return path_1.default.dirname(path_1.default.dirname(metaPath));
12
+ }
13
+ function parseAttemptFromMetaPath(metaPath) {
14
+ const normalized = metaPath.replace(/\\/g, "/");
15
+ const match = normalized.match(/\/attempt-(\d+)\/meta\.json$/);
16
+ if (!match)
17
+ return 0;
18
+ const attempt = Number(match[1]);
19
+ return Number.isFinite(attempt) ? attempt : 0;
20
+ }
21
+ function highestAttemptPath(paths) {
22
+ return paths.reduce((best, current) => parseAttemptFromMetaPath(current) >= parseAttemptFromMetaPath(best)
23
+ ? current
24
+ : best);
25
+ }
26
+ /** Pick one meta.json per test: flaky pass attempt, else last failed attempt. */
27
+ function selectMetaFilesForAnalysis(metaPaths, readMeta) {
28
+ const byTest = new Map();
29
+ for (const metaPath of metaPaths) {
30
+ const key = testKeyFromMetaPath(metaPath);
31
+ const group = byTest.get(key) ?? [];
32
+ group.push(metaPath);
33
+ byTest.set(key, group);
34
+ }
35
+ const selected = [];
36
+ for (const group of byTest.values()) {
37
+ const flaky = group.filter((p) => readMeta(p)?.is_flaky_suspected);
38
+ selected.push(highestAttemptPath(flaky.length > 0 ? flaky : group));
39
+ }
40
+ return selected;
41
+ }
@@ -1,4 +1,4 @@
1
- import { Page } from "@playwright/test";
1
+ import type { Page } from "@playwright/test";
2
2
  export declare class BasePage {
3
3
  protected page: Page;
4
4
  constructor(page: Page);
@@ -1,5 +1,5 @@
1
- import { expect } from "@playwright/test";
1
+ declare const expect: import("playwright/test").Expect<{}>;
2
2
  import { env } from "../config/env";
3
3
  import "./testHooks";
4
- export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
4
+ export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions>;
5
5
  export { expect, env };
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.env = exports.expect = exports.test = void 0;
4
- const test_1 = require("@playwright/test");
5
- Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return test_1.expect; } });
4
+ const playwrightTest_1 = require("./playwrightTest");
5
+ const { test: base, expect } = playwrightTest_1.playwrightTest;
6
+ exports.expect = expect;
6
7
  const env_1 = require("../config/env");
7
8
  Object.defineProperty(exports, "env", { enumerable: true, get: function () { return env_1.env; } });
8
9
  require("./testHooks");
9
- exports.test = test_1.test;
10
+ exports.test = base;
@@ -7,6 +7,7 @@ exports.default = globalTeardown;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const failureAnalyzer_1 = require("../ai/failureAnalyzer");
10
+ const selectMetaForAnalysis_1 = require("../lib/selectMetaForAnalysis");
10
11
  function findMetaFiles(dir, results = []) {
11
12
  const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
12
13
  for (const entry of entries) {
@@ -20,6 +21,14 @@ function findMetaFiles(dir, results = []) {
20
21
  }
21
22
  return results;
22
23
  }
24
+ function readMeta(metaPath) {
25
+ try {
26
+ return JSON.parse(fs_1.default.readFileSync(metaPath, "utf8"));
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
23
32
  async function globalTeardown() {
24
33
  if (process.env.AI_ANALYSIS !== "true")
25
34
  return;
@@ -29,7 +38,8 @@ async function globalTeardown() {
29
38
  const metaFiles = findMetaFiles(runDir);
30
39
  if (metaFiles.length === 0)
31
40
  return;
32
- for (const metaPath of metaFiles) {
41
+ const toAnalyze = (0, selectMetaForAnalysis_1.selectMetaFilesForAnalysis)(metaFiles, readMeta);
42
+ for (const metaPath of toAnalyze) {
33
43
  console.log(`Analyzing: ${metaPath}`);
34
44
  try {
35
45
  const analysis = await (0, failureAnalyzer_1.analyzeFailureFile)(metaPath);
@@ -0,0 +1 @@
1
+ export declare const playwrightTest: typeof import("@playwright/test");
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.playwrightTest = void 0;
7
+ const node_module_1 = require("node:module");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ // Resolve @playwright/test from the consumer project (process.cwd()), not from
10
+ // this package's node_modules. Required when qa-intelligence is linked via file:
11
+ // and the library repo has its own devDependency copy of Playwright.
12
+ const requireFromConsumer = (0, node_module_1.createRequire)(node_path_1.default.join(process.cwd(), "package.json"));
13
+ exports.playwrightTest = requireFromConsumer("@playwright/test");
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.step = step;
4
- const test_1 = require("@playwright/test");
4
+ const playwrightTest_1 = require("./playwrightTest");
5
+ const { test } = playwrightTest_1.playwrightTest;
5
6
  async function step(name, fn) {
6
- return await test_1.test.step(name, fn);
7
+ return await test.step(name, fn);
7
8
  }
@@ -1 +1 @@
1
- export declare const test: import("@playwright/test").TestType<import("@playwright/test").PlaywrightTestArgs & import("@playwright/test").PlaywrightTestOptions, import("@playwright/test").PlaywrightWorkerArgs & import("@playwright/test").PlaywrightWorkerOptions>;
1
+ export declare const test: import("playwright/test").TestType<import("playwright/test").PlaywrightTestArgs & import("playwright/test").PlaywrightTestOptions, import("playwright/test").PlaywrightWorkerArgs & import("playwright/test").PlaywrightWorkerOptions>;
@@ -4,11 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.test = void 0;
7
- const test_1 = require("@playwright/test");
7
+ const playwrightTest_1 = require("./playwrightTest");
8
+ const { test: base } = playwrightTest_1.playwrightTest;
8
9
  const fs_1 = __importDefault(require("fs"));
9
10
  const logger_1 = require("../reporting/logger");
10
11
  const path_1 = __importDefault(require("path"));
11
- exports.test = test_1.test;
12
+ exports.test = base;
12
13
  exports.test.afterEach(async ({ page }, testInfo) => {
13
14
  const logFile = (0, logger_1.getLogFilePath)();
14
15
  if (fs_1.default.existsSync(logFile)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qa-intelligence",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "engines": {
5
5
  "node": ">=18"
6
6
  },
@@ -6,6 +6,9 @@
6
6
  },
7
7
  "dependencies": {
8
8
  "@playwright/test": "^1.58.0",
9
- "qa-intelligence": "^1.1.4"
9
+ "qa-intelligence": "^1.1.6"
10
+ },
11
+ "devDependencies": {
12
+ "@types/node": "^22.0.0"
10
13
  }
11
14
  }