testchimp-runner-core 0.1.20 → 0.1.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/execution-service.d.ts.map +1 -1
- package/dist/execution-service.js +212 -103
- 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/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.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/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,EAA+C,MAAM,qBAAqB,CAAC;AAmbpG;;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;IAm2B7C;;;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;IAsxBxF;;;;;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}`);
|
|
@@ -963,6 +970,78 @@ class ExecutionService {
|
|
|
963
970
|
}
|
|
964
971
|
return { success: false, error: 'No script or steps provided for execution' };
|
|
965
972
|
}
|
|
973
|
+
if (options.runPomDenormalized && options.originalScript) {
|
|
974
|
+
const originalCount = allStatements.length;
|
|
975
|
+
const originalStepIds = allStatements
|
|
976
|
+
.map((stmt, idx) => ({ idx, stepId: stmt.stepId, code: stmt.code.substring(0, 50) }))
|
|
977
|
+
.filter(s => s.stepId);
|
|
978
|
+
this.log(`[executeStepsInPersistentContext] Starting POM denormalization: ${originalCount} original statements, ` +
|
|
979
|
+
`${originalStepIds.length} with stepIds`);
|
|
980
|
+
// Convert to ScriptStep[] for denormalization
|
|
981
|
+
const scriptSteps = allStatements.map(stmt => ({
|
|
982
|
+
code: stmt.code,
|
|
983
|
+
description: stmt.intentComment || '',
|
|
984
|
+
id: stmt.stepId, // stepId may not exist for InitializationCodeUtils results
|
|
985
|
+
isVariableDeclaration: stmt.isVariableDeclaration
|
|
986
|
+
}));
|
|
987
|
+
// Denormalize POM calls
|
|
988
|
+
const denormalizedSteps = pom_denormalizer_1.PomDenormalizer.denormalizePomSteps(scriptSteps, {
|
|
989
|
+
originalScript: options.originalScript,
|
|
990
|
+
tempDir: options.tempDir || '',
|
|
991
|
+
testFileDir: resolvedTestFileDir,
|
|
992
|
+
logger: (msg, level) => this.log(msg, level)
|
|
993
|
+
});
|
|
994
|
+
const denormalizedCount = denormalizedSteps.length;
|
|
995
|
+
const denormalizedStepIds = denormalizedSteps
|
|
996
|
+
.map((step, idx) => ({ idx, stepId: step.id, isVar: step.isVariableDeclaration, code: step.code.substring(0, 50) }))
|
|
997
|
+
.filter(s => s.stepId && !s.isVar);
|
|
998
|
+
this.log(`[executeStepsInPersistentContext] Denormalization complete: ${originalCount} -> ${denormalizedCount} statements ` +
|
|
999
|
+
`(${denormalizedCount - originalCount > 0 ? '+' : ''}${denormalizedCount - originalCount}), ` +
|
|
1000
|
+
`${denormalizedStepIds.length} non-variable steps with stepIds`);
|
|
1001
|
+
if (denormalizedStepIds.length > 0 && this.log) {
|
|
1002
|
+
const stepIdPreview = denormalizedStepIds.slice(0, 5).map(s => `idx=${s.idx} stepId=${s.stepId?.substring(0, 16)}...`).join(', ');
|
|
1003
|
+
this.log(`[executeStepsInPersistentContext] Sample denormalized stepIds: ${stepIdPreview}${denormalizedStepIds.length > 5 ? '...' : ''}`);
|
|
1004
|
+
}
|
|
1005
|
+
// Convert back to inline type format
|
|
1006
|
+
allStatements = denormalizedSteps.map(step => ({
|
|
1007
|
+
code: step.code,
|
|
1008
|
+
isVariableDeclaration: step.isVariableDeclaration || false,
|
|
1009
|
+
intentComment: step.description !== step.code ? step.description : undefined,
|
|
1010
|
+
screenStateAnnotation: undefined,
|
|
1011
|
+
stepId: step.id
|
|
1012
|
+
}));
|
|
1013
|
+
// Verify stepId preservation
|
|
1014
|
+
const preservedStepIds = allStatements
|
|
1015
|
+
.map((stmt, idx) => ({ idx, stepId: stmt.stepId, isVar: stmt.isVariableDeclaration }))
|
|
1016
|
+
.filter(s => s.stepId && !s.isVar);
|
|
1017
|
+
this.log(`[executeStepsInPersistentContext] StepId preservation: ${preservedStepIds.length} non-variable statements have stepIds ` +
|
|
1018
|
+
`(expected: ${denormalizedStepIds.length})`);
|
|
1019
|
+
// Verify stepId matching between denormalized and preserved
|
|
1020
|
+
if (preservedStepIds.length !== denormalizedStepIds.length) {
|
|
1021
|
+
this.log(`[executeStepsInPersistentContext] WARNING: StepId count mismatch! ` +
|
|
1022
|
+
`Denormalized: ${denormalizedStepIds.length}, Preserved: ${preservedStepIds.length}`, 'warn');
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
// Check if stepIds match
|
|
1026
|
+
let matchCount = 0;
|
|
1027
|
+
for (let i = 0; i < Math.min(denormalizedStepIds.length, preservedStepIds.length); i++) {
|
|
1028
|
+
const denorm = denormalizedStepIds[i];
|
|
1029
|
+
const preserved = preservedStepIds.find(p => p.idx === denorm.idx);
|
|
1030
|
+
if (preserved && preserved.stepId === denorm.stepId) {
|
|
1031
|
+
matchCount++;
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
this.log(`[executeStepsInPersistentContext] StepId matching: ${matchCount}/${Math.min(denormalizedStepIds.length, preservedStepIds.length)} stepIds match`);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
if (!options.runPomDenormalized) {
|
|
1039
|
+
this.log(`[executeStepsInPersistentContext] POM denormalization skipped: runPomDenormalized=false`);
|
|
1040
|
+
}
|
|
1041
|
+
else if (!options.originalScript) {
|
|
1042
|
+
this.log(`[executeStepsInPersistentContext] POM denormalization skipped: originalScript not provided`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
966
1045
|
// Build stepId -> statement index map before execution
|
|
967
1046
|
const statementIndexByStepId = new Map();
|
|
968
1047
|
for (let idx = 0; idx < allStatements.length; idx++) {
|
|
@@ -980,6 +1059,38 @@ class ExecutionService {
|
|
|
980
1059
|
}
|
|
981
1060
|
return count;
|
|
982
1061
|
};
|
|
1062
|
+
const normalizeJumpStepId = (stepId) => stepId.replace(/#\d+$/, '');
|
|
1063
|
+
const consumeJumpRequest = (contextLabel) => {
|
|
1064
|
+
const jumpRequest = page.__tcExecutionJump;
|
|
1065
|
+
if (!jumpRequest || typeof jumpRequest !== 'object' || !jumpRequest.toStepId) {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
if (jumpCount >= maxJumpCount) {
|
|
1069
|
+
this.log(`[executeStepsInPersistentContext] Max jump count ${maxJumpCount} reached - ignoring jump`, 'warn');
|
|
1070
|
+
delete page.__tcExecutionJump;
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
const rawStepId = String(jumpRequest.toStepId);
|
|
1074
|
+
const normalizedStepId = normalizeJumpStepId(rawStepId);
|
|
1075
|
+
const targetIndex = statementIndexByStepId.get(normalizedStepId);
|
|
1076
|
+
delete page.__tcExecutionJump;
|
|
1077
|
+
if (targetIndex === undefined) {
|
|
1078
|
+
this.log(`[executeStepsInPersistentContext] Jump target stepId not found: ${rawStepId}`, 'warn');
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
const nextIndex = targetIndex + 1;
|
|
1082
|
+
jumpCount += 1;
|
|
1083
|
+
if (nextIndex >= allStatements.length) {
|
|
1084
|
+
this.log(`[executeStepsInPersistentContext] Jump target is last statement; ending execution`, 'warn');
|
|
1085
|
+
return allStatements.length;
|
|
1086
|
+
}
|
|
1087
|
+
stepNumber = countNonVariableStatementsBefore(nextIndex);
|
|
1088
|
+
const normalizedNote = rawStepId !== normalizedStepId
|
|
1089
|
+
? ` (normalized from ${rawStepId})`
|
|
1090
|
+
: '';
|
|
1091
|
+
this.log(`[executeStepsInPersistentContext] Jumping to step index ${nextIndex} (stepId: ${normalizedStepId})${normalizedNote} [${contextLabel}]`);
|
|
1092
|
+
return targetIndex;
|
|
1093
|
+
};
|
|
983
1094
|
// Execute all statements sequentially
|
|
984
1095
|
// Variable declarations execute silently, other statements trigger callbacks
|
|
985
1096
|
let stepNumber = 0; // Track step number (excludes variables)
|
|
@@ -1009,32 +1120,10 @@ class ExecutionService {
|
|
|
1009
1120
|
const isVariable = stmt.isVariableDeclaration;
|
|
1010
1121
|
let stepMeta;
|
|
1011
1122
|
// Jump request handling (allow rewinding to a prior checkpoint step)
|
|
1012
|
-
const
|
|
1013
|
-
if (
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
delete page.__tcExecutionJump;
|
|
1017
|
-
}
|
|
1018
|
-
else {
|
|
1019
|
-
const targetIndex = statementIndexByStepId.get(jumpRequest.toStepId);
|
|
1020
|
-
delete page.__tcExecutionJump;
|
|
1021
|
-
if (targetIndex === undefined) {
|
|
1022
|
-
this.log(`[executeStepsInPersistentContext] Jump target stepId not found: ${jumpRequest.toStepId}`, 'warn');
|
|
1023
|
-
}
|
|
1024
|
-
else {
|
|
1025
|
-
const nextIndex = targetIndex + 1;
|
|
1026
|
-
jumpCount += 1;
|
|
1027
|
-
if (nextIndex >= allStatements.length) {
|
|
1028
|
-
this.log(`[executeStepsInPersistentContext] Jump target is last statement; ending execution`, 'warn');
|
|
1029
|
-
i = allStatements.length;
|
|
1030
|
-
continue;
|
|
1031
|
-
}
|
|
1032
|
-
stepNumber = countNonVariableStatementsBefore(nextIndex);
|
|
1033
|
-
this.log(`[executeStepsInPersistentContext] Jumping to step index ${nextIndex} (stepId: ${jumpRequest.toStepId})`);
|
|
1034
|
-
i = targetIndex;
|
|
1035
|
-
continue;
|
|
1036
|
-
}
|
|
1037
|
-
}
|
|
1123
|
+
const jumpIndexBefore = consumeJumpRequest('before-step');
|
|
1124
|
+
if (jumpIndexBefore !== null) {
|
|
1125
|
+
i = jumpIndexBefore;
|
|
1126
|
+
continue;
|
|
1038
1127
|
}
|
|
1039
1128
|
// Check cancellation
|
|
1040
1129
|
if (this.progressReporter?.shouldContinue && options.jobId) {
|
|
@@ -1069,14 +1158,6 @@ class ExecutionService {
|
|
|
1069
1158
|
let shouldSkipStep = false;
|
|
1070
1159
|
if (this.progressReporter?.beforeStepStart && options.jobId) {
|
|
1071
1160
|
const { description, baseStepId, stepId, runNumber } = stepMeta;
|
|
1072
|
-
// Log stepId usage for debugging
|
|
1073
|
-
if (stmt.stepId) {
|
|
1074
|
-
const stepIdPreview = stmt.stepId.length > 16 ? `${stmt.stepId.substring(0, 16)}...` : stmt.stepId;
|
|
1075
|
-
this.log(`[stepId] beforeStepStart: Using hash-based stepId: ${stepIdPreview} (stepNumber: ${stepNumber}, run: ${runNumber})`);
|
|
1076
|
-
}
|
|
1077
|
-
else {
|
|
1078
|
-
this.log(`[stepId] beforeStepStart: WARNING: No hash-based stepId, using fallback: ${baseStepId} (stepNumber: ${stepNumber}, run: ${runNumber})`);
|
|
1079
|
-
}
|
|
1080
1161
|
const beforeStepResult = await this.progressReporter.beforeStepStart({
|
|
1081
1162
|
stepId,
|
|
1082
1163
|
stepNumber,
|
|
@@ -1119,6 +1200,11 @@ class ExecutionService {
|
|
|
1119
1200
|
}
|
|
1120
1201
|
// Skip step execution if beforeStepStart returned skip: true
|
|
1121
1202
|
if (shouldSkipStep) {
|
|
1203
|
+
const jumpIndexAfterSkip = consumeJumpRequest('after-skip');
|
|
1204
|
+
if (jumpIndexAfterSkip !== null) {
|
|
1205
|
+
i = jumpIndexAfterSkip;
|
|
1206
|
+
continue;
|
|
1207
|
+
}
|
|
1122
1208
|
continue; // Skip to next step
|
|
1123
1209
|
}
|
|
1124
1210
|
}
|
|
@@ -1151,6 +1237,11 @@ class ExecutionService {
|
|
|
1151
1237
|
wasRepaired: false
|
|
1152
1238
|
});
|
|
1153
1239
|
}
|
|
1240
|
+
const jumpIndexAfterSuccess = consumeJumpRequest('after-success');
|
|
1241
|
+
if (jumpIndexAfterSuccess !== null) {
|
|
1242
|
+
i = jumpIndexAfterSuccess;
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1154
1245
|
}
|
|
1155
1246
|
else {
|
|
1156
1247
|
this.log(` Executed variable declaration (no callback): ${stmt.code}`);
|
|
@@ -1381,12 +1472,18 @@ class ExecutionService {
|
|
|
1381
1472
|
}
|
|
1382
1473
|
// Execute script in persistent context (STOP on first error)
|
|
1383
1474
|
// Pre-parsed steps from consumer (if any) will be ignored - AST extracts from script
|
|
1475
|
+
this.log(`[runExactly] Calling executeStepsInPersistentContext (existing browser): ` +
|
|
1476
|
+
`runPomDenormalized=${request.runPomDenormalized}, ` +
|
|
1477
|
+
`originalScript=${!!request.script} (length: ${request.script?.length || 0}), ` +
|
|
1478
|
+
`tempDir=${!!request.tempDir}`);
|
|
1384
1479
|
const result = await this.executeStepsInPersistentContext(request.steps || [], // Pre-parsed steps (optional, for backward compatibility)
|
|
1385
1480
|
page, browser, context, {
|
|
1386
1481
|
mode: 'RUN_EXACTLY',
|
|
1387
1482
|
jobId: request.jobId,
|
|
1388
1483
|
tempDir: request.tempDir,
|
|
1389
|
-
originalScript: request.script // AST will extract statements from this
|
|
1484
|
+
originalScript: request.script, // AST will extract statements from this
|
|
1485
|
+
testFolderPath: request.testFolderPath,
|
|
1486
|
+
runPomDenormalized: request.runPomDenormalized
|
|
1390
1487
|
});
|
|
1391
1488
|
// LIFECYCLE: afterEndTest
|
|
1392
1489
|
if (this.progressReporter?.afterEndTest) {
|
|
@@ -1434,12 +1531,18 @@ class ExecutionService {
|
|
|
1434
1531
|
}
|
|
1435
1532
|
// Execute script in persistent context (STOP on first error)
|
|
1436
1533
|
// Pre-parsed steps from consumer (if any) will be ignored - AST extracts from script
|
|
1534
|
+
this.log(`[runExactly] Calling executeStepsInPersistentContext (new browser, attempt ${attempt}): ` +
|
|
1535
|
+
`runPomDenormalized=${request.runPomDenormalized}, ` +
|
|
1536
|
+
`originalScript=${!!request.script} (length: ${request.script?.length || 0}), ` +
|
|
1537
|
+
`tempDir=${!!request.tempDir}`);
|
|
1437
1538
|
const result = await this.executeStepsInPersistentContext(request.steps || [], // Pre-parsed steps (optional, for backward compatibility)
|
|
1438
1539
|
page, browser, context, {
|
|
1439
1540
|
mode: 'RUN_EXACTLY',
|
|
1440
1541
|
jobId: request.jobId,
|
|
1441
1542
|
tempDir: request.tempDir,
|
|
1442
|
-
originalScript: request.script // AST will extract statements from this
|
|
1543
|
+
originalScript: request.script, // AST will extract statements from this
|
|
1544
|
+
testFolderPath: request.testFolderPath,
|
|
1545
|
+
runPomDenormalized: request.runPomDenormalized
|
|
1443
1546
|
});
|
|
1444
1547
|
// LIFECYCLE: afterEndTest
|
|
1445
1548
|
if (this.progressReporter?.afterEndTest) {
|
|
@@ -1555,7 +1658,9 @@ class ExecutionService {
|
|
|
1555
1658
|
jobId: request.jobId,
|
|
1556
1659
|
model,
|
|
1557
1660
|
tempDir: request.tempDir,
|
|
1558
|
-
originalScript: request.script // Pass original script for import extraction
|
|
1661
|
+
originalScript: request.script, // Pass original script for import extraction
|
|
1662
|
+
testFolderPath: request.testFolderPath,
|
|
1663
|
+
runPomDenormalized: request.runPomDenormalized
|
|
1559
1664
|
});
|
|
1560
1665
|
const updatedSteps = result.updatedSteps || steps;
|
|
1561
1666
|
const allStepsSuccessful = result.success;
|
|
@@ -1866,7 +1971,7 @@ class ExecutionService {
|
|
|
1866
1971
|
this.log(`Executing ${flattened.fileLevelHooks.beforeAll.length} file-level beforeAll hook(s)...`);
|
|
1867
1972
|
try {
|
|
1868
1973
|
for (const hook of flattened.fileLevelHooks.beforeAll) {
|
|
1869
|
-
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, fileLevelContext || undefined, hook);
|
|
1974
|
+
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, fileLevelContext || undefined, hook);
|
|
1870
1975
|
}
|
|
1871
1976
|
this.log('File-level beforeAll hooks executed successfully');
|
|
1872
1977
|
}
|
|
@@ -1937,7 +2042,7 @@ class ExecutionService {
|
|
|
1937
2042
|
// Get or create suite context (includes file variables + suite variables)
|
|
1938
2043
|
const suiteContext = await getOrCreateSuiteContext(suitePath, suite.suiteVariables, flattened.fileVariables);
|
|
1939
2044
|
for (const hook of suite.beforeAll) {
|
|
1940
|
-
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, suiteContext, hook);
|
|
2045
|
+
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, suiteContext, hook);
|
|
1941
2046
|
}
|
|
1942
2047
|
suiteBeforeAllExecuted.add(suiteKey);
|
|
1943
2048
|
this.log(`Suite-level beforeAll hooks executed for suite: ${suiteDisplayName}`);
|
|
@@ -1962,7 +2067,7 @@ class ExecutionService {
|
|
|
1962
2067
|
this.log(`Executing ${flattened.fileLevelHooks.beforeEach.length} file-level beforeEach hook(s) for test: ${test.fullName}`);
|
|
1963
2068
|
try {
|
|
1964
2069
|
for (const hook of flattened.fileLevelHooks.beforeEach) {
|
|
1965
|
-
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, fileLevelContext || undefined, hook, jobId, testStepCounter);
|
|
2070
|
+
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, fileLevelContext || undefined, hook, jobId, testStepCounter);
|
|
1966
2071
|
testStepCounter += stepsExecuted;
|
|
1967
2072
|
}
|
|
1968
2073
|
}
|
|
@@ -1980,7 +2085,7 @@ class ExecutionService {
|
|
|
1980
2085
|
// Get or create suite context for this test's suite
|
|
1981
2086
|
const suiteContext = await getOrCreateSuiteContext(testData.suitePath, testData.suiteVariables, flattened.fileVariables);
|
|
1982
2087
|
for (const hook of testData.suiteBeforeEachHooks) {
|
|
1983
|
-
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, suiteContext, hook, jobId, testStepCounter);
|
|
2088
|
+
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, suiteContext, hook, jobId, testStepCounter);
|
|
1984
2089
|
testStepCounter += stepsExecuted;
|
|
1985
2090
|
}
|
|
1986
2091
|
}
|
|
@@ -2041,7 +2146,9 @@ class ExecutionService {
|
|
|
2041
2146
|
jobId: jobId,
|
|
2042
2147
|
tempDir: request.tempDir,
|
|
2043
2148
|
originalScript: testScript, // For module resolution (imports)
|
|
2044
|
-
model: request.model
|
|
2149
|
+
model: request.model,
|
|
2150
|
+
testFolderPath: request.testFolderPath,
|
|
2151
|
+
runPomDenormalized: request.runPomDenormalized
|
|
2045
2152
|
}, suiteContext // Pass shared context
|
|
2046
2153
|
);
|
|
2047
2154
|
if (!result.success) {
|
|
@@ -2090,7 +2197,7 @@ class ExecutionService {
|
|
|
2090
2197
|
// Get or create suite context for this test's suite
|
|
2091
2198
|
const suiteContext = await getOrCreateSuiteContext(testData.suitePath, testData.suiteVariables, flattened.fileVariables);
|
|
2092
2199
|
for (const hook of testData.suiteAfterEachHooks) {
|
|
2093
|
-
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, suiteContext, hook, jobId, testStepCounter);
|
|
2200
|
+
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, suiteContext, hook, jobId, testStepCounter);
|
|
2094
2201
|
testStepCounter += stepsExecuted;
|
|
2095
2202
|
}
|
|
2096
2203
|
}
|
|
@@ -2105,7 +2212,7 @@ class ExecutionService {
|
|
|
2105
2212
|
this.log(`Executing ${flattened.fileLevelHooks.afterEach.length} file-level afterEach hook(s) for test: ${test.fullName}`);
|
|
2106
2213
|
try {
|
|
2107
2214
|
for (const hook of flattened.fileLevelHooks.afterEach) {
|
|
2108
|
-
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, fileLevelContext || undefined, hook, jobId, testStepCounter);
|
|
2215
|
+
const stepsExecuted = await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, fileLevelContext || undefined, hook, jobId, testStepCounter);
|
|
2109
2216
|
testStepCounter += stepsExecuted;
|
|
2110
2217
|
}
|
|
2111
2218
|
}
|
|
@@ -2144,7 +2251,7 @@ class ExecutionService {
|
|
|
2144
2251
|
// Get or create suite context (should already exist, but safe to call)
|
|
2145
2252
|
const suiteContext = await getOrCreateSuiteContext(suitePath, suite.suiteVariables, flattened.fileVariables);
|
|
2146
2253
|
for (const hook of suite.afterAll) {
|
|
2147
|
-
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, suiteContext, hook);
|
|
2254
|
+
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, suiteContext, hook);
|
|
2148
2255
|
}
|
|
2149
2256
|
this.log(`Suite-level afterAll hooks executed for suite: ${suiteDisplayName}`);
|
|
2150
2257
|
}
|
|
@@ -2209,7 +2316,7 @@ class ExecutionService {
|
|
|
2209
2316
|
this.log(`Executing ${flattened.fileLevelHooks.afterAll.length} file-level afterAll hook(s)...`);
|
|
2210
2317
|
try {
|
|
2211
2318
|
for (const hook of flattened.fileLevelHooks.afterAll) {
|
|
2212
|
-
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, fileLevelContext || undefined, hook);
|
|
2319
|
+
await this.executeHook(hook.code, page, context, browser, request.tempDir, originalScript, request.testFolderPath, request.runPomDenormalized, fileLevelContext || undefined, hook);
|
|
2213
2320
|
}
|
|
2214
2321
|
this.log('File-level afterAll hooks executed successfully');
|
|
2215
2322
|
}
|
|
@@ -2275,7 +2382,7 @@ class ExecutionService {
|
|
|
2275
2382
|
* If jobId is provided, executes step-wise with callbacks for step reporting
|
|
2276
2383
|
* @returns Number of non-variable steps executed (for step counter tracking)
|
|
2277
2384
|
*/
|
|
2278
|
-
async executeHook(hookCode, page, context, browser, tempDir, originalScript, sharedContext, hook, jobId, stepIdOffset = 0) {
|
|
2385
|
+
async executeHook(hookCode, page, context, browser, tempDir, originalScript, testFolderPath, runPomDenormalized, sharedContext, hook, jobId, stepIdOffset = 0) {
|
|
2279
2386
|
const { expect, test } = require('@playwright/test');
|
|
2280
2387
|
const { ai } = require('ai-wright');
|
|
2281
2388
|
// Construct hook script with imports from original script (for module resolution)
|
|
@@ -2314,7 +2421,9 @@ class ExecutionService {
|
|
|
2314
2421
|
mode: types_1.ExecutionMode.RUN_EXACTLY,
|
|
2315
2422
|
jobId: jobId,
|
|
2316
2423
|
tempDir: tempDir,
|
|
2317
|
-
originalScript: hookScript
|
|
2424
|
+
originalScript: hookScript,
|
|
2425
|
+
testFolderPath: testFolderPath,
|
|
2426
|
+
runPomDenormalized: runPomDenormalized
|
|
2318
2427
|
}, hookContext // Pass shared context
|
|
2319
2428
|
);
|
|
2320
2429
|
if (!result.success) {
|