testchimp-runner-core 0.1.21 → 0.1.23
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/execution-service.d.ts.map +1 -1
- package/dist/execution-service.js +235 -78
- package/dist/execution-service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/progress-reporter.d.ts +15 -0
- package/dist/progress-reporter.d.ts.map +1 -1
- package/dist/progress-reporter.js +12 -1
- package/dist/progress-reporter.js.map +1 -1
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/initialization-code-utils.d.ts +2 -0
- package/dist/utils/initialization-code-utils.d.ts.map +1 -1
- package/dist/utils/initialization-code-utils.js +26 -3
- package/dist/utils/initialization-code-utils.js.map +1 -1
- package/dist/utils/pom-denormalizer.d.ts +44 -0
- package/dist/utils/pom-denormalizer.d.ts.map +1 -0
- package/dist/utils/pom-denormalizer.js +754 -0
- package/dist/utils/pom-denormalizer.js.map +1 -0
- package/dist/utils/script-parser-utils.js +5 -5
- package/dist/utils/script-parser-utils.js.map +1 -1
- package/dist/utils/test-file-parser.d.ts +3 -0
- package/dist/utils/test-file-parser.d.ts.map +1 -1
- package/dist/utils/test-file-parser.js +43 -5
- package/dist/utils/test-file-parser.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"execution-service.d.ts","sourceRoot":"","sources":["../src/execution-service.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,0BAA0B,EAC1B,2BAA2B,EAE3B,sBAAsB,EACtB,uBAAuB,EAKvB,wBAAwB,EACxB,yBAAyB,EAG1B,MAAM,SAAS,CAAC;AAKjB,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"execution-service.d.ts","sourceRoot":"","sources":["../src/execution-service.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,0BAA0B,EAC1B,2BAA2B,EAE3B,sBAAsB,EACtB,uBAAuB,EAKvB,wBAAwB,EACxB,yBAAyB,EAG1B,MAAM,SAAS,CAAC;AAKjB,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAY3C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,gBAAgB,EAAuE,MAAM,qBAAqB,CAAC;AAmb5H;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,gBAAgB,CAAC,CAAmB;IAC5C,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,OAAO,CAAC,uBAAuB,CAAS;IACxC,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,MAAM,CAAC,CAA8D;IAC7E,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,cAAc,CAAkB;IAExC;;OAEG;IACH,aAAa;gBAKX,UAAU,CAAC,EAAE,UAAU,EACvB,UAAU,CAAC,EAAE,MAAM,EACnB,uBAAuB,GAAE,MAAW,EACpC,WAAW,CAAC,EAAE,WAAW,EACzB,gBAAgB,CAAC,EAAE,gBAAgB;IA8BrC;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI;IAKpF;;OAEG;IACH,OAAO,CAAC,GAAG;IASX,OAAO,CAAC,WAAW;IAanB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIjC;;;OAGG;IACH,aAAa,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;IAQ3C;;OAEG;IACG,aAAa,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAkBtF;;;;;;;;;;OAUG;IACG,cAAc,CAClB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,GAAG,EACT,OAAO,CAAC,EAAE,GAAG,EACb,cAAc,CAAC,EAAE,GAAG,EACpB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAuBhB;;OAEG;YACW,qBAAqB;IAmBnC;;OAEG;IACG,gBAAgB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAgCjG;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAsC7B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACH,OAAO,IAAI,OAAO;IAIlB;;;;OAIG;YACW,+BAA+B;IA86B7C;;;OAGG;YACW,iBAAiB;YAiGjB,UAAU;YAoMV,eAAe;YAsKf,oBAAoB;YAiBpB,eAAe;IAuC7B,OAAO,CAAC,qBAAqB;IAK7B;;OAEG;YACW,iBAAiB;IAI/B;;;;OAIG;IACG,WAAW,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IAuxBxF;;;;;OAKG;YACW,WAAW;CAoI1B"}
|
|
@@ -51,6 +51,7 @@ const import_utils_1 = require("./utils/import-utils");
|
|
|
51
51
|
const initialization_code_utils_1 = require("./utils/initialization-code-utils");
|
|
52
52
|
const script_parser_utils_1 = require("./utils/script-parser-utils");
|
|
53
53
|
const script_generator_utils_1 = require("./utils/script-generator-utils");
|
|
54
|
+
const pom_denormalizer_1 = require("./utils/pom-denormalizer");
|
|
54
55
|
const progress_reporter_1 = require("./progress-reporter");
|
|
55
56
|
const backend_proxy_llm_provider_1 = require("./providers/backend-proxy-llm-provider");
|
|
56
57
|
const build_info_1 = require("./build-info");
|
|
@@ -645,12 +646,71 @@ class ExecutionService {
|
|
|
645
646
|
* Variables persist across steps without re-execution
|
|
646
647
|
*/
|
|
647
648
|
async executeStepsInPersistentContext(steps, page, browser, browserContext, options, sharedContext) {
|
|
648
|
-
this.log(`[executeStepsInPersistentContext] Called with ${steps.length} steps, mode=${options.mode}, originalScript=${!!options.originalScript}, tempDir=${!!options.tempDir}, sharedContext=${!!sharedContext}`);
|
|
649
|
+
this.log(`[executeStepsInPersistentContext] Called with ${steps.length} steps, mode=${options.mode}, originalScript=${!!options.originalScript}, tempDir=${!!options.tempDir}, sharedContext=${!!sharedContext}, runPomDenormalized=${!!options.runPomDenormalized}`);
|
|
649
650
|
const { expect, test } = require('@playwright/test');
|
|
650
651
|
const { ai } = require('ai-wright');
|
|
651
652
|
// Use shared context if provided, otherwise create new context
|
|
652
653
|
const persistentContext = sharedContext || new PersistentExecutionContext(page, expect, test, ai, browser, browserContext, options.tempDir);
|
|
653
654
|
const isSharedContext = !!sharedContext;
|
|
655
|
+
const resolvedTestFileDir = (() => {
|
|
656
|
+
if (!options.tempDir) {
|
|
657
|
+
return undefined;
|
|
658
|
+
}
|
|
659
|
+
if (options.testFolderPath && options.testFolderPath.length > 0) {
|
|
660
|
+
return path.join(options.tempDir, ...options.testFolderPath);
|
|
661
|
+
}
|
|
662
|
+
const defaultDir = path.join(options.tempDir, 'tests');
|
|
663
|
+
try {
|
|
664
|
+
const testsDir = defaultDir;
|
|
665
|
+
const fs = require('fs');
|
|
666
|
+
if (!fs.existsSync(testsDir)) {
|
|
667
|
+
this.log(`Tests directory ${testsDir} does not exist, using default for module resolution`, 'warn');
|
|
668
|
+
return defaultDir;
|
|
669
|
+
}
|
|
670
|
+
const findTestFile = (dir, depth = 0, maxDepth = 10) => {
|
|
671
|
+
if (depth > maxDepth) {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
try {
|
|
675
|
+
const files = fs.readdirSync(dir);
|
|
676
|
+
for (const file of files) {
|
|
677
|
+
const fullPath = path.join(dir, file);
|
|
678
|
+
try {
|
|
679
|
+
const stat = fs.statSync(fullPath);
|
|
680
|
+
if (stat.isDirectory()) {
|
|
681
|
+
const found = findTestFile(fullPath, depth + 1, maxDepth);
|
|
682
|
+
if (found)
|
|
683
|
+
return found;
|
|
684
|
+
}
|
|
685
|
+
else if (file.endsWith('.spec.ts') || file.endsWith('.spec.js') ||
|
|
686
|
+
file.endsWith('.test.ts') || file.endsWith('.test.js')) {
|
|
687
|
+
return fullPath;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
catch (statError) {
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
catch (e) {
|
|
696
|
+
return null;
|
|
697
|
+
}
|
|
698
|
+
return null;
|
|
699
|
+
};
|
|
700
|
+
const testFilePath = findTestFile(testsDir);
|
|
701
|
+
if (testFilePath) {
|
|
702
|
+
const testFileDir = path.dirname(testFilePath);
|
|
703
|
+
this.log(`Found test file at ${testFilePath}, using directory ${testFileDir} for module resolution`);
|
|
704
|
+
return testFileDir;
|
|
705
|
+
}
|
|
706
|
+
this.log(`No test file found in ${testsDir}, using tests directory for module resolution`, 'warn');
|
|
707
|
+
return testsDir;
|
|
708
|
+
}
|
|
709
|
+
catch (searchError) {
|
|
710
|
+
this.log(`Could not search for test file: ${searchError.message}, using tests directory for module resolution`, 'warn');
|
|
711
|
+
return defaultDir;
|
|
712
|
+
}
|
|
713
|
+
})();
|
|
654
714
|
// Extract and execute imports from original script if provided
|
|
655
715
|
this.log(`[executeStepsInPersistentContext] Checking imports: originalScript=${!!options.originalScript}, tempDir=${!!options.tempDir}`);
|
|
656
716
|
if (options.originalScript && options.tempDir) {
|
|
@@ -664,60 +724,7 @@ class ExecutionService {
|
|
|
664
724
|
// Execute imports with proper context - find the actual test file path
|
|
665
725
|
// All tests are within the tests folder
|
|
666
726
|
// We search for test files in tempDir/tests directory tree
|
|
667
|
-
|
|
668
|
-
try {
|
|
669
|
-
const fs = require('fs');
|
|
670
|
-
const testsDir = path.join(options.tempDir, 'tests');
|
|
671
|
-
// Check if tests directory exists
|
|
672
|
-
if (!fs.existsSync(testsDir)) {
|
|
673
|
-
this.log(`Tests directory ${testsDir} does not exist, using default for module resolution`, 'warn');
|
|
674
|
-
}
|
|
675
|
-
else {
|
|
676
|
-
// Recursive search with depth limit to prevent infinite loops
|
|
677
|
-
const findTestFile = (dir, depth = 0, maxDepth = 10) => {
|
|
678
|
-
if (depth > maxDepth) {
|
|
679
|
-
return null; // Prevent infinite recursion
|
|
680
|
-
}
|
|
681
|
-
try {
|
|
682
|
-
const files = fs.readdirSync(dir);
|
|
683
|
-
for (const file of files) {
|
|
684
|
-
const fullPath = path.join(dir, file);
|
|
685
|
-
try {
|
|
686
|
-
const stat = fs.statSync(fullPath);
|
|
687
|
-
if (stat.isDirectory()) {
|
|
688
|
-
const found = findTestFile(fullPath, depth + 1, maxDepth);
|
|
689
|
-
if (found)
|
|
690
|
-
return found;
|
|
691
|
-
}
|
|
692
|
-
else if (file.endsWith('.spec.ts') || file.endsWith('.spec.js') ||
|
|
693
|
-
file.endsWith('.test.ts') || file.endsWith('.test.js')) {
|
|
694
|
-
return fullPath; // Found a test file
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
catch (statError) {
|
|
698
|
-
// Skip files we can't stat
|
|
699
|
-
continue;
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
catch (e) {
|
|
704
|
-
// Continue searching in other directories
|
|
705
|
-
}
|
|
706
|
-
return null;
|
|
707
|
-
};
|
|
708
|
-
const testFilePath = findTestFile(testsDir);
|
|
709
|
-
if (testFilePath) {
|
|
710
|
-
testFileDir = path.dirname(testFilePath);
|
|
711
|
-
this.log(`Found test file at ${testFilePath}, using directory ${testFileDir} for module resolution`);
|
|
712
|
-
}
|
|
713
|
-
else {
|
|
714
|
-
this.log(`No test file found in ${testsDir}, using tests directory for module resolution`, 'warn');
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
catch (searchError) {
|
|
719
|
-
this.log(`Could not search for test file: ${searchError.message}, using tests directory for module resolution`, 'warn');
|
|
720
|
-
}
|
|
727
|
+
const testFileDir = resolvedTestFileDir || path.join(options.tempDir, 'tests');
|
|
721
728
|
// Store test file directory in context for module resolution
|
|
722
729
|
persistentContext.setTestFileDir(testFileDir);
|
|
723
730
|
this.log(`Module resolution context: testFileDir=${testFileDir}, tempDir=${options.tempDir}`);
|
|
@@ -945,6 +952,8 @@ class ExecutionService {
|
|
|
945
952
|
isVariableDeclaration,
|
|
946
953
|
intentComment: step.description !== step.code ? step.description : undefined,
|
|
947
954
|
screenStateAnnotation: undefined, // Will be in description for pre-parsed steps
|
|
955
|
+
scenarioAnnotation: step.scenarioAnnotation,
|
|
956
|
+
scenarioAnnotationLine: undefined,
|
|
948
957
|
stepId: step.id // Preserve stepId (should be hash-based for test body statements)
|
|
949
958
|
};
|
|
950
959
|
});
|
|
@@ -963,6 +972,80 @@ class ExecutionService {
|
|
|
963
972
|
}
|
|
964
973
|
return { success: false, error: 'No script or steps provided for execution' };
|
|
965
974
|
}
|
|
975
|
+
if (options.runPomDenormalized && options.originalScript) {
|
|
976
|
+
const originalCount = allStatements.length;
|
|
977
|
+
const originalStepIds = allStatements
|
|
978
|
+
.map((stmt, idx) => ({ idx, stepId: stmt.stepId, code: stmt.code.substring(0, 50) }))
|
|
979
|
+
.filter(s => s.stepId);
|
|
980
|
+
this.log(`[executeStepsInPersistentContext] Starting POM denormalization: ${originalCount} original statements, ` +
|
|
981
|
+
`${originalStepIds.length} with stepIds`);
|
|
982
|
+
// Convert to ScriptStep[] for denormalization
|
|
983
|
+
const scriptSteps = allStatements.map(stmt => ({
|
|
984
|
+
code: stmt.code,
|
|
985
|
+
description: stmt.intentComment || '',
|
|
986
|
+
id: stmt.stepId, // stepId may not exist for InitializationCodeUtils results
|
|
987
|
+
isVariableDeclaration: stmt.isVariableDeclaration
|
|
988
|
+
}));
|
|
989
|
+
// Denormalize POM calls
|
|
990
|
+
const denormalizedSteps = pom_denormalizer_1.PomDenormalizer.denormalizePomSteps(scriptSteps, {
|
|
991
|
+
originalScript: options.originalScript,
|
|
992
|
+
tempDir: options.tempDir || '',
|
|
993
|
+
testFileDir: resolvedTestFileDir,
|
|
994
|
+
logger: (msg, level) => this.log(msg, level)
|
|
995
|
+
});
|
|
996
|
+
const denormalizedCount = denormalizedSteps.length;
|
|
997
|
+
const denormalizedStepIds = denormalizedSteps
|
|
998
|
+
.map((step, idx) => ({ idx, stepId: step.id, isVar: step.isVariableDeclaration, code: step.code.substring(0, 50) }))
|
|
999
|
+
.filter(s => s.stepId && !s.isVar);
|
|
1000
|
+
this.log(`[executeStepsInPersistentContext] Denormalization complete: ${originalCount} -> ${denormalizedCount} statements ` +
|
|
1001
|
+
`(${denormalizedCount - originalCount > 0 ? '+' : ''}${denormalizedCount - originalCount}), ` +
|
|
1002
|
+
`${denormalizedStepIds.length} non-variable steps with stepIds`);
|
|
1003
|
+
if (denormalizedStepIds.length > 0 && this.log) {
|
|
1004
|
+
const stepIdPreview = denormalizedStepIds.slice(0, 5).map(s => `idx=${s.idx} stepId=${s.stepId?.substring(0, 16)}...`).join(', ');
|
|
1005
|
+
this.log(`[executeStepsInPersistentContext] Sample denormalized stepIds: ${stepIdPreview}${denormalizedStepIds.length > 5 ? '...' : ''}`);
|
|
1006
|
+
}
|
|
1007
|
+
// Convert back to inline type format
|
|
1008
|
+
allStatements = denormalizedSteps.map(step => ({
|
|
1009
|
+
code: step.code,
|
|
1010
|
+
isVariableDeclaration: step.isVariableDeclaration || false,
|
|
1011
|
+
intentComment: step.description !== step.code ? step.description : undefined,
|
|
1012
|
+
screenStateAnnotation: undefined,
|
|
1013
|
+
scenarioAnnotation: step.scenarioAnnotation,
|
|
1014
|
+
scenarioAnnotationLine: undefined,
|
|
1015
|
+
stepId: step.id
|
|
1016
|
+
}));
|
|
1017
|
+
// Verify stepId preservation
|
|
1018
|
+
const preservedStepIds = allStatements
|
|
1019
|
+
.map((stmt, idx) => ({ idx, stepId: stmt.stepId, isVar: stmt.isVariableDeclaration }))
|
|
1020
|
+
.filter(s => s.stepId && !s.isVar);
|
|
1021
|
+
this.log(`[executeStepsInPersistentContext] StepId preservation: ${preservedStepIds.length} non-variable statements have stepIds ` +
|
|
1022
|
+
`(expected: ${denormalizedStepIds.length})`);
|
|
1023
|
+
// Verify stepId matching between denormalized and preserved
|
|
1024
|
+
if (preservedStepIds.length !== denormalizedStepIds.length) {
|
|
1025
|
+
this.log(`[executeStepsInPersistentContext] WARNING: StepId count mismatch! ` +
|
|
1026
|
+
`Denormalized: ${denormalizedStepIds.length}, Preserved: ${preservedStepIds.length}`, 'warn');
|
|
1027
|
+
}
|
|
1028
|
+
else {
|
|
1029
|
+
// Check if stepIds match
|
|
1030
|
+
let matchCount = 0;
|
|
1031
|
+
for (let i = 0; i < Math.min(denormalizedStepIds.length, preservedStepIds.length); i++) {
|
|
1032
|
+
const denorm = denormalizedStepIds[i];
|
|
1033
|
+
const preserved = preservedStepIds.find(p => p.idx === denorm.idx);
|
|
1034
|
+
if (preserved && preserved.stepId === denorm.stepId) {
|
|
1035
|
+
matchCount++;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
this.log(`[executeStepsInPersistentContext] StepId matching: ${matchCount}/${Math.min(denormalizedStepIds.length, preservedStepIds.length)} stepIds match`);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
if (!options.runPomDenormalized) {
|
|
1043
|
+
this.log(`[executeStepsInPersistentContext] POM denormalization skipped: runPomDenormalized=false`);
|
|
1044
|
+
}
|
|
1045
|
+
else if (!options.originalScript) {
|
|
1046
|
+
this.log(`[executeStepsInPersistentContext] POM denormalization skipped: originalScript not provided`);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
966
1049
|
// Build stepId -> statement index map before execution
|
|
967
1050
|
const statementIndexByStepId = new Map();
|
|
968
1051
|
for (let idx = 0; idx < allStatements.length; idx++) {
|
|
@@ -1012,6 +1095,54 @@ class ExecutionService {
|
|
|
1012
1095
|
this.log(`[executeStepsInPersistentContext] Jumping to step index ${nextIndex} (stepId: ${normalizedStepId})${normalizedNote} [${contextLabel}]`);
|
|
1013
1096
|
return targetIndex;
|
|
1014
1097
|
};
|
|
1098
|
+
// Scenario coverage tracking (@Scenario annotations)
|
|
1099
|
+
const scenarioTitlesOrdered = [];
|
|
1100
|
+
const scenarioTitleToIndex = new Map();
|
|
1101
|
+
const scenarioBoundaryByStatementIndex = new Map();
|
|
1102
|
+
for (let idx = 0; idx < allStatements.length; idx++) {
|
|
1103
|
+
const scenarioTitle = allStatements[idx].scenarioAnnotation?.trim();
|
|
1104
|
+
if (!scenarioTitle) {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
if (scenarioTitleToIndex.has(scenarioTitle)) {
|
|
1108
|
+
continue; // dedupe by title (ignore subsequent occurrences)
|
|
1109
|
+
}
|
|
1110
|
+
scenarioTitleToIndex.set(scenarioTitle, scenarioTitlesOrdered.length);
|
|
1111
|
+
scenarioTitlesOrdered.push(scenarioTitle);
|
|
1112
|
+
scenarioBoundaryByStatementIndex.set(idx, scenarioTitle);
|
|
1113
|
+
}
|
|
1114
|
+
let currentScenarioTitle;
|
|
1115
|
+
let currentScenarioIndex = -1;
|
|
1116
|
+
const completedScenarioTitles = new Set();
|
|
1117
|
+
const emitScenarioCompletion = async (title, status) => {
|
|
1118
|
+
if (!title || completedScenarioTitles.has(title)) {
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
completedScenarioTitles.add(title);
|
|
1122
|
+
if (this.progressReporter?.onScenarioCompletion) {
|
|
1123
|
+
await this.progressReporter.onScenarioCompletion(title, status);
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
const finalizeScenarioFailure = async () => {
|
|
1127
|
+
if (scenarioTitlesOrdered.length === 0) {
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
if (!currentScenarioTitle) {
|
|
1131
|
+
for (const title of scenarioTitlesOrdered) {
|
|
1132
|
+
await emitScenarioCompletion(title, progress_reporter_1.ScenarioCoverageStatus.NOT_ATTEMPTED);
|
|
1133
|
+
}
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
await emitScenarioCompletion(currentScenarioTitle, progress_reporter_1.ScenarioCoverageStatus.FAILED);
|
|
1137
|
+
for (let idx = currentScenarioIndex + 1; idx < scenarioTitlesOrdered.length; idx++) {
|
|
1138
|
+
await emitScenarioCompletion(scenarioTitlesOrdered[idx], progress_reporter_1.ScenarioCoverageStatus.NOT_ATTEMPTED);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
const finalizeScenarioSuccess = async () => {
|
|
1142
|
+
if (currentScenarioTitle) {
|
|
1143
|
+
await emitScenarioCompletion(currentScenarioTitle, progress_reporter_1.ScenarioCoverageStatus.SUCCESSFUL);
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1015
1146
|
// Execute all statements sequentially
|
|
1016
1147
|
// Variable declarations execute silently, other statements trigger callbacks
|
|
1017
1148
|
let stepNumber = 0; // Track step number (excludes variables)
|
|
@@ -1046,6 +1177,17 @@ class ExecutionService {
|
|
|
1046
1177
|
i = jumpIndexBefore;
|
|
1047
1178
|
continue;
|
|
1048
1179
|
}
|
|
1180
|
+
// Scenario boundary handling (before executing statement)
|
|
1181
|
+
const scenarioBoundaryTitle = scenarioBoundaryByStatementIndex.get(i);
|
|
1182
|
+
if (scenarioBoundaryTitle) {
|
|
1183
|
+
if (currentScenarioTitle && currentScenarioTitle !== scenarioBoundaryTitle) {
|
|
1184
|
+
await emitScenarioCompletion(currentScenarioTitle, progress_reporter_1.ScenarioCoverageStatus.SUCCESSFUL);
|
|
1185
|
+
}
|
|
1186
|
+
if (!completedScenarioTitles.has(scenarioBoundaryTitle)) {
|
|
1187
|
+
currentScenarioTitle = scenarioBoundaryTitle;
|
|
1188
|
+
currentScenarioIndex = scenarioTitleToIndex.get(scenarioBoundaryTitle) ?? -1;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1049
1191
|
// Check cancellation
|
|
1050
1192
|
if (this.progressReporter?.shouldContinue && options.jobId) {
|
|
1051
1193
|
const shouldContinue = await this.progressReporter.shouldContinue(options.jobId);
|
|
@@ -1079,14 +1221,6 @@ class ExecutionService {
|
|
|
1079
1221
|
let shouldSkipStep = false;
|
|
1080
1222
|
if (this.progressReporter?.beforeStepStart && options.jobId) {
|
|
1081
1223
|
const { description, baseStepId, stepId, runNumber } = stepMeta;
|
|
1082
|
-
// Log stepId usage for debugging
|
|
1083
|
-
if (stmt.stepId) {
|
|
1084
|
-
const stepIdPreview = stmt.stepId.length > 16 ? `${stmt.stepId.substring(0, 16)}...` : stmt.stepId;
|
|
1085
|
-
this.log(`[stepId] beforeStepStart: Using hash-based stepId: ${stepIdPreview} (stepNumber: ${stepNumber}, run: ${runNumber})`);
|
|
1086
|
-
}
|
|
1087
|
-
else {
|
|
1088
|
-
this.log(`[stepId] beforeStepStart: WARNING: No hash-based stepId, using fallback: ${baseStepId} (stepNumber: ${stepNumber}, run: ${runNumber})`);
|
|
1089
|
-
}
|
|
1090
1224
|
const beforeStepResult = await this.progressReporter.beforeStepStart({
|
|
1091
1225
|
stepId,
|
|
1092
1226
|
stepNumber,
|
|
@@ -1215,6 +1349,7 @@ class ExecutionService {
|
|
|
1215
1349
|
if (!isSharedContext) {
|
|
1216
1350
|
persistentContext.dispose();
|
|
1217
1351
|
}
|
|
1352
|
+
await finalizeScenarioFailure();
|
|
1218
1353
|
return { success: false, failedStepIndex: i, error: errorMsg };
|
|
1219
1354
|
}
|
|
1220
1355
|
else {
|
|
@@ -1270,6 +1405,7 @@ class ExecutionService {
|
|
|
1270
1405
|
if (!isSharedContext) {
|
|
1271
1406
|
persistentContext.dispose();
|
|
1272
1407
|
}
|
|
1408
|
+
await finalizeScenarioFailure();
|
|
1273
1409
|
return { success: false, failedStepIndex: i, error: errorMsg };
|
|
1274
1410
|
}
|
|
1275
1411
|
}
|
|
@@ -1279,6 +1415,7 @@ class ExecutionService {
|
|
|
1279
1415
|
if (!isSharedContext) {
|
|
1280
1416
|
persistentContext.dispose();
|
|
1281
1417
|
}
|
|
1418
|
+
await finalizeScenarioFailure();
|
|
1282
1419
|
return { success: false, failedStepIndex: i, error: `Variable declaration failed: ${errorMsg}` };
|
|
1283
1420
|
}
|
|
1284
1421
|
}
|
|
@@ -1299,6 +1436,7 @@ class ExecutionService {
|
|
|
1299
1436
|
code: s.code,
|
|
1300
1437
|
success: true
|
|
1301
1438
|
}));
|
|
1439
|
+
await finalizeScenarioSuccess();
|
|
1302
1440
|
return { success: true, updatedSteps };
|
|
1303
1441
|
}
|
|
1304
1442
|
/**
|
|
@@ -1401,12 +1539,18 @@ class ExecutionService {
|
|
|
1401
1539
|
}
|
|
1402
1540
|
// Execute script in persistent context (STOP on first error)
|
|
1403
1541
|
// Pre-parsed steps from consumer (if any) will be ignored - AST extracts from script
|
|
1542
|
+
this.log(`[runExactly] Calling executeStepsInPersistentContext (existing browser): ` +
|
|
1543
|
+
`runPomDenormalized=${request.runPomDenormalized}, ` +
|
|
1544
|
+
`originalScript=${!!request.script} (length: ${request.script?.length || 0}), ` +
|
|
1545
|
+
`tempDir=${!!request.tempDir}`);
|
|
1404
1546
|
const result = await this.executeStepsInPersistentContext(request.steps || [], // Pre-parsed steps (optional, for backward compatibility)
|
|
1405
1547
|
page, browser, context, {
|
|
1406
1548
|
mode: 'RUN_EXACTLY',
|
|
1407
1549
|
jobId: request.jobId,
|
|
1408
1550
|
tempDir: request.tempDir,
|
|
1409
|
-
originalScript: request.script // AST will extract statements from this
|
|
1551
|
+
originalScript: request.script, // AST will extract statements from this
|
|
1552
|
+
testFolderPath: request.testFolderPath,
|
|
1553
|
+
runPomDenormalized: request.runPomDenormalized
|
|
1410
1554
|
});
|
|
1411
1555
|
// LIFECYCLE: afterEndTest
|
|
1412
1556
|
if (this.progressReporter?.afterEndTest) {
|
|
@@ -1454,12 +1598,18 @@ class ExecutionService {
|
|
|
1454
1598
|
}
|
|
1455
1599
|
// Execute script in persistent context (STOP on first error)
|
|
1456
1600
|
// Pre-parsed steps from consumer (if any) will be ignored - AST extracts from script
|
|
1601
|
+
this.log(`[runExactly] Calling executeStepsInPersistentContext (new browser, attempt ${attempt}): ` +
|
|
1602
|
+
`runPomDenormalized=${request.runPomDenormalized}, ` +
|
|
1603
|
+
`originalScript=${!!request.script} (length: ${request.script?.length || 0}), ` +
|
|
1604
|
+
`tempDir=${!!request.tempDir}`);
|
|
1457
1605
|
const result = await this.executeStepsInPersistentContext(request.steps || [], // Pre-parsed steps (optional, for backward compatibility)
|
|
1458
1606
|
page, browser, context, {
|
|
1459
1607
|
mode: 'RUN_EXACTLY',
|
|
1460
1608
|
jobId: request.jobId,
|
|
1461
1609
|
tempDir: request.tempDir,
|
|
1462
|
-
originalScript: request.script // AST will extract statements from this
|
|
1610
|
+
originalScript: request.script, // AST will extract statements from this
|
|
1611
|
+
testFolderPath: request.testFolderPath,
|
|
1612
|
+
runPomDenormalized: request.runPomDenormalized
|
|
1463
1613
|
});
|
|
1464
1614
|
// LIFECYCLE: afterEndTest
|
|
1465
1615
|
if (this.progressReporter?.afterEndTest) {
|
|
@@ -1575,7 +1725,9 @@ class ExecutionService {
|
|
|
1575
1725
|
jobId: request.jobId,
|
|
1576
1726
|
model,
|
|
1577
1727
|
tempDir: request.tempDir,
|
|
1578
|
-
originalScript: request.script // Pass original script for import extraction
|
|
1728
|
+
originalScript: request.script, // Pass original script for import extraction
|
|
1729
|
+
testFolderPath: request.testFolderPath,
|
|
1730
|
+
runPomDenormalized: request.runPomDenormalized
|
|
1579
1731
|
});
|
|
1580
1732
|
const updatedSteps = result.updatedSteps || steps;
|
|
1581
1733
|
const allStepsSuccessful = result.success;
|
|
@@ -1886,7 +2038,7 @@ class ExecutionService {
|
|
|
1886
2038
|
this.log(`Executing ${flattened.fileLevelHooks.beforeAll.length} file-level beforeAll hook(s)...`);
|
|
1887
2039
|
try {
|
|
1888
2040
|
for (const hook of flattened.fileLevelHooks.beforeAll) {
|
|
1889
|
-
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, fileLevelContext || undefined, hook);
|
|
2041
|
+
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, fileLevelContext || undefined, hook);
|
|
1890
2042
|
}
|
|
1891
2043
|
this.log('File-level beforeAll hooks executed successfully');
|
|
1892
2044
|
}
|
|
@@ -1957,7 +2109,7 @@ class ExecutionService {
|
|
|
1957
2109
|
// Get or create suite context (includes file variables + suite variables)
|
|
1958
2110
|
const suiteContext = await getOrCreateSuiteContext(suitePath, suite.suiteVariables, flattened.fileVariables);
|
|
1959
2111
|
for (const hook of suite.beforeAll) {
|
|
1960
|
-
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, suiteContext, hook);
|
|
2112
|
+
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, suiteContext, hook);
|
|
1961
2113
|
}
|
|
1962
2114
|
suiteBeforeAllExecuted.add(suiteKey);
|
|
1963
2115
|
this.log(`Suite-level beforeAll hooks executed for suite: ${suiteDisplayName}`);
|
|
@@ -1982,7 +2134,7 @@ class ExecutionService {
|
|
|
1982
2134
|
this.log(`Executing ${flattened.fileLevelHooks.beforeEach.length} file-level beforeEach hook(s) for test: ${test.fullName}`);
|
|
1983
2135
|
try {
|
|
1984
2136
|
for (const hook of flattened.fileLevelHooks.beforeEach) {
|
|
1985
|
-
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, fileLevelContext || undefined, hook, jobId, testStepCounter);
|
|
2137
|
+
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, fileLevelContext || undefined, hook, jobId, testStepCounter);
|
|
1986
2138
|
testStepCounter += stepsExecuted;
|
|
1987
2139
|
}
|
|
1988
2140
|
}
|
|
@@ -2000,7 +2152,7 @@ class ExecutionService {
|
|
|
2000
2152
|
// Get or create suite context for this test's suite
|
|
2001
2153
|
const suiteContext = await getOrCreateSuiteContext(testData.suitePath, testData.suiteVariables, flattened.fileVariables);
|
|
2002
2154
|
for (const hook of testData.suiteBeforeEachHooks) {
|
|
2003
|
-
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, suiteContext, hook, jobId, testStepCounter);
|
|
2155
|
+
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, suiteContext, hook, jobId, testStepCounter);
|
|
2004
2156
|
testStepCounter += stepsExecuted;
|
|
2005
2157
|
}
|
|
2006
2158
|
}
|
|
@@ -2044,7 +2196,8 @@ class ExecutionService {
|
|
|
2044
2196
|
id: stepId,
|
|
2045
2197
|
code: stmt.code,
|
|
2046
2198
|
description: stmt.intentComment || stmt.code.trim().substring(0, 100) || '',
|
|
2047
|
-
isVariableDeclaration: stmt.isVariableDeclaration // Pass AST-based detection flag
|
|
2199
|
+
isVariableDeclaration: stmt.isVariableDeclaration, // Pass AST-based detection flag
|
|
2200
|
+
scenarioAnnotation: stmt.scenarioAnnotation
|
|
2048
2201
|
};
|
|
2049
2202
|
});
|
|
2050
2203
|
// Get or create suite context for this test's suite
|
|
@@ -2061,7 +2214,9 @@ class ExecutionService {
|
|
|
2061
2214
|
jobId: jobId,
|
|
2062
2215
|
tempDir: request.tempDir,
|
|
2063
2216
|
originalScript: testScript, // For module resolution (imports)
|
|
2064
|
-
model: request.model
|
|
2217
|
+
model: request.model,
|
|
2218
|
+
testFolderPath: request.testFolderPath,
|
|
2219
|
+
runPomDenormalized: request.runPomDenormalized
|
|
2065
2220
|
}, suiteContext // Pass shared context
|
|
2066
2221
|
);
|
|
2067
2222
|
if (!result.success) {
|
|
@@ -2110,7 +2265,7 @@ class ExecutionService {
|
|
|
2110
2265
|
// Get or create suite context for this test's suite
|
|
2111
2266
|
const suiteContext = await getOrCreateSuiteContext(testData.suitePath, testData.suiteVariables, flattened.fileVariables);
|
|
2112
2267
|
for (const hook of testData.suiteAfterEachHooks) {
|
|
2113
|
-
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, suiteContext, hook, jobId, testStepCounter);
|
|
2268
|
+
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, suiteContext, hook, jobId, testStepCounter);
|
|
2114
2269
|
testStepCounter += stepsExecuted;
|
|
2115
2270
|
}
|
|
2116
2271
|
}
|
|
@@ -2125,7 +2280,7 @@ class ExecutionService {
|
|
|
2125
2280
|
this.log(`Executing ${flattened.fileLevelHooks.afterEach.length} file-level afterEach hook(s) for test: ${test.fullName}`);
|
|
2126
2281
|
try {
|
|
2127
2282
|
for (const hook of flattened.fileLevelHooks.afterEach) {
|
|
2128
|
-
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, fileLevelContext || undefined, hook, jobId, testStepCounter);
|
|
2283
|
+
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, fileLevelContext || undefined, hook, jobId, testStepCounter);
|
|
2129
2284
|
testStepCounter += stepsExecuted;
|
|
2130
2285
|
}
|
|
2131
2286
|
}
|
|
@@ -2164,7 +2319,7 @@ class ExecutionService {
|
|
|
2164
2319
|
// Get or create suite context (should already exist, but safe to call)
|
|
2165
2320
|
const suiteContext = await getOrCreateSuiteContext(suitePath, suite.suiteVariables, flattened.fileVariables);
|
|
2166
2321
|
for (const hook of suite.afterAll) {
|
|
2167
|
-
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, suiteContext, hook);
|
|
2322
|
+
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, suiteContext, hook);
|
|
2168
2323
|
}
|
|
2169
2324
|
this.log(`Suite-level afterAll hooks executed for suite: ${suiteDisplayName}`);
|
|
2170
2325
|
}
|
|
@@ -2229,7 +2384,7 @@ class ExecutionService {
|
|
|
2229
2384
|
this.log(`Executing ${flattened.fileLevelHooks.afterAll.length} file-level afterAll hook(s)...`);
|
|
2230
2385
|
try {
|
|
2231
2386
|
for (const hook of flattened.fileLevelHooks.afterAll) {
|
|
2232
|
-
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, fileLevelContext || undefined, hook);
|
|
2387
|
+
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, fileLevelContext || undefined, hook);
|
|
2233
2388
|
}
|
|
2234
2389
|
this.log('File-level afterAll hooks executed successfully');
|
|
2235
2390
|
}
|
|
@@ -2295,7 +2450,7 @@ class ExecutionService {
|
|
|
2295
2450
|
* If jobId is provided, executes step-wise with callbacks for step reporting
|
|
2296
2451
|
* @returns Number of non-variable steps executed (for step counter tracking)
|
|
2297
2452
|
*/
|
|
2298
|
-
async executeHook(hookCode, page, context, browser, tempDir, originalScript, sharedContext, hook, jobId, stepIdOffset = 0) {
|
|
2453
|
+
async executeHook(hookCode, page, context, browser, tempDir, originalScript, testFolderPath, runPomDenormalized, sharedContext, hook, jobId, stepIdOffset = 0) {
|
|
2299
2454
|
const { expect, test } = require('@playwright/test');
|
|
2300
2455
|
const { ai } = require('ai-wright');
|
|
2301
2456
|
// Construct hook script with imports from original script (for module resolution)
|
|
@@ -2334,7 +2489,9 @@ class ExecutionService {
|
|
|
2334
2489
|
mode: types_1.ExecutionMode.RUN_EXACTLY,
|
|
2335
2490
|
jobId: jobId,
|
|
2336
2491
|
tempDir: tempDir,
|
|
2337
|
-
originalScript: hookScript
|
|
2492
|
+
originalScript: hookScript,
|
|
2493
|
+
testFolderPath: testFolderPath,
|
|
2494
|
+
runPomDenormalized: runPomDenormalized
|
|
2338
2495
|
}, hookContext // Pass shared context
|
|
2339
2496
|
);
|
|
2340
2497
|
if (!result.success) {
|