quality-intelligence-engine 2.2.17 → 2.2.22
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/dist/bin/qi.js +8 -3
- package/dist/src/adapters/playwright-folder.adapter.js +68 -0
- package/dist/src/adapters/playwright-json.adapter.js +46 -0
- package/dist/src/adapters/playwright.adapter.js +36 -0
- package/dist/src/cli/qi-test.js +20 -0
- package/dist/src/cli/qi.js +59 -0
- package/dist/src/cli/ui/divider.js +10 -0
- package/dist/src/cli/ui/failureLogger.js +24 -0
- package/dist/src/configLoader.js +60 -0
- package/dist/src/console/issue-view.js +81 -0
- package/dist/src/failure-analysis/failure-analyzer.js +38 -0
- package/dist/src/final-run.js +131 -0
- package/dist/src/final-runner.js +27 -0
- package/dist/src/fixtures/networkCollector.js +18 -0
- package/dist/src/history/failure-history.store.js +25 -0
- package/dist/src/history/failure-history.types.js +4 -0
- package/dist/src/history/failure-trend.analyzer.js +31 -0
- package/dist/src/index.js +10 -0
- package/dist/src/integrations/ci/ci-annotator.js +26 -0
- package/dist/src/intelligence/confidence-calibration.engine.js +21 -0
- package/dist/src/intelligence/decision-intelligence/confidence.engine.js +16 -0
- package/dist/src/intelligence/decision-intelligence/decision.config.js +21 -0
- package/dist/src/intelligence/decision-intelligence/decision.engine.js +23 -0
- package/dist/src/intelligence/decision-intelligence/decision.types.js +2 -0
- package/dist/src/intelligence/failure-fingerprinting/failure.classifier.js +32 -0
- package/dist/src/intelligence/failure-fingerprinting/failure.store.js +17 -0
- package/dist/src/intelligence/failure-fingerprinting/failure.tracker.js +19 -0
- package/dist/src/intelligence/failure-fingerprinting/fingerprint.generator.js +28 -0
- package/dist/src/intelligence/failure-fingerprinting/fingerprint.types.js +2 -0
- package/dist/src/intelligence/flakiness-intelligence/flakiness.analyzer.js +21 -0
- package/dist/src/intelligence/flakiness-intelligence/flakiness.fingerprint.js +16 -0
- package/dist/src/intelligence/flakiness-intelligence/flakiness.store.js +24 -0
- package/dist/src/intelligence/flakiness-intelligence/flakiness.tracker.js +28 -0
- package/dist/src/intelligence/flakiness-intelligence/flakiness.types.js +2 -0
- package/dist/src/intelligence/intelligence.pipeline.js +17 -0
- package/dist/src/intelligence/llm-explainer.js +19 -0
- package/dist/src/intelligence/root-cause/rootcause.engine.js +25 -0
- package/dist/src/intelligence/trend-intelligence/trend.engine.js +25 -0
- package/dist/src/markdownWriter.js +64 -0
- package/dist/src/normalizer.js +17 -0
- package/dist/src/passAnalyzer.js +38 -0
- package/dist/src/pipeline/ai.summarizer.js +32 -0
- package/dist/src/pipeline/failure-analysis.pipeline.js +11 -0
- package/dist/src/pipeline/failure-grouping.pipeline.js +59 -0
- package/dist/src/playwright/qi.reporter.js +17 -0
- package/dist/src/reporter.js +46 -0
- package/dist/src/rules.js +32 -0
- package/dist/src/runManager.js +19 -0
- package/dist/src/runner.js +13 -0
- package/dist/src/runtime/networkCollector.js +34 -0
- package/dist/src/stackParser.js +17 -0
- package/dist/src/test-run.js +48 -0
- package/dist/src/types/analysis-result.js +2 -0
- package/dist/src/types/failure.types.js +3 -0
- package/dist/src/types/playwright-failure.js +2 -0
- package/dist/src/types.js +5 -0
- package/dist/src/utils/artifact.locator.js +35 -0
- package/dist/src/utils/confidence-constants.js +90 -0
- package/dist/src/utils/file-utils.js +118 -0
- package/dist/src/v2/llm/llm-advisor.js +17 -0
- package/dist/src/v2/pipeline/v2-intelligence.pipeline.js +17 -0
- package/dist/src/v2/self-healing/self-healer.js +44 -0
- package/dist/src/v2/trace-intelligence/trace-analyzer.js +33 -0
- package/dist/src/v2-test-run.js +59 -0
- package/package.json +1 -1
package/dist/bin/qi.js
CHANGED
|
@@ -13,10 +13,15 @@ async function main() {
|
|
|
13
13
|
const args = process.argv.slice(2);
|
|
14
14
|
const playwrightResultsDir = args.find(a => !a.startsWith("--")) ?? "playwright-results";
|
|
15
15
|
const showIssues = args.includes("--issue") || args.includes("--issues");
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* __dirname → dist/bin
|
|
18
|
+
* projectRoot → dist
|
|
19
|
+
*/
|
|
17
20
|
const projectRoot = path_1.default.resolve(__dirname, "..");
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
/**
|
|
22
|
+
* final-run.ts → dist/final-run.js
|
|
23
|
+
* (this file IS published)
|
|
24
|
+
*/
|
|
20
25
|
const finalRunPath = path_1.default.join(projectRoot, "final-run.js");
|
|
21
26
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
22
27
|
const { runFinal } = require(finalRunPath);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/adapters/playwright-folder.adapter.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.parsePlaywrightFailureFolders = parsePlaywrightFailureFolders;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
/**
|
|
11
|
+
* Improved Playwright failure folder parser
|
|
12
|
+
* Focus: reduce UNKNOWN classifications
|
|
13
|
+
*/
|
|
14
|
+
function parsePlaywrightFailureFolders(resultsDir) {
|
|
15
|
+
const fullDir = path_1.default.resolve(resultsDir);
|
|
16
|
+
if (!fs_1.default.existsSync(fullDir)) {
|
|
17
|
+
throw new Error(`Playwright results directory not found: ${fullDir}`);
|
|
18
|
+
}
|
|
19
|
+
const entries = fs_1.default.readdirSync(fullDir, { withFileTypes: true });
|
|
20
|
+
const failures = [];
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (!entry.isDirectory())
|
|
23
|
+
continue;
|
|
24
|
+
const name = entry.name;
|
|
25
|
+
if (!name.includes("FAIL"))
|
|
26
|
+
continue;
|
|
27
|
+
const lower = name.toLowerCase();
|
|
28
|
+
let classification = "UNKNOWN";
|
|
29
|
+
// 🔢 Numeric / data mismatches
|
|
30
|
+
if (lower.includes("numeric-mismatch") ||
|
|
31
|
+
lower.includes("count") ||
|
|
32
|
+
lower.includes("total") ||
|
|
33
|
+
lower.includes("sum") ||
|
|
34
|
+
lower.includes("price")) {
|
|
35
|
+
classification = "NUMERIC_MISMATCH";
|
|
36
|
+
}
|
|
37
|
+
// 🔐 Auth / permission issues
|
|
38
|
+
else if (lower.includes("unauthorized") ||
|
|
39
|
+
lower.includes("forbidden") ||
|
|
40
|
+
lower.includes("permission")) {
|
|
41
|
+
classification = "AUTH_BUG";
|
|
42
|
+
}
|
|
43
|
+
// ⏱ Timeouts / waits
|
|
44
|
+
else if (lower.includes("timeout") ||
|
|
45
|
+
lower.includes("timed-out") ||
|
|
46
|
+
lower.includes("wait")) {
|
|
47
|
+
classification = "TIMEOUT";
|
|
48
|
+
}
|
|
49
|
+
// 🧭 UI / locator issues
|
|
50
|
+
else if (lower.includes("locator") ||
|
|
51
|
+
lower.includes("not-visible") ||
|
|
52
|
+
lower.includes("hidden") ||
|
|
53
|
+
lower.includes("detached")) {
|
|
54
|
+
classification = "UI_STATE";
|
|
55
|
+
}
|
|
56
|
+
// 🔁 Retry / flaky signal
|
|
57
|
+
else if (lower.includes("retry")) {
|
|
58
|
+
classification = "FLAKY";
|
|
59
|
+
}
|
|
60
|
+
failures.push({
|
|
61
|
+
testName: name,
|
|
62
|
+
classification,
|
|
63
|
+
failureType: classification,
|
|
64
|
+
message: `Derived from Playwright folder name: ${name}`
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return failures;
|
|
68
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/adapters/playwright-json.adapter.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.parsePlaywrightJsonReport = parsePlaywrightJsonReport;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
/**
|
|
11
|
+
* Converts Playwright JSON report → FailureAnalysisResult[]
|
|
12
|
+
*/
|
|
13
|
+
function parsePlaywrightJsonReport(reportPath) {
|
|
14
|
+
const fullPath = path_1.default.resolve(reportPath);
|
|
15
|
+
const raw = fs_1.default.readFileSync(fullPath, "utf-8");
|
|
16
|
+
const report = JSON.parse(raw);
|
|
17
|
+
const failures = [];
|
|
18
|
+
for (const suite of report.suites ?? []) {
|
|
19
|
+
for (const spec of suite.specs ?? []) {
|
|
20
|
+
for (const test of spec.tests ?? []) {
|
|
21
|
+
for (const result of test.results ?? []) {
|
|
22
|
+
if (result.status === "failed") {
|
|
23
|
+
const error = result.error?.message ?? "";
|
|
24
|
+
let classification = "UNKNOWN";
|
|
25
|
+
if (error.includes("toHaveCount")) {
|
|
26
|
+
classification = "NUMERIC_MISMATCH";
|
|
27
|
+
}
|
|
28
|
+
else if (error.includes("timeout")) {
|
|
29
|
+
classification = "TIMEOUT";
|
|
30
|
+
}
|
|
31
|
+
else if (error.includes("locator")) {
|
|
32
|
+
classification = "LOCATOR_CHANGED";
|
|
33
|
+
}
|
|
34
|
+
failures.push({
|
|
35
|
+
testName: test.title,
|
|
36
|
+
classification,
|
|
37
|
+
failureType: classification,
|
|
38
|
+
message: error
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return failures;
|
|
46
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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.readPlaywrightFailures = readPlaywrightFailures;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function readPlaywrightFailures(reportDir) {
|
|
10
|
+
const reportFile = path_1.default.join(reportDir, 'report.json');
|
|
11
|
+
if (!fs_1.default.existsSync(reportFile)) {
|
|
12
|
+
throw new Error(`Playwright JSON report not found.\nExpected at: ${reportFile}`);
|
|
13
|
+
}
|
|
14
|
+
const raw = JSON.parse(fs_1.default.readFileSync(reportFile, 'utf-8'));
|
|
15
|
+
const failures = [];
|
|
16
|
+
for (const suite of raw.suites ?? []) {
|
|
17
|
+
for (const spec of suite.specs ?? []) {
|
|
18
|
+
for (const test of spec.tests ?? []) {
|
|
19
|
+
const failedResults = test.results?.filter((r) => r.status === 'failed');
|
|
20
|
+
if (!failedResults || failedResults.length === 0)
|
|
21
|
+
continue;
|
|
22
|
+
const firstFailure = failedResults[0];
|
|
23
|
+
const error = firstFailure.error;
|
|
24
|
+
failures.push({
|
|
25
|
+
testName: spec.title,
|
|
26
|
+
errorMessage: error?.message ?? 'Unknown error',
|
|
27
|
+
stack: error?.stack,
|
|
28
|
+
filePath: error?.location?.file ?? spec.file,
|
|
29
|
+
lineNumber: error?.location?.line ?? spec.line ?? 0,
|
|
30
|
+
retryCount: failedResults.length - 1
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return failures;
|
|
36
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runQiTest = runQiTest;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
function runQiTest(args) {
|
|
6
|
+
const playwrightArgs = [
|
|
7
|
+
'playwright',
|
|
8
|
+
'test',
|
|
9
|
+
'--config',
|
|
10
|
+
'playwright.config.ts',
|
|
11
|
+
...args
|
|
12
|
+
];
|
|
13
|
+
const child = (0, child_process_1.spawn)('npx', playwrightArgs, {
|
|
14
|
+
stdio: 'inherit',
|
|
15
|
+
shell: true
|
|
16
|
+
});
|
|
17
|
+
child.on('exit', code => {
|
|
18
|
+
process.exit(code ?? 0);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
// src/cli/qi.ts
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.runQiCli = runQiCli;
|
|
6
|
+
const runner_1 = require("../runner");
|
|
7
|
+
/**
|
|
8
|
+
* Core CLI runner (reusable)
|
|
9
|
+
*/
|
|
10
|
+
async function runQiCli(input) {
|
|
11
|
+
const results = await (0, runner_1.runQualityIntelligence)(input);
|
|
12
|
+
if (!Array.isArray(results) || results.length === 0) {
|
|
13
|
+
console.log("No failures detected.");
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
for (const f of results) {
|
|
17
|
+
const testName = f.testName ?? "UNKNOWN_TEST";
|
|
18
|
+
const isExpected = testName.includes("EXPECTED");
|
|
19
|
+
console.log("=================================");
|
|
20
|
+
console.log(`Test Name : ${testName}${isExpected ? " (EXPECTED)" : ""}`);
|
|
21
|
+
console.log(`Failure Type : ${f.failureType ?? f.classification}`);
|
|
22
|
+
console.log(`Confidence : ${f.confidence ?? "N/A"}\n`);
|
|
23
|
+
if (f.rootCause?.length) {
|
|
24
|
+
console.log("Root Cause:");
|
|
25
|
+
for (const line of f.rootCause) {
|
|
26
|
+
console.log(` - ${line}`);
|
|
27
|
+
}
|
|
28
|
+
console.log();
|
|
29
|
+
}
|
|
30
|
+
if (f.evidence?.length) {
|
|
31
|
+
console.log("Evidence:");
|
|
32
|
+
for (const line of f.evidence) {
|
|
33
|
+
console.log(` - ${line}`);
|
|
34
|
+
}
|
|
35
|
+
console.log();
|
|
36
|
+
}
|
|
37
|
+
if (f.diagnosis?.length) {
|
|
38
|
+
console.log("Diagnosis:");
|
|
39
|
+
for (const line of f.diagnosis) {
|
|
40
|
+
console.log(` - ${line}`);
|
|
41
|
+
}
|
|
42
|
+
console.log();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* CLI ENTRY POINT
|
|
48
|
+
* Allows: npx qi <path>
|
|
49
|
+
*/
|
|
50
|
+
async function main() {
|
|
51
|
+
const input = process.argv[2] ?? "playwright-results";
|
|
52
|
+
await runQiCli(input);
|
|
53
|
+
}
|
|
54
|
+
if (process.argv[1]?.includes("qi")) {
|
|
55
|
+
main().catch(err => {
|
|
56
|
+
console.error("QI CLI failed:", err);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.divider = divider;
|
|
4
|
+
function divider(title, width = 60) {
|
|
5
|
+
const line = '━'.repeat(width);
|
|
6
|
+
if (!title) {
|
|
7
|
+
return `\n${line}\n`;
|
|
8
|
+
}
|
|
9
|
+
return `\n${line}\n${title}\n${line}\n`;
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logFailure = logFailure;
|
|
4
|
+
function logFailure(input) {
|
|
5
|
+
const confidencePercent = Math.round(input.confidence * 100);
|
|
6
|
+
console.log(`
|
|
7
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
8
|
+
❌ Automation Test Failure
|
|
9
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
10
|
+
|
|
11
|
+
Test Name : ${input.testName}
|
|
12
|
+
Failure Type : ${input.failureType}
|
|
13
|
+
Severity : ${input.severity}
|
|
14
|
+
Priority : ${input.priority}
|
|
15
|
+
Confidence : ${confidencePercent}%
|
|
16
|
+
|
|
17
|
+
Failure Location:
|
|
18
|
+
- Test File : ${input.file}
|
|
19
|
+
- Line Number : ${input.line}
|
|
20
|
+
|
|
21
|
+
Root Cause:
|
|
22
|
+
- ${input.rootCause}
|
|
23
|
+
`);
|
|
24
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadConfig = loadConfig;
|
|
4
|
+
const file_utils_1 = require("./utils/file-utils");
|
|
5
|
+
/**
|
|
6
|
+
* Validate configuration object structure
|
|
7
|
+
*/
|
|
8
|
+
function validateConfig(config) {
|
|
9
|
+
if (typeof config !== 'object' || config === null) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
const cfg = config;
|
|
13
|
+
// Validate engine section
|
|
14
|
+
if (typeof cfg.engine !== 'object' ||
|
|
15
|
+
cfg.engine === null) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
const engine = cfg.engine;
|
|
19
|
+
if (typeof engine.mode !== 'string' ||
|
|
20
|
+
typeof engine.confidenceThresholds !== 'object') {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Load and validate configuration
|
|
27
|
+
*/
|
|
28
|
+
function loadConfig() {
|
|
29
|
+
try {
|
|
30
|
+
const configPath = (0, file_utils_1.resolvePath)('config', 'agent.config.json');
|
|
31
|
+
const config = (0, file_utils_1.readJsonFile)(configPath);
|
|
32
|
+
if (!validateConfig(config)) {
|
|
33
|
+
throw new Error('Invalid configuration structure');
|
|
34
|
+
}
|
|
35
|
+
return config;
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error('❌ Failed to load configuration:', error);
|
|
39
|
+
console.error('\n💡 Using default configuration');
|
|
40
|
+
// Return safe defaults
|
|
41
|
+
return {
|
|
42
|
+
engine: {
|
|
43
|
+
mode: 'standard',
|
|
44
|
+
confidenceThresholds: {
|
|
45
|
+
fail: 0.85,
|
|
46
|
+
passRisk: 0.6
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
output: {
|
|
50
|
+
writeMarkdown: true,
|
|
51
|
+
maxHistoryRuns: 10
|
|
52
|
+
},
|
|
53
|
+
passAnalysis: {
|
|
54
|
+
enable: true,
|
|
55
|
+
reportFlakyPass: true,
|
|
56
|
+
reportApiWarnings: true
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/console/issue-view.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.extractIssues = extractIssues;
|
|
5
|
+
exports.filterIssues = filterIssues;
|
|
6
|
+
exports.printIssues = printIssues;
|
|
7
|
+
/* ===================================================== */
|
|
8
|
+
/* 🔹 EXISTING LOGIC (RESTORED – NO BEHAVIOR CHANGE) */
|
|
9
|
+
/* ===================================================== */
|
|
10
|
+
function extractIssues(groupedCounts, confidence) {
|
|
11
|
+
return Object.entries(groupedCounts).map(([type, count]) => ({
|
|
12
|
+
type,
|
|
13
|
+
count,
|
|
14
|
+
confidence,
|
|
15
|
+
autoHealAllowed: type === "FLAKY",
|
|
16
|
+
owner: {
|
|
17
|
+
team: type === "FLAKY"
|
|
18
|
+
? "QA"
|
|
19
|
+
: type === "NUMERIC_MISMATCH"
|
|
20
|
+
? "Backend"
|
|
21
|
+
: "Frontend"
|
|
22
|
+
},
|
|
23
|
+
summary: type === "FLAKY"
|
|
24
|
+
? "Test behavior is unstable across executions"
|
|
25
|
+
: type === "NUMERIC_MISMATCH"
|
|
26
|
+
? "Numeric values returned by backend do not match expected results"
|
|
27
|
+
: "Test failures detected but root cause is unclear",
|
|
28
|
+
reasoning: type === "FLAKY"
|
|
29
|
+
? ["Failures disappear on retry", "Timing or synchronization issues detected"]
|
|
30
|
+
: type === "NUMERIC_MISMATCH"
|
|
31
|
+
? ["API response data differs from expected calculation"]
|
|
32
|
+
: ["Failure artifacts exist", "Signals insufficient for confident classification"],
|
|
33
|
+
recommendations: type === "FLAKY"
|
|
34
|
+
? ["Improve waits or synchronization", "Stabilize test setup and assertions"]
|
|
35
|
+
: type === "NUMERIC_MISMATCH"
|
|
36
|
+
? ["Verify backend calculation logic", "Validate API response against expected schema"]
|
|
37
|
+
: ["Review screenshot and trace manually"]
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
function filterIssues(issues, filter) {
|
|
41
|
+
if (!filter.enabled)
|
|
42
|
+
return [];
|
|
43
|
+
let result = issues;
|
|
44
|
+
if (filter.keyword) {
|
|
45
|
+
result = result.filter(i => i.type.toLowerCase().includes(filter.keyword.toLowerCase()));
|
|
46
|
+
}
|
|
47
|
+
if (filter.topN) {
|
|
48
|
+
result = result.slice(0, filter.topN);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
/* ===================================================== */
|
|
53
|
+
/* 🔹 UPDATED PRINTER (NEW DESIGN) */
|
|
54
|
+
/* ===================================================== */
|
|
55
|
+
function printIssues(issues, failureFolder) {
|
|
56
|
+
if (!issues.length) {
|
|
57
|
+
console.log("No matching issues found.");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
issues.forEach((issue, index) => {
|
|
61
|
+
console.log(`\nISSUE #${index + 1}`);
|
|
62
|
+
console.log(`Type : ${issue.type}`);
|
|
63
|
+
console.log(`Occurrences : ${issue.count}`);
|
|
64
|
+
console.log(`Confidence : ${issue.confidence}`);
|
|
65
|
+
console.log(`Domain : ${issue.owner?.team ?? "UNKNOWN"}`);
|
|
66
|
+
console.log("\nWHAT IS THE ISSUE:");
|
|
67
|
+
console.log(` ${issue.summary}`);
|
|
68
|
+
console.log("\nWHY IT IS HAPPENING:");
|
|
69
|
+
issue.reasoning.forEach((r) => console.log(` • ${r}`));
|
|
70
|
+
console.log("\nWHAT TO DO NEXT:");
|
|
71
|
+
issue.recommendations.forEach((r) => console.log(` • ${r}`));
|
|
72
|
+
if (issue.autoHealAllowed) {
|
|
73
|
+
console.log("\nSelf-Healing:");
|
|
74
|
+
console.log(" Allowed : YES (retry-based stabilization is safe)");
|
|
75
|
+
}
|
|
76
|
+
console.log("\nEvidence:");
|
|
77
|
+
console.log(` Screenshot : ${failureFolder}/test-failed-1.png`);
|
|
78
|
+
console.log(` Trace : npx playwright show-trace ${failureFolder}/trace.zip`);
|
|
79
|
+
console.log("-".repeat(70));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/failure-analysis/failure-analyzer.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.FailureAnalyzer = void 0;
|
|
5
|
+
/**
|
|
6
|
+
* FailureAnalyzer
|
|
7
|
+
* ----------------
|
|
8
|
+
* Phase 1 core analyzer.
|
|
9
|
+
* This class is intentionally simple and stable.
|
|
10
|
+
* Do NOT add history, trends, or intelligence here.
|
|
11
|
+
*/
|
|
12
|
+
class FailureAnalyzer {
|
|
13
|
+
analyze(input) {
|
|
14
|
+
if (!input || !Array.isArray(input)) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
return input.map((item) => {
|
|
18
|
+
// Minimal safe classification logic
|
|
19
|
+
if (item?.type === "NUMERIC_MISMATCH") {
|
|
20
|
+
return {
|
|
21
|
+
classification: "NUMERIC_MISMATCH",
|
|
22
|
+
message: item.message
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (item?.type === "API_BUG") {
|
|
26
|
+
return {
|
|
27
|
+
classification: "API_BUG",
|
|
28
|
+
message: item.message
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
classification: "UNKNOWN",
|
|
33
|
+
message: item?.message
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.FailureAnalyzer = FailureAnalyzer;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/final-run.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.runFinal = runFinal;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const runner_1 = require("./runner");
|
|
11
|
+
const v2_intelligence_pipeline_1 = require("./v2/pipeline/v2-intelligence.pipeline");
|
|
12
|
+
/* ===================================================== */
|
|
13
|
+
/* 🔹 STRONG METADATA DERIVATION (NO GUESSING) */
|
|
14
|
+
/* ===================================================== */
|
|
15
|
+
function deriveTestMetadata(failureFolder) {
|
|
16
|
+
// 1️⃣ Try spec file (best)
|
|
17
|
+
try {
|
|
18
|
+
const files = fs_1.default.readdirSync(failureFolder, { recursive: true });
|
|
19
|
+
const spec = files.find(f => f.endsWith(".spec.ts") || f.endsWith(".spec.js"));
|
|
20
|
+
if (spec) {
|
|
21
|
+
return {
|
|
22
|
+
testName: path_1.default.basename(spec).replace(/\.(spec\.ts|spec\.js)$/, ""),
|
|
23
|
+
testLocation: `${spec}:?`
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
// 2️⃣ Fallback: derive from folder name (deterministic)
|
|
29
|
+
const folderName = path_1.default.basename(failureFolder);
|
|
30
|
+
const cleaned = folderName
|
|
31
|
+
.replace(/--chromium|--firefox|--webkit/g, "")
|
|
32
|
+
.replace(/-FAIL.*/i, "")
|
|
33
|
+
.replace(/[-_]/g, " ")
|
|
34
|
+
.trim();
|
|
35
|
+
return {
|
|
36
|
+
testName: cleaned || "Unknown test",
|
|
37
|
+
testLocation: "Derived from Playwright results"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/* ===================================================== */
|
|
41
|
+
/* 🔹 FAILURE FALLBACK CLASSIFIER (SAFE) */
|
|
42
|
+
/* ===================================================== */
|
|
43
|
+
function normalizeFailure(dominant, failureFolder) {
|
|
44
|
+
if (dominant !== "UNCLASSIFIED_FAILURE") {
|
|
45
|
+
return {
|
|
46
|
+
primary: dominant,
|
|
47
|
+
domain: dominant === "NUMERIC_MISMATCH"
|
|
48
|
+
? "API"
|
|
49
|
+
: dominant === "FLAKY"
|
|
50
|
+
? "TEST"
|
|
51
|
+
: dominant === "ASSERTION_FAILURE"
|
|
52
|
+
? "UI"
|
|
53
|
+
: "UNKNOWN"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const keywords = ["invalid", "required", "rejected", "format", "error", "fail"];
|
|
57
|
+
const name = failureFolder.toLowerCase();
|
|
58
|
+
if (keywords.some(k => name.includes(k))) {
|
|
59
|
+
return {
|
|
60
|
+
primary: "UI_VALIDATION_FAILURE",
|
|
61
|
+
domain: "UI"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
primary: "UNKNOWN",
|
|
66
|
+
domain: "UNKNOWN"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function line(char = "=", count = 70) {
|
|
70
|
+
console.log(char.repeat(count));
|
|
71
|
+
}
|
|
72
|
+
/* ===================================================== */
|
|
73
|
+
/* ✅ MAIN EXECUTION – NO LOGIC REGRESSION */
|
|
74
|
+
/* ===================================================== */
|
|
75
|
+
async function runFinal() {
|
|
76
|
+
const playwrightResultsDir = "playwright-results";
|
|
77
|
+
const folders = fs_1.default.readdirSync(playwrightResultsDir).filter(f => fs_1.default.statSync(path_1.default.join(playwrightResultsDir, f)).isDirectory());
|
|
78
|
+
if (!folders.length) {
|
|
79
|
+
console.error("No Playwright failure folders found.");
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const failureFolder = path_1.default.join(playwrightResultsDir, folders[0]);
|
|
83
|
+
const meta = deriveTestMetadata(failureFolder);
|
|
84
|
+
const v1 = await (0, runner_1.runQualityIntelligence)(playwrightResultsDir);
|
|
85
|
+
const normalized = normalizeFailure(v1.dominantCategory, failureFolder);
|
|
86
|
+
line();
|
|
87
|
+
console.log("QUALITY INTELLIGENCE – FINAL PROJECT OUTPUT");
|
|
88
|
+
line();
|
|
89
|
+
console.log(`Test Case : ${meta.testName}`);
|
|
90
|
+
console.log(`Test Location : ${meta.testLocation}`);
|
|
91
|
+
console.log(`Primary Failure : ${normalized.primary}`);
|
|
92
|
+
console.log(`Failure Domain : ${normalized.domain}`);
|
|
93
|
+
console.log(`Confidence Score : ${v1.confidence}`);
|
|
94
|
+
console.log(`Trend Signal : ${v1.trend?.signal ?? "STABLE"}`);
|
|
95
|
+
line("-");
|
|
96
|
+
console.log("WHAT FAILED:");
|
|
97
|
+
console.log(normalized.primary === "UI_VALIDATION_FAILURE"
|
|
98
|
+
? " UI validation error occurred during user input"
|
|
99
|
+
: normalized.primary === "NUMERIC_MISMATCH"
|
|
100
|
+
? " Numeric values returned by backend do not match expected results"
|
|
101
|
+
: " Test failure detected");
|
|
102
|
+
line("-");
|
|
103
|
+
console.log("WHY IT FAILED:");
|
|
104
|
+
v1.intelligence.confidence.explanation.forEach(e => console.log(` • ${e}`));
|
|
105
|
+
line("-");
|
|
106
|
+
console.log("WHAT TO DO NEXT:");
|
|
107
|
+
if (normalized.domain === "UI") {
|
|
108
|
+
console.log(" • Review UI validation rules and test assertions");
|
|
109
|
+
}
|
|
110
|
+
else if (normalized.domain === "API") {
|
|
111
|
+
console.log(" • Verify backend calculations and API responses");
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.log(" • Review screenshot and trace manually");
|
|
115
|
+
}
|
|
116
|
+
const v2 = await (0, v2_intelligence_pipeline_1.runV2Intelligence)(failureFolder, normalized.primary, "Derived from Playwright failure artifacts");
|
|
117
|
+
line("-");
|
|
118
|
+
console.log("TRACE & SELF-HEALING INTELLIGENCE");
|
|
119
|
+
console.log("Evidence:");
|
|
120
|
+
v2.trace.screenshots.forEach(s => console.log(` Screenshot: ${s}`));
|
|
121
|
+
line("-");
|
|
122
|
+
console.log("Self-Healing Decision:");
|
|
123
|
+
console.log(" Allowed : NO (requires human decision)");
|
|
124
|
+
line();
|
|
125
|
+
}
|
|
126
|
+
/* ===================================================== */
|
|
127
|
+
/* CLI SUPPORT */
|
|
128
|
+
/* ===================================================== */
|
|
129
|
+
if (require.main === module) {
|
|
130
|
+
runFinal();
|
|
131
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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.runFinalOutput = runFinalOutput;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function runFinalOutput(options) {
|
|
10
|
+
const args = [];
|
|
11
|
+
if (options.showIssues) {
|
|
12
|
+
args.push("--issue");
|
|
13
|
+
}
|
|
14
|
+
// run existing final-run.ts via ts-node / node
|
|
15
|
+
const scriptPath = path_1.default.join(__dirname, "final-run.ts");
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
const proc = (0, child_process_1.spawn)("node", ["--loader", "ts-node/esm", scriptPath, ...args], {
|
|
18
|
+
stdio: "inherit"
|
|
19
|
+
});
|
|
20
|
+
proc.on("close", code => {
|
|
21
|
+
if (code === 0)
|
|
22
|
+
resolve();
|
|
23
|
+
else
|
|
24
|
+
reject(new Error(`final-run exited with ${code}`));
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.test = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
exports.test = test_1.test.extend({
|
|
6
|
+
networkResponses: async ({ page }, use) => {
|
|
7
|
+
const responses = [];
|
|
8
|
+
page.on('response', async (response) => {
|
|
9
|
+
responses.push({
|
|
10
|
+
url: response.url(),
|
|
11
|
+
method: response.request().method(),
|
|
12
|
+
status: response.status(),
|
|
13
|
+
ok: response.ok()
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
await use(responses);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
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.saveFailureHistory = saveFailureHistory;
|
|
7
|
+
exports.loadFailureHistory = loadFailureHistory;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const HISTORY_DIR = path_1.default.join(process.cwd(), "artifacts/failure-history");
|
|
11
|
+
function saveFailureHistory(entry) {
|
|
12
|
+
if (!fs_1.default.existsSync(HISTORY_DIR)) {
|
|
13
|
+
fs_1.default.mkdirSync(HISTORY_DIR, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
const filePath = path_1.default.join(HISTORY_DIR, `${entry.runId}.json`);
|
|
16
|
+
fs_1.default.writeFileSync(filePath, JSON.stringify(entry, null, 2));
|
|
17
|
+
}
|
|
18
|
+
function loadFailureHistory() {
|
|
19
|
+
if (!fs_1.default.existsSync(HISTORY_DIR))
|
|
20
|
+
return [];
|
|
21
|
+
return fs_1.default.readdirSync(HISTORY_DIR).map(file => {
|
|
22
|
+
const fullPath = path_1.default.join(HISTORY_DIR, file);
|
|
23
|
+
return JSON.parse(fs_1.default.readFileSync(fullPath, "utf-8"));
|
|
24
|
+
});
|
|
25
|
+
}
|