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.
@@ -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;AAW3C,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;IA6tB7C;;;OAGG;YACW,iBAAiB;YAiGjB,UAAU;YAkLV,eAAe;YAoKf,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;IAorBxF;;;;;OAKG;YACW,WAAW;CAgI1B"}
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
- let testFileDir = path.join(options.tempDir, 'tests'); // Default to tests directory
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 jumpRequest = page.__tcExecutionJump;
1013
- if (jumpRequest && typeof jumpRequest === 'object' && jumpRequest.toStepId) {
1014
- if (jumpCount >= maxJumpCount) {
1015
- this.log(`[executeStepsInPersistentContext] Max jump count ${maxJumpCount} reached - ignoring jump`, 'warn');
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) {