donobu 2.47.0 → 2.48.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/dist/assets/generated/version +1 -1
- package/dist/cli/donobu-cli.js +254 -49
- package/dist/cli/donobu-cli.js.map +1 -1
- package/dist/clients/DonobuGptClient.d.ts +25 -0
- package/dist/clients/DonobuGptClient.d.ts.map +1 -0
- package/dist/clients/DonobuGptClient.js +49 -0
- package/dist/clients/DonobuGptClient.js.map +1 -0
- package/dist/clients/GptClientFactory.d.ts.map +1 -1
- package/dist/clients/GptClientFactory.js +4 -0
- package/dist/clients/GptClientFactory.js.map +1 -1
- package/dist/clients/OpenAiGptClient.d.ts +3 -2
- package/dist/clients/OpenAiGptClient.d.ts.map +1 -1
- package/dist/clients/OpenAiGptClient.js +4 -3
- package/dist/clients/OpenAiGptClient.js.map +1 -1
- package/dist/envVars.d.ts +4 -0
- package/dist/envVars.d.ts.map +1 -1
- package/dist/envVars.js +4 -0
- package/dist/envVars.js.map +1 -1
- package/dist/esm/assets/generated/version +1 -1
- package/dist/esm/cli/donobu-cli.js +254 -49
- package/dist/esm/cli/donobu-cli.js.map +1 -1
- package/dist/esm/clients/DonobuGptClient.d.ts +25 -0
- package/dist/esm/clients/DonobuGptClient.d.ts.map +1 -0
- package/dist/esm/clients/DonobuGptClient.js +49 -0
- package/dist/esm/clients/DonobuGptClient.js.map +1 -0
- package/dist/esm/clients/GptClientFactory.d.ts.map +1 -1
- package/dist/esm/clients/GptClientFactory.js +4 -0
- package/dist/esm/clients/GptClientFactory.js.map +1 -1
- package/dist/esm/clients/OpenAiGptClient.d.ts +3 -2
- package/dist/esm/clients/OpenAiGptClient.d.ts.map +1 -1
- package/dist/esm/clients/OpenAiGptClient.js +4 -3
- package/dist/esm/clients/OpenAiGptClient.js.map +1 -1
- package/dist/esm/envVars.d.ts +4 -0
- package/dist/esm/envVars.d.ts.map +1 -1
- package/dist/esm/envVars.js +4 -0
- package/dist/esm/envVars.js.map +1 -1
- package/dist/esm/lib/fixtures/gptClients.d.ts +5 -0
- package/dist/esm/lib/fixtures/gptClients.d.ts.map +1 -1
- package/dist/esm/lib/fixtures/gptClients.js +25 -3
- package/dist/esm/lib/fixtures/gptClients.js.map +1 -1
- package/dist/esm/lib/testExtension.js +2 -2
- package/dist/esm/lib/testExtension.js.map +1 -1
- package/dist/esm/lib/utils/donobuTestStack.d.ts +1 -0
- package/dist/esm/lib/utils/donobuTestStack.d.ts.map +1 -1
- package/dist/esm/lib/utils/donobuTestStack.js +1 -1
- package/dist/esm/lib/utils/donobuTestStack.js.map +1 -1
- package/dist/esm/managers/AdminApiController.d.ts +1 -1
- package/dist/esm/managers/AdminApiController.d.ts.map +1 -1
- package/dist/esm/managers/AdminApiController.js.map +1 -1
- package/dist/esm/managers/CodeGenerator.d.ts.map +1 -1
- package/dist/esm/managers/CodeGenerator.js +3 -0
- package/dist/esm/managers/CodeGenerator.js.map +1 -1
- package/dist/esm/managers/DonobuFlowsManager.d.ts +1 -1
- package/dist/esm/managers/DonobuFlowsManager.d.ts.map +1 -1
- package/dist/esm/managers/DonobuFlowsManager.js +6 -0
- package/dist/esm/managers/DonobuFlowsManager.js.map +1 -1
- package/dist/esm/managers/DonobuStack.d.ts +1 -0
- package/dist/esm/managers/DonobuStack.d.ts.map +1 -1
- package/dist/esm/managers/DonobuStack.js +1 -1
- package/dist/esm/managers/DonobuStack.js.map +1 -1
- package/dist/esm/managers/GptConfigsManager.d.ts +1 -1
- package/dist/esm/managers/GptConfigsManager.d.ts.map +1 -1
- package/dist/esm/managers/GptConfigsManager.js +25 -0
- package/dist/esm/managers/GptConfigsManager.js.map +1 -1
- package/dist/esm/models/GptConfig.d.ts +8 -0
- package/dist/esm/models/GptConfig.d.ts.map +1 -1
- package/dist/esm/models/GptConfig.js +6 -1
- package/dist/esm/models/GptConfig.js.map +1 -1
- package/dist/lib/fixtures/gptClients.d.ts +5 -0
- package/dist/lib/fixtures/gptClients.d.ts.map +1 -1
- package/dist/lib/fixtures/gptClients.js +25 -3
- package/dist/lib/fixtures/gptClients.js.map +1 -1
- package/dist/lib/testExtension.js +2 -2
- package/dist/lib/testExtension.js.map +1 -1
- package/dist/lib/utils/donobuTestStack.d.ts +1 -0
- package/dist/lib/utils/donobuTestStack.d.ts.map +1 -1
- package/dist/lib/utils/donobuTestStack.js +1 -1
- package/dist/lib/utils/donobuTestStack.js.map +1 -1
- package/dist/managers/AdminApiController.d.ts +1 -1
- package/dist/managers/AdminApiController.d.ts.map +1 -1
- package/dist/managers/AdminApiController.js.map +1 -1
- package/dist/managers/CodeGenerator.d.ts.map +1 -1
- package/dist/managers/CodeGenerator.js +3 -0
- package/dist/managers/CodeGenerator.js.map +1 -1
- package/dist/managers/DonobuFlowsManager.d.ts +1 -1
- package/dist/managers/DonobuFlowsManager.d.ts.map +1 -1
- package/dist/managers/DonobuFlowsManager.js +6 -0
- package/dist/managers/DonobuFlowsManager.js.map +1 -1
- package/dist/managers/DonobuStack.d.ts +1 -0
- package/dist/managers/DonobuStack.d.ts.map +1 -1
- package/dist/managers/DonobuStack.js +1 -1
- package/dist/managers/DonobuStack.js.map +1 -1
- package/dist/managers/GptConfigsManager.d.ts +1 -1
- package/dist/managers/GptConfigsManager.d.ts.map +1 -1
- package/dist/managers/GptConfigsManager.js +25 -0
- package/dist/managers/GptConfigsManager.js.map +1 -1
- package/dist/models/GptConfig.d.ts +8 -0
- package/dist/models/GptConfig.d.ts.map +1 -1
- package/dist/models/GptConfig.js +6 -1
- package/dist/models/GptConfig.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1423
|
package/dist/cli/donobu-cli.js
CHANGED
|
@@ -64,13 +64,14 @@ const triageTestFailure_1 = require("../lib/utils/triageTestFailure");
|
|
|
64
64
|
const Logger_1 = require("../utils/Logger");
|
|
65
65
|
const FAILURE_EVIDENCE_PREFIX = 'failure-evidence-';
|
|
66
66
|
const TREATMENT_PLAN_PREFIX = 'treatment-plan-';
|
|
67
|
+
const PLAYWRIGHT_JSON_REPORT_FILENAME = 'report.json';
|
|
67
68
|
/**
|
|
68
69
|
* Execute `npx playwright` while wiring Donobu-specific environment controls.
|
|
69
70
|
* Streams stdout/stderr to the current process so our CLI mirrors the native
|
|
70
71
|
* Playwright experience, and forwards termination signals to keep CI parity.
|
|
71
72
|
*/
|
|
72
73
|
async function runPlaywright(args, envOverrides = {}) {
|
|
73
|
-
Logger_1.appLogger.
|
|
74
|
+
Logger_1.appLogger.debug(`Running Playwright with args: ${JSON.stringify(args)}`);
|
|
74
75
|
if (Object.keys(envOverrides).length > 0) {
|
|
75
76
|
Logger_1.appLogger.debug(`Playwright env overrides: ${JSON.stringify(envOverrides, null, 2)}`);
|
|
76
77
|
}
|
|
@@ -298,20 +299,23 @@ const noopAsync = async () => { };
|
|
|
298
299
|
* wrapper config when necessary so we can append the JSON reporter alongside
|
|
299
300
|
* user-specified reporters.
|
|
300
301
|
*/
|
|
301
|
-
async function ensureJsonReporter(originalArgs) {
|
|
302
|
+
async function ensureJsonReporter(originalArgs, options = {}) {
|
|
302
303
|
const args = [...originalArgs];
|
|
303
|
-
if (injectJsonReporterIntoArgs(args)) {
|
|
304
|
+
if (injectJsonReporterIntoArgs(args, options)) {
|
|
304
305
|
return { args, cleanup: noopAsync };
|
|
305
306
|
}
|
|
306
307
|
const configPath = await resolvePlaywrightConfigPath(args);
|
|
307
308
|
if (!configPath) {
|
|
308
309
|
if (!hasReporterArg(args)) {
|
|
309
|
-
|
|
310
|
+
const reporterValue = options.jsonOutputFile
|
|
311
|
+
? `json=${options.jsonOutputFile}`
|
|
312
|
+
: 'json';
|
|
313
|
+
args.push(`--reporter=${reporterValue}`);
|
|
310
314
|
}
|
|
311
315
|
Logger_1.appLogger.debug('No Playwright config detected; falling back to CLI --reporter=json injection.');
|
|
312
316
|
return { args, cleanup: noopAsync };
|
|
313
317
|
}
|
|
314
|
-
const { configPath: wrapperPath, cleanup } = await createConfigWrapperWithJsonReporter(configPath);
|
|
318
|
+
const { configPath: wrapperPath, cleanup } = await createConfigWrapperWithJsonReporter(configPath, options);
|
|
315
319
|
Logger_1.appLogger.debug(`Augmenting Playwright config at ${configPath} with temporary wrapper ${wrapperPath} to ensure JSON reporter.`);
|
|
316
320
|
const strippedArgs = stripConfigArgs(args);
|
|
317
321
|
const finalArgs = insertConfigArg(strippedArgs, wrapperPath);
|
|
@@ -331,7 +335,7 @@ function hasReporterArg(args) {
|
|
|
331
335
|
}
|
|
332
336
|
return false;
|
|
333
337
|
}
|
|
334
|
-
function injectJsonReporterIntoArgs(args) {
|
|
338
|
+
function injectJsonReporterIntoArgs(args, options = {}) {
|
|
335
339
|
let reporterFlagFound = false;
|
|
336
340
|
for (let i = 0; i < args.length; i += 1) {
|
|
337
341
|
const arg = args[i];
|
|
@@ -342,7 +346,7 @@ function injectJsonReporterIntoArgs(args) {
|
|
|
342
346
|
reporterFlagFound = true;
|
|
343
347
|
const valueIndex = i + 1;
|
|
344
348
|
if (valueIndex < args.length) {
|
|
345
|
-
const { value, changed } = ensureReporterValueHasJson(args[valueIndex]);
|
|
349
|
+
const { value, changed } = ensureReporterValueHasJson(args[valueIndex], options);
|
|
346
350
|
args[valueIndex] = value;
|
|
347
351
|
if (changed) {
|
|
348
352
|
reporterFlagFound = true;
|
|
@@ -354,13 +358,13 @@ function injectJsonReporterIntoArgs(args) {
|
|
|
354
358
|
if (arg.startsWith('--reporter=') || arg.startsWith('-r=')) {
|
|
355
359
|
reporterFlagFound = true;
|
|
356
360
|
const [prefix, rawValue] = arg.split('=', 2);
|
|
357
|
-
const { value } = ensureReporterValueHasJson(rawValue ?? '');
|
|
361
|
+
const { value } = ensureReporterValueHasJson(rawValue ?? '', options);
|
|
358
362
|
args[i] = `${prefix}=${value}`;
|
|
359
363
|
}
|
|
360
364
|
}
|
|
361
365
|
return reporterFlagFound;
|
|
362
366
|
}
|
|
363
|
-
function ensureReporterValueHasJson(value) {
|
|
367
|
+
function ensureReporterValueHasJson(value, options = {}) {
|
|
364
368
|
const segments = value
|
|
365
369
|
.split(',')
|
|
366
370
|
.map((segment) => segment.trim())
|
|
@@ -369,7 +373,10 @@ function ensureReporterValueHasJson(value) {
|
|
|
369
373
|
if (hasJson) {
|
|
370
374
|
return { value, changed: false };
|
|
371
375
|
}
|
|
372
|
-
|
|
376
|
+
const jsonEntry = options.jsonOutputFile
|
|
377
|
+
? `json=${options.jsonOutputFile}`
|
|
378
|
+
: 'json';
|
|
379
|
+
segments.push(jsonEntry);
|
|
373
380
|
return { value: segments.join(','), changed: true };
|
|
374
381
|
}
|
|
375
382
|
async function resolvePlaywrightConfigPath(args) {
|
|
@@ -447,10 +454,10 @@ function insertConfigArg(args, configPath) {
|
|
|
447
454
|
...args.slice(dashDashIndex),
|
|
448
455
|
];
|
|
449
456
|
}
|
|
450
|
-
async function createConfigWrapperWithJsonReporter(originalConfigPath) {
|
|
457
|
+
async function createConfigWrapperWithJsonReporter(originalConfigPath, options = {}) {
|
|
451
458
|
const stagingDir = await fs_1.promises.mkdtemp(path.join(os.tmpdir(), 'donobu-playwright-config-'));
|
|
452
459
|
const wrapperPath = path.join(stagingDir, 'playwright.config.cjs');
|
|
453
|
-
const content = buildConfigWrapperContent(originalConfigPath);
|
|
460
|
+
const content = buildConfigWrapperContent(originalConfigPath, options.jsonOutputFile);
|
|
454
461
|
await fs_1.promises.writeFile(wrapperPath, content, 'utf-8');
|
|
455
462
|
const cleanup = async () => {
|
|
456
463
|
try {
|
|
@@ -462,11 +469,17 @@ async function createConfigWrapperWithJsonReporter(originalConfigPath) {
|
|
|
462
469
|
};
|
|
463
470
|
return { configPath: wrapperPath, cleanup };
|
|
464
471
|
}
|
|
465
|
-
function buildConfigWrapperContent(originalConfigPath) {
|
|
472
|
+
function buildConfigWrapperContent(originalConfigPath, jsonOutputFileOverride) {
|
|
466
473
|
const sanitisedPath = originalConfigPath.replace(/\\/g, '\\\\');
|
|
467
|
-
|
|
474
|
+
const forcedJsonPath = jsonOutputFileOverride
|
|
475
|
+
? jsonOutputFileOverride.replace(/\\/g, '\\\\')
|
|
476
|
+
: null;
|
|
477
|
+
const defaultJsonName = PLAYWRIGHT_JSON_REPORT_FILENAME;
|
|
478
|
+
const forcedLiteral = forcedJsonPath ? `'${forcedJsonPath}'` : 'null';
|
|
479
|
+
return `"use strict";
|
|
468
480
|
|
|
469
481
|
const path = require('path');
|
|
482
|
+
const forcedJsonOutputFile = ${forcedLiteral};
|
|
470
483
|
|
|
471
484
|
function loadBaseConfig() {
|
|
472
485
|
const imported = require('${sanitisedPath}');
|
|
@@ -544,7 +557,10 @@ if (Array.isArray(normalizedConfig.projects)) {
|
|
|
544
557
|
if (typeof useConfig.storageState === 'string') {
|
|
545
558
|
useConfig.storageState = absolutify(useConfig.storageState);
|
|
546
559
|
}
|
|
547
|
-
if (
|
|
560
|
+
if (
|
|
561
|
+
typeof useConfig.baseURL === 'string' &&
|
|
562
|
+
!/^https?:/i.test(useConfig.baseURL)
|
|
563
|
+
) {
|
|
548
564
|
useConfig.baseURL = absolutify(useConfig.baseURL);
|
|
549
565
|
}
|
|
550
566
|
projectConfig.use = useConfig;
|
|
@@ -580,18 +596,46 @@ const hasJsonReporter = reporters.some((entry) => {
|
|
|
580
596
|
});
|
|
581
597
|
|
|
582
598
|
if (!hasJsonReporter) {
|
|
583
|
-
const
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
599
|
+
const outputFile =
|
|
600
|
+
forcedJsonOutputFile ||
|
|
601
|
+
(() => {
|
|
602
|
+
const outputDir = process.env.PLAYWRIGHT_JSON_OUTPUT_DIR
|
|
603
|
+
? path.resolve(process.cwd(), process.env.PLAYWRIGHT_JSON_OUTPUT_DIR)
|
|
604
|
+
: path.resolve(configDir, 'test-results');
|
|
605
|
+
const outputName =
|
|
606
|
+
process.env.PLAYWRIGHT_JSON_OUTPUT_NAME || '${defaultJsonName}';
|
|
607
|
+
return path.isAbsolute(outputName)
|
|
608
|
+
? outputName
|
|
609
|
+
: path.join(outputDir, outputName);
|
|
610
|
+
})();
|
|
590
611
|
reporters.push(['json', { outputFile }]);
|
|
591
612
|
}
|
|
592
613
|
|
|
593
614
|
const normalisedReporters = reporters.map((entry) => {
|
|
594
|
-
if (
|
|
615
|
+
if (typeof entry === 'string') {
|
|
616
|
+
if (!forcedJsonOutputFile) {
|
|
617
|
+
return entry;
|
|
618
|
+
}
|
|
619
|
+
const segments = entry
|
|
620
|
+
.split(',')
|
|
621
|
+
.map((segment) => segment.trim())
|
|
622
|
+
.filter((segment) => segment.length > 0);
|
|
623
|
+
const rewritten = segments.map((segment) => {
|
|
624
|
+
const [name] = segment.split('=', 2);
|
|
625
|
+
if (name === 'json') {
|
|
626
|
+
return \`json=\${forcedJsonOutputFile}\`;
|
|
627
|
+
}
|
|
628
|
+
return segment;
|
|
629
|
+
});
|
|
630
|
+
return rewritten.join(',');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
if (
|
|
634
|
+
Array.isArray(entry) &&
|
|
635
|
+
entry.length > 1 &&
|
|
636
|
+
entry[1] &&
|
|
637
|
+
typeof entry[1] === 'object'
|
|
638
|
+
) {
|
|
595
639
|
const options = { ...entry[1] };
|
|
596
640
|
if (options.outputFile) {
|
|
597
641
|
options.outputFile = absolutify(options.outputFile);
|
|
@@ -599,8 +643,19 @@ const normalisedReporters = reporters.map((entry) => {
|
|
|
599
643
|
if (options.outputFolder) {
|
|
600
644
|
options.outputFolder = absolutify(options.outputFolder);
|
|
601
645
|
}
|
|
646
|
+
if (entry[0] === 'json' && forcedJsonOutputFile) {
|
|
647
|
+
options.outputFile = forcedJsonOutputFile;
|
|
648
|
+
}
|
|
602
649
|
return [entry[0], options];
|
|
603
650
|
}
|
|
651
|
+
|
|
652
|
+
if (Array.isArray(entry) && entry.length > 0) {
|
|
653
|
+
const name = typeof entry[0] === 'string' ? entry[0] : '';
|
|
654
|
+
if (name === 'json' && forcedJsonOutputFile) {
|
|
655
|
+
return [entry[0], { outputFile: forcedJsonOutputFile }];
|
|
656
|
+
}
|
|
657
|
+
return entry;
|
|
658
|
+
}
|
|
604
659
|
return entry;
|
|
605
660
|
});
|
|
606
661
|
|
|
@@ -615,23 +670,113 @@ module.exports = {
|
|
|
615
670
|
* We keep a stable snapshot because Playwright may delete the file between
|
|
616
671
|
* retries or when the `--output` folder is cleaned.
|
|
617
672
|
*/
|
|
618
|
-
async function copyJsonReport(outputDir, destinationPath) {
|
|
619
|
-
const
|
|
673
|
+
async function copyJsonReport(outputDir, destinationPath, options = {}) {
|
|
674
|
+
const candidatePaths = new Set();
|
|
675
|
+
const envDefinedPath = resolveEnvJsonReportPath(options.envOverrides);
|
|
676
|
+
if (envDefinedPath) {
|
|
677
|
+
candidatePaths.add(envDefinedPath);
|
|
678
|
+
}
|
|
679
|
+
candidatePaths.add(path.join(outputDir, PLAYWRIGHT_JSON_REPORT_FILENAME));
|
|
680
|
+
(options.additionalCandidates ?? []).forEach((candidate) => {
|
|
681
|
+
if (candidate) {
|
|
682
|
+
candidatePaths.add(candidate);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
for (const sourcePath of candidatePaths) {
|
|
686
|
+
const copied = await tryCopyReport(sourcePath, destinationPath);
|
|
687
|
+
if (copied) {
|
|
688
|
+
return { sourcePath, destinationPath };
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const fallbackSource = await findJsonReportInDir(outputDir);
|
|
692
|
+
if (fallbackSource) {
|
|
693
|
+
const copied = await tryCopyReport(fallbackSource, destinationPath);
|
|
694
|
+
if (copied) {
|
|
695
|
+
return { sourcePath: fallbackSource, destinationPath };
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
function resolveEnvJsonReportPath(envOverrides) {
|
|
701
|
+
if (!envOverrides) {
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
const outputDir = envOverrides.PLAYWRIGHT_JSON_OUTPUT_DIR;
|
|
705
|
+
const outputName = envOverrides.PLAYWRIGHT_JSON_OUTPUT_NAME;
|
|
706
|
+
if (!outputDir || !outputName) {
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
const resolvedDir = path.isAbsolute(outputDir)
|
|
710
|
+
? outputDir
|
|
711
|
+
: path.resolve(process.cwd(), outputDir);
|
|
712
|
+
return path.isAbsolute(outputName)
|
|
713
|
+
? outputName
|
|
714
|
+
: path.join(resolvedDir, outputName);
|
|
715
|
+
}
|
|
716
|
+
async function tryCopyReport(sourcePath, destinationPath) {
|
|
620
717
|
try {
|
|
621
718
|
await fs_1.promises.access(sourcePath, fs_1.constants.F_OK);
|
|
622
719
|
}
|
|
623
720
|
catch {
|
|
624
|
-
return
|
|
721
|
+
return false;
|
|
625
722
|
}
|
|
626
723
|
await ensureDirectory(path.dirname(destinationPath));
|
|
627
724
|
try {
|
|
628
725
|
await fs_1.promises.copyFile(sourcePath, destinationPath);
|
|
629
|
-
return
|
|
726
|
+
return true;
|
|
630
727
|
}
|
|
631
728
|
catch (error) {
|
|
632
729
|
Logger_1.appLogger.warn(`Failed to copy Playwright JSON report from ${sourcePath} to ${destinationPath}.`, error);
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async function findJsonReportInDir(outputDir) {
|
|
734
|
+
let entries;
|
|
735
|
+
try {
|
|
736
|
+
entries = await fs_1.promises.readdir(outputDir);
|
|
737
|
+
}
|
|
738
|
+
catch {
|
|
633
739
|
return null;
|
|
634
740
|
}
|
|
741
|
+
const candidates = entries
|
|
742
|
+
.filter((entry) => entry.endsWith('.json'))
|
|
743
|
+
.sort((a, b) => {
|
|
744
|
+
const aScore = a.includes('report') ? 0 : 1;
|
|
745
|
+
const bScore = b.includes('report') ? 0 : 1;
|
|
746
|
+
if (aScore !== bScore) {
|
|
747
|
+
return aScore - bScore;
|
|
748
|
+
}
|
|
749
|
+
return a.localeCompare(b);
|
|
750
|
+
});
|
|
751
|
+
for (const fileName of candidates) {
|
|
752
|
+
const fullPath = path.join(outputDir, fileName);
|
|
753
|
+
if (await isLikelyPlaywrightReport(fullPath)) {
|
|
754
|
+
return fullPath;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
async function isLikelyPlaywrightReport(filePath) {
|
|
760
|
+
try {
|
|
761
|
+
const raw = await fs_1.promises.readFile(filePath, 'utf8');
|
|
762
|
+
const parsed = JSON.parse(raw);
|
|
763
|
+
return (!!parsed && typeof parsed === 'object' && Array.isArray(parsed.suites));
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
async function overwriteReportTargets(sourcePath, targets) {
|
|
770
|
+
const uniqueTargets = Array.from(new Set(targets)).filter((target) => target && target !== sourcePath);
|
|
771
|
+
await Promise.all(uniqueTargets.map(async (targetPath) => {
|
|
772
|
+
try {
|
|
773
|
+
await ensureDirectory(path.dirname(targetPath));
|
|
774
|
+
await fs_1.promises.copyFile(sourcePath, targetPath);
|
|
775
|
+
}
|
|
776
|
+
catch (error) {
|
|
777
|
+
Logger_1.appLogger.warn(`Failed to copy merged Playwright report to ${targetPath}.`, error);
|
|
778
|
+
}
|
|
779
|
+
}));
|
|
635
780
|
}
|
|
636
781
|
/**
|
|
637
782
|
* Donobu always wants Playwright's JSON reporter enabled so we can build
|
|
@@ -643,7 +788,7 @@ function applyJsonReportEnv(env, outputDir) {
|
|
|
643
788
|
env.PLAYWRIGHT_JSON_OUTPUT_DIR = outputDir;
|
|
644
789
|
}
|
|
645
790
|
if (!env.PLAYWRIGHT_JSON_OUTPUT_NAME) {
|
|
646
|
-
env.PLAYWRIGHT_JSON_OUTPUT_NAME =
|
|
791
|
+
env.PLAYWRIGHT_JSON_OUTPUT_NAME = PLAYWRIGHT_JSON_REPORT_FILENAME;
|
|
647
792
|
}
|
|
648
793
|
}
|
|
649
794
|
/**
|
|
@@ -987,7 +1132,10 @@ async function attemptAutoHealRun(params) {
|
|
|
987
1132
|
// Flag downstream systems so they know this invocation came from auto-heal.
|
|
988
1133
|
envOverrides.DONOBU_AUTO_HEAL_ACTIVE = '1';
|
|
989
1134
|
Logger_1.appLogger.info(`Auto-heal: applying directives from ${evaluation.eligiblePlans.length} treatment plan(s) and re-running Playwright...`);
|
|
990
|
-
const
|
|
1135
|
+
const healJsonReportPath = path.join(staging.playwrightOutputDir, PLAYWRIGHT_JSON_REPORT_FILENAME);
|
|
1136
|
+
const reporterSetup = await ensureJsonReporter(healArgsForRun, {
|
|
1137
|
+
jsonOutputFile: healJsonReportPath,
|
|
1138
|
+
});
|
|
991
1139
|
try {
|
|
992
1140
|
healExitCode = await runPlaywright(reporterSetup.args, envOverrides);
|
|
993
1141
|
}
|
|
@@ -995,9 +1143,12 @@ async function attemptAutoHealRun(params) {
|
|
|
995
1143
|
await reporterSetup.cleanup();
|
|
996
1144
|
}
|
|
997
1145
|
const healReportDestination = path.join(params.playwrightOutputDir, `donobu-auto-heal-report-${Date.now()}.json`);
|
|
998
|
-
const healReportCopy = await copyJsonReport(staging.playwrightOutputDir, healReportDestination
|
|
1146
|
+
const healReportCopy = await copyJsonReport(staging.playwrightOutputDir, healReportDestination, {
|
|
1147
|
+
envOverrides,
|
|
1148
|
+
additionalCandidates: [healJsonReportPath],
|
|
1149
|
+
});
|
|
999
1150
|
if (healTriageEnabled && healTriageContext && healExitCode !== 0) {
|
|
1000
|
-
await postProcessTriageRun(healTriageContext, healArgsForRun, healReportCopy ?? undefined);
|
|
1151
|
+
await postProcessTriageRun(healTriageContext, healArgsForRun, healReportCopy?.destinationPath ?? undefined);
|
|
1001
1152
|
const finalTriageBaseDir = params.options.triageOutputDir
|
|
1002
1153
|
? path.resolve(params.options.triageOutputDir)
|
|
1003
1154
|
: path.join(params.playwrightOutputDir, 'donobu-triage');
|
|
@@ -1024,7 +1175,7 @@ async function attemptAutoHealRun(params) {
|
|
|
1024
1175
|
const mergedReportPath = path.join(params.playwrightOutputDir, `donobu-merged-report-${Date.now()}.json`);
|
|
1025
1176
|
await mergePlaywrightJsonReports({
|
|
1026
1177
|
initialReportPath: params.initialReportPath,
|
|
1027
|
-
healReportPath: healReportCopy ?? undefined,
|
|
1178
|
+
healReportPath: healReportCopy?.destinationPath ?? undefined,
|
|
1028
1179
|
mergedReportPath,
|
|
1029
1180
|
healedTests: evaluation.eligiblePlans.map((record) => ({
|
|
1030
1181
|
plan: record.plan,
|
|
@@ -1032,6 +1183,9 @@ async function attemptAutoHealRun(params) {
|
|
|
1032
1183
|
})),
|
|
1033
1184
|
healSucceeded: healExitCode === 0,
|
|
1034
1185
|
});
|
|
1186
|
+
if (params.reportTargets.length > 0) {
|
|
1187
|
+
await overwriteReportTargets(mergedReportPath, params.reportTargets);
|
|
1188
|
+
}
|
|
1035
1189
|
}
|
|
1036
1190
|
}
|
|
1037
1191
|
finally {
|
|
@@ -1058,7 +1212,8 @@ async function mergePlaywrightJsonReports(params) {
|
|
|
1058
1212
|
const healIndex = indexReport(healReport);
|
|
1059
1213
|
const healedKeys = new Set();
|
|
1060
1214
|
if (healReport) {
|
|
1061
|
-
|
|
1215
|
+
const processedHealEntries = new Set();
|
|
1216
|
+
const processHealEntry = (healEntry) => {
|
|
1062
1217
|
const key = buildTestKey(healEntry.suite.file, healEntry.test.projectName, healEntry.test.title);
|
|
1063
1218
|
let combinedEntry = (healEntry.test.testId
|
|
1064
1219
|
? combinedIndex.byId.get(healEntry.test.testId)
|
|
@@ -1078,16 +1233,23 @@ async function mergePlaywrightJsonReports(params) {
|
|
|
1078
1233
|
initialIndex.byKey.get(key) ??
|
|
1079
1234
|
null;
|
|
1080
1235
|
const combinedTest = combinedEntry.test;
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1236
|
+
if (healEntry.test.results?.length) {
|
|
1237
|
+
combinedTest.results = [
|
|
1238
|
+
...(combinedTest.results ?? []),
|
|
1239
|
+
...healEntry.test.results,
|
|
1240
|
+
];
|
|
1241
|
+
}
|
|
1242
|
+
if (healEntry.test.status !== undefined) {
|
|
1243
|
+
combinedTest.status = healEntry.test.status;
|
|
1244
|
+
}
|
|
1086
1245
|
if (healEntry.test.outcome !== undefined) {
|
|
1087
1246
|
combinedTest.outcome = healEntry.test.outcome;
|
|
1088
1247
|
}
|
|
1089
|
-
const originalStatus = originalEntry
|
|
1090
|
-
|
|
1248
|
+
const originalStatus = originalEntry
|
|
1249
|
+
? getFinalResultStatus(originalEntry.test)
|
|
1250
|
+
: undefined;
|
|
1251
|
+
const healStatus = getFinalResultStatus(healEntry.test);
|
|
1252
|
+
if (healStatus === 'passed' &&
|
|
1091
1253
|
originalStatus &&
|
|
1092
1254
|
originalStatus !== 'passed') {
|
|
1093
1255
|
combinedTest.annotations = combinedTest.annotations ?? [];
|
|
@@ -1100,11 +1262,22 @@ async function mergePlaywrightJsonReports(params) {
|
|
|
1100
1262
|
combinedTest.donobuStatus = 'healed';
|
|
1101
1263
|
healedKeys.add(key);
|
|
1102
1264
|
}
|
|
1103
|
-
}
|
|
1265
|
+
};
|
|
1266
|
+
const iterateEntries = (entries) => {
|
|
1267
|
+
for (const [, healEntry] of entries) {
|
|
1268
|
+
if (processedHealEntries.has(healEntry)) {
|
|
1269
|
+
continue;
|
|
1270
|
+
}
|
|
1271
|
+
processedHealEntries.add(healEntry);
|
|
1272
|
+
processHealEntry(healEntry);
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
iterateEntries(healIndex.byId);
|
|
1276
|
+
iterateEntries(healIndex.byKey);
|
|
1104
1277
|
}
|
|
1105
1278
|
if (params.healSucceeded && healedKeys.size === 0) {
|
|
1106
1279
|
params.healedTests.forEach((descriptor) => {
|
|
1107
|
-
const key = buildTestKey(descriptor.testCase.file, descriptor.testCase.projectName, descriptor.testCase.title);
|
|
1280
|
+
const key = buildTestKey(normalizeSpecPath(descriptor.testCase.file), descriptor.testCase.projectName, descriptor.testCase.title);
|
|
1108
1281
|
const entry = combinedIndex.byKey.get(key);
|
|
1109
1282
|
if (entry) {
|
|
1110
1283
|
entry.test.annotations = entry.test.annotations ?? [];
|
|
@@ -1132,7 +1305,7 @@ async function mergePlaywrightJsonReports(params) {
|
|
|
1132
1305
|
};
|
|
1133
1306
|
await ensureDirectory(path.dirname(params.mergedReportPath));
|
|
1134
1307
|
await fs_1.promises.writeFile(params.mergedReportPath, JSON.stringify(combined, null, 2), 'utf8');
|
|
1135
|
-
Logger_1.appLogger.
|
|
1308
|
+
Logger_1.appLogger.debug(`Saved merged Playwright report to ${params.mergedReportPath}.`);
|
|
1136
1309
|
}
|
|
1137
1310
|
// Playwright does not reliably expose stable IDs across reports; fall back to a composite key.
|
|
1138
1311
|
function buildTestKey(file, projectName, title) {
|
|
@@ -1140,6 +1313,12 @@ function buildTestKey(file, projectName, title) {
|
|
|
1140
1313
|
.map((segment) => segment.toString())
|
|
1141
1314
|
.join('::');
|
|
1142
1315
|
}
|
|
1316
|
+
function getFinalResultStatus(test) {
|
|
1317
|
+
if (!test) {
|
|
1318
|
+
return undefined;
|
|
1319
|
+
}
|
|
1320
|
+
return test.results?.at?.(-1)?.status ?? test.status;
|
|
1321
|
+
}
|
|
1143
1322
|
/**
|
|
1144
1323
|
* Build lookup tables for quickly finding test entries inside a Playwright
|
|
1145
1324
|
* report. We index by both `testId` (preferred) and the composite key to handle
|
|
@@ -1319,7 +1498,7 @@ async function runTestCommand(cliArgs) {
|
|
|
1319
1498
|
if (triageEnabled) {
|
|
1320
1499
|
try {
|
|
1321
1500
|
triageContext = await prepareTriageContext(playwrightOutputDir, options);
|
|
1322
|
-
Logger_1.appLogger.
|
|
1501
|
+
Logger_1.appLogger.debug(`[donobu triage] Will collect test failure evidence in ${triageContext.runDir}.`);
|
|
1323
1502
|
}
|
|
1324
1503
|
catch (error) {
|
|
1325
1504
|
Logger_1.appLogger.error('Failed to prepare test-failure triage directory. Continuing without triage.', error);
|
|
@@ -1348,11 +1527,27 @@ async function runTestCommand(cliArgs) {
|
|
|
1348
1527
|
}
|
|
1349
1528
|
let generatedPlans = [];
|
|
1350
1529
|
let initialReportCopy = null;
|
|
1530
|
+
const reportTargets = new Set();
|
|
1351
1531
|
if (triageEnabled) {
|
|
1352
1532
|
const initialReportDestination = triageContext
|
|
1353
1533
|
? path.join(triageContext.runDir, 'initial-playwright-report.json')
|
|
1354
1534
|
: path.join(playwrightOutputDir, `donobu-initial-report-${Date.now()}.json`);
|
|
1355
|
-
|
|
1535
|
+
const initialReportResult = await copyJsonReport(playwrightOutputDir, initialReportDestination, {
|
|
1536
|
+
envOverrides,
|
|
1537
|
+
additionalCandidates: [
|
|
1538
|
+
path.join(playwrightOutputDir, PLAYWRIGHT_JSON_REPORT_FILENAME),
|
|
1539
|
+
],
|
|
1540
|
+
});
|
|
1541
|
+
if (initialReportResult) {
|
|
1542
|
+
initialReportCopy = initialReportResult.destinationPath;
|
|
1543
|
+
reportTargets.add(initialReportResult.sourcePath);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
if (reportTargets.size === 0) {
|
|
1547
|
+
const discoveredReport = await findJsonReportInDir(playwrightOutputDir);
|
|
1548
|
+
if (discoveredReport) {
|
|
1549
|
+
reportTargets.add(discoveredReport);
|
|
1550
|
+
}
|
|
1356
1551
|
}
|
|
1357
1552
|
if (triageEnabled && triageContext && exitCode !== 0) {
|
|
1358
1553
|
generatedPlans = await postProcessTriageRun(triageContext, playwrightArgs, initialReportCopy ?? undefined);
|
|
@@ -1367,6 +1562,7 @@ async function runTestCommand(cliArgs) {
|
|
|
1367
1562
|
generatedPlans,
|
|
1368
1563
|
currentExitCode: exitCode,
|
|
1369
1564
|
initialReportPath: initialReportCopy ?? undefined,
|
|
1565
|
+
reportTargets: Array.from(reportTargets),
|
|
1370
1566
|
});
|
|
1371
1567
|
return autoHealOutcome.exitCode;
|
|
1372
1568
|
}
|
|
@@ -1438,7 +1634,10 @@ async function runHealCommand(cliArgs) {
|
|
|
1438
1634
|
// Downstream hooks check this flag to avoid recursive auto-heal loops.
|
|
1439
1635
|
envOverrides.DONOBU_AUTO_HEAL_ACTIVE = '1';
|
|
1440
1636
|
Logger_1.appLogger.info(`Re-running Playwright using treatment plan at ${parsed.planPath}...`);
|
|
1441
|
-
const
|
|
1637
|
+
const healJsonReportPath = path.join(playwrightOutputDir, PLAYWRIGHT_JSON_REPORT_FILENAME);
|
|
1638
|
+
const reporterSetup = await ensureJsonReporter(healArgsWithDirectives, {
|
|
1639
|
+
jsonOutputFile: healJsonReportPath,
|
|
1640
|
+
});
|
|
1442
1641
|
Logger_1.appLogger.debug(`Heal command Playwright args: ${JSON.stringify(reporterSetup.args)} with env overrides ${JSON.stringify(envOverrides)}`);
|
|
1443
1642
|
let exitCode;
|
|
1444
1643
|
try {
|
|
@@ -1448,15 +1647,18 @@ async function runHealCommand(cliArgs) {
|
|
|
1448
1647
|
await reporterSetup.cleanup();
|
|
1449
1648
|
}
|
|
1450
1649
|
const healReportDestination = path.join(path.dirname(parsed.planPath), `donobu-heal-report-${Date.now()}.json`);
|
|
1451
|
-
const healReportCopy = await copyJsonReport(playwrightOutputDir, healReportDestination
|
|
1650
|
+
const healReportCopy = await copyJsonReport(playwrightOutputDir, healReportDestination, {
|
|
1651
|
+
envOverrides,
|
|
1652
|
+
additionalCandidates: [healJsonReportPath],
|
|
1653
|
+
});
|
|
1452
1654
|
if (triageEnabled && triageContext && exitCode !== 0) {
|
|
1453
|
-
await postProcessTriageRun(triageContext, healArgsWithDirectives, healReportCopy ?? undefined);
|
|
1655
|
+
await postProcessTriageRun(triageContext, healArgsWithDirectives, healReportCopy?.destinationPath ?? undefined);
|
|
1454
1656
|
}
|
|
1455
1657
|
if (persisted.reportPath || healReportCopy) {
|
|
1456
1658
|
const mergedReportPath = path.join(path.dirname(parsed.planPath), 'donobu-heal-merged-report.json');
|
|
1457
1659
|
await mergePlaywrightJsonReports({
|
|
1458
1660
|
initialReportPath: persisted.reportPath,
|
|
1459
|
-
healReportPath: healReportCopy ?? undefined,
|
|
1661
|
+
healReportPath: healReportCopy?.destinationPath ?? undefined,
|
|
1460
1662
|
mergedReportPath,
|
|
1461
1663
|
healedTests: [
|
|
1462
1664
|
{
|
|
@@ -1466,6 +1668,9 @@ async function runHealCommand(cliArgs) {
|
|
|
1466
1668
|
],
|
|
1467
1669
|
healSucceeded: exitCode === 0,
|
|
1468
1670
|
});
|
|
1671
|
+
if (persisted.reportPath) {
|
|
1672
|
+
await overwriteReportTargets(mergedReportPath, [persisted.reportPath]);
|
|
1673
|
+
}
|
|
1469
1674
|
}
|
|
1470
1675
|
return exitCode;
|
|
1471
1676
|
}
|