testchimp-runner-core 0.0.16 → 0.0.17

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.
@@ -27,6 +27,7 @@ export class ExecutionService {
27
27
  private creditUsageService: CreditUsageService;
28
28
  private maxConcurrentExecutions: number;
29
29
  private activeExecutions: Set<Promise<any>> = new Set();
30
+ private logger?: (message: string, level?: 'log' | 'error' | 'warn') => void;
30
31
 
31
32
  constructor(authConfig?: AuthConfig, backendUrl?: string, maxConcurrentExecutions: number = 10) {
32
33
  this.playwrightService = new PlaywrightService();
@@ -35,6 +36,30 @@ export class ExecutionService {
35
36
  this.maxConcurrentExecutions = maxConcurrentExecutions;
36
37
  }
37
38
 
39
+ /**
40
+ * Set a logger callback for capturing execution logs
41
+ */
42
+ setLogger(logger: (message: string, level?: 'log' | 'error' | 'warn') => void): void {
43
+ this.logger = logger;
44
+ }
45
+
46
+ /**
47
+ * Log a message using the configured logger or console
48
+ */
49
+ private log(message: string, level: 'log' | 'error' | 'warn' = 'log'): void {
50
+ if (this.logger) {
51
+ this.logger(message, level);
52
+ } else {
53
+ if (level === 'error') {
54
+ console.error(message);
55
+ } else if (level === 'warn') {
56
+ console.warn(message);
57
+ } else {
58
+ console.log(message);
59
+ }
60
+ }
61
+ }
62
+
38
63
  /**
39
64
  * Initialize the execution service
40
65
  */
@@ -189,7 +214,7 @@ export class ExecutionService {
189
214
  const totalAttempts = deflakeRunCount + 1; // Original run + deflake attempts
190
215
  let lastError: Error | null = null;
191
216
 
192
- console.log(`runExactly: deflake_run_count = ${request.deflake_run_count}, totalAttempts = ${totalAttempts}`);
217
+ this.log(`runExactly: deflake_run_count = ${request.deflake_run_count}, totalAttempts = ${totalAttempts}`);
193
218
 
194
219
  // Script content should be provided by the caller (TestChimpService)
195
220
  // The TestChimpService handles file reading through the appropriate FileHandler
@@ -198,7 +223,7 @@ export class ExecutionService {
198
223
  }
199
224
 
200
225
  for (let attempt = 1; attempt <= totalAttempts; attempt++) {
201
- console.log(`Attempting deflake run ${attempt}/${totalAttempts}`);
226
+ this.log(`Attempting deflake run ${attempt}/${totalAttempts}`);
202
227
  const { browser, context, page } = await this.initializeBrowser(request.playwrightConfig, request.headless, request.playwrightConfigFilePath);
203
228
 
204
229
  try {
@@ -215,7 +240,7 @@ export class ExecutionService {
215
240
  };
216
241
  } catch (error) {
217
242
  lastError = error instanceof Error ? error : new Error('Script execution failed');
218
- console.log(`Initial run failed: ${lastError.message}`);
243
+ this.log(`Initial run failed: ${lastError.message}`);
219
244
 
220
245
  try {
221
246
  await browser.close();
@@ -225,7 +250,7 @@ export class ExecutionService {
225
250
 
226
251
  // If this is not the last attempt, continue to next attempt
227
252
  if (attempt < totalAttempts) {
228
- console.log(`Deflaking attempt ${attempt} failed, trying again... (${attempt + 1}/${totalAttempts})`);
253
+ this.log(`Deflaking attempt ${attempt} failed, trying again... (${attempt + 1}/${totalAttempts})`);
229
254
  continue;
230
255
  }
231
256
  }
@@ -250,7 +275,7 @@ export class ExecutionService {
250
275
  }
251
276
 
252
277
  // First, try runExactly (which includes deflaking if configured)
253
- console.log('Attempting runExactly first (with deflaking if configured)...');
278
+ this.log('Attempting runExactly first (with deflaking if configured)...');
254
279
  const runExactlyResult = await this.runExactly(request, startTime, model);
255
280
 
256
281
  // If runExactly succeeded, return that result
@@ -259,18 +284,18 @@ export class ExecutionService {
259
284
  }
260
285
 
261
286
  // runExactly failed, start AI repair
262
- console.log('runExactly failed, starting AI repair process...');
287
+ this.log('runExactly failed, starting AI repair process...');
263
288
 
264
289
  try {
265
290
 
266
291
  // Start browser initialization and script parsing in parallel for faster startup
267
- console.log('Initializing repair browser and parsing script...');
292
+ this.log('Initializing repair browser and parsing script...');
268
293
  const [steps, { browser: repairBrowser, context: repairContext, page: repairPage }] = await Promise.all([
269
294
  this.parseScriptIntoSteps(request.script, model),
270
295
  this.initializeBrowser(request.playwrightConfig, request.headless, request.playwrightConfigFilePath) // Use request.headless (defaults to false/headed)
271
296
  ]);
272
297
 
273
- console.log('Starting AI repair with parsed steps...');
298
+ this.log('Starting AI repair with parsed steps...');
274
299
  const updatedSteps = await this.repairStepsWithAI(steps, repairPage, repairFlexibility, model);
275
300
 
276
301
  // Always generate the updated script
@@ -283,13 +308,13 @@ export class ExecutionService {
283
308
  const hasSuccessfulRepairs = updatedSteps.some(step => step.success);
284
309
 
285
310
  // Debug: Log step success status
286
- console.log('Step success status:', updatedSteps.map((step, index) => `Step ${index + 1}: ${step.success ? 'SUCCESS' : 'FAILED'}`));
287
- console.log('All steps successful:', allStepsSuccessful);
288
- console.log('Has successful repairs:', hasSuccessfulRepairs);
311
+ this.log('Step success status: ' + updatedSteps.map((step, index) => `Step ${index + 1}: ${step.success ? 'SUCCESS' : 'FAILED'}`).join(', '));
312
+ this.log(`All steps successful: ${allStepsSuccessful}`);
313
+ this.log(`Has successful repairs: ${hasSuccessfulRepairs}`);
289
314
 
290
315
  // Debug: Log individual step details
291
316
  updatedSteps.forEach((step, index) => {
292
- console.log(`Step ${index + 1} details: success=${step.success}, description="${step.description}"`);
317
+ this.log(`Step ${index + 1} details: success=${step.success}, description="${step.description}"`);
293
318
  });
294
319
 
295
320
  // Update file if we have any successful repairs (partial or complete)
@@ -345,14 +370,14 @@ export class ExecutionService {
345
370
  private async parseScriptIntoSteps(script: string, model: string): Promise<(ScriptStep & { success?: boolean; error?: string })[]> {
346
371
  // First try LLM-based parsing
347
372
  try {
348
- console.log('Attempting LLM-based script parsing...');
373
+ this.log('Attempting LLM-based script parsing...');
349
374
  const result = await this.llmFacade.parseScriptIntoSteps(script, model);
350
- console.log('LLM parsing successful, got', result.length, 'steps');
375
+ this.log(`LLM parsing successful, got ${result.length} steps`);
351
376
  return result;
352
377
  } catch (error) {
353
- console.log('LLM parsing failed, falling back to code parsing:', error);
378
+ this.log(`LLM parsing failed, falling back to code parsing: ${error}`);
354
379
  const fallbackResult = this.parseScriptIntoStepsFallback(script);
355
- console.log('Fallback parsing successful, got', fallbackResult.length, 'steps');
380
+ this.log(`Fallback parsing successful, got ${fallbackResult.length} steps`);
356
381
  return fallbackResult;
357
382
  }
358
383
  }
@@ -428,26 +453,26 @@ export class ExecutionService {
428
453
  let i = 0;
429
454
  while (i < updatedSteps.length) {
430
455
  const step = updatedSteps[i];
431
- console.log(`Loop iteration: i=${i}, step description="${step.description}", total steps=${updatedSteps.length}`);
456
+ this.log(`Loop iteration: i=${i}, step description="${step.description}", total steps=${updatedSteps.length}`);
432
457
 
433
458
  try {
434
459
  // Try to execute the step directly without context replay
435
- console.log(`Attempting Step ${i + 1}: ${step.description}`);
436
- console.log(` Code: ${step.code}`);
460
+ this.log(`Attempting Step ${i + 1}: ${step.description}`);
461
+ this.log(` Code: ${step.code}`);
437
462
  await this.executeStepCode(step.code, page);
438
463
  step.success = true;
439
- console.log(`Step ${i + 1} executed successfully: ${step.description}`);
440
- console.log(`Step ${i + 1} success status set to: ${step.success}`);
464
+ this.log(`Step ${i + 1} executed successfully: ${step.description}`);
465
+ this.log(`Step ${i + 1} success status set to: ${step.success}`);
441
466
 
442
467
  // Add this step's code to the execution context for future steps (for variable tracking)
443
468
  executionContext += step.code + '\n';
444
469
  i++; // Move to next step
445
470
  } catch (error) {
446
- console.log(`Step ${i + 1} failed: ${step.description}`);
447
- console.log(` Failed code: ${step.code}`);
448
- console.log(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
471
+ this.log(`Step ${i + 1} failed: ${step.description}`);
472
+ this.log(` Failed code: ${step.code}`);
473
+ this.log(` Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
449
474
  if (error instanceof Error && error.stack) {
450
- console.log(` Stack trace: ${error.stack}`);
475
+ this.log(` Stack trace: ${error.stack}`);
451
476
  }
452
477
  step.success = false;
453
478
  step.error = this.safeSerializeError(error);
@@ -465,7 +490,7 @@ export class ExecutionService {
465
490
  const originalCode = step.code;
466
491
 
467
492
  for (let attempt = 1; attempt <= maxTries; attempt++) {
468
- console.log(`Step ${i + 1} repair attempt ${attempt}/${maxTries}`);
493
+ this.log(`Step ${i + 1} repair attempt ${attempt}/${maxTries}`);
469
494
 
470
495
  // Get current page state for AI repair
471
496
  const pageInfo = await this.getEnhancedPageInfo(page);
@@ -488,7 +513,7 @@ export class ExecutionService {
488
513
  );
489
514
 
490
515
  if (!repairSuggestion.shouldContinue) {
491
- console.log(`AI decided to stop repair at attempt ${attempt}: ${repairSuggestion.reason}`);
516
+ this.log(`AI decided to stop repair at attempt ${attempt}: ${repairSuggestion.reason}`);
492
517
  break;
493
518
  }
494
519
 
@@ -501,20 +526,14 @@ export class ExecutionService {
501
526
  insertAfterIndex: repairSuggestion.action.operation === StepOperation.INSERT ? i - 1 : undefined // For INSERT, insert before current step
502
527
  };
503
528
 
504
- console.log(`🔧 Applying repair action:`, {
505
- operation: repairAction.operation,
506
- stepIndex: repairAction.stepIndex,
507
- insertAfterIndex: repairAction.insertAfterIndex,
508
- newStepDescription: repairAction.newStep?.description,
509
- newStepCode: repairAction.newStep?.code
510
- });
511
- console.log(`🔧 Steps array before repair:`, updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`));
529
+ this.log(`🔧 Applying repair action: ${repairAction.operation} on step ${repairAction.stepIndex}`);
530
+ this.log(`🔧 Steps array before repair: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
512
531
 
513
532
  const result = await this.applyRepairActionInContext(repairAction, updatedSteps, i, page, executionContext, contextVariables);
514
533
 
515
534
  if (result.success) {
516
535
  repairSuccess = true;
517
- console.log(`🔧 Steps array after repair:`, updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`));
536
+ this.log(`🔧 Steps array after repair: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
518
537
 
519
538
  // Mark the appropriate step(s) as successful based on operation type
520
539
  if (repairAction.operation === StepOperation.MODIFY) {
@@ -523,7 +542,7 @@ export class ExecutionService {
523
542
  step.error = undefined;
524
543
  updatedSteps[i].success = true;
525
544
  updatedSteps[i].error = undefined;
526
- console.log(`Step ${i + 1} marked as successful after MODIFY repair`);
545
+ this.log(`Step ${i + 1} marked as successful after MODIFY repair`);
527
546
  } else if (repairAction.operation === StepOperation.INSERT) {
528
547
  // For INSERT: mark the newly inserted step as successful
529
548
  const insertIndex = repairAction.insertAfterIndex !== undefined ? repairAction.insertAfterIndex + 1 : i + 1;
@@ -543,7 +562,7 @@ export class ExecutionService {
543
562
  repairAction.operation === StepOperation.REMOVE ?
544
563
  `REMOVE: step at index ${repairAction.stepIndex}` :
545
564
  repairAction.operation;
546
- console.log(`Step ${i + 1} repair action ${commandInfo} executed successfully on attempt ${attempt}`);
565
+ this.log(`Step ${i + 1} repair action ${commandInfo} executed successfully on attempt ${attempt}`);
547
566
 
548
567
  // Update execution context based on the repair action
549
568
  if (repairAction.operation === StepOperation.MODIFY && repairAction.newStep) {
@@ -575,21 +594,21 @@ export class ExecutionService {
575
594
  // Update step index based on operation
576
595
  if (repairAction.operation === StepOperation.INSERT) {
577
596
  // For INSERT: inserted step is already executed
578
- console.log(`INSERT operation: current i=${i}, insertAfterIndex=${repairAction.insertAfterIndex}`);
579
- console.log(`INSERT: Steps array length before: ${updatedSteps.length}`);
580
- console.log(`INSERT: Steps before operation:`, updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`));
597
+ this.log(`INSERT operation: current i=${i}, insertAfterIndex=${repairAction.insertAfterIndex}`);
598
+ this.log(`INSERT: Steps array length before: ${updatedSteps.length}`);
599
+ this.log(`INSERT: Steps before operation: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
581
600
 
582
601
  if (repairAction.insertAfterIndex !== undefined && repairAction.insertAfterIndex < i) {
583
602
  // If inserting before current position, current step moved down by 1
584
- console.log(`INSERT before current position: incrementing i from ${i} to ${i + 1}`);
603
+ this.log(`INSERT before current position: incrementing i from ${i} to ${i + 1}`);
585
604
  i++; // Move to the original step that was pushed to the next position
586
605
  } else {
587
606
  // If inserting at or after current position, stay at current step
588
- console.log(`INSERT at/after current position: keeping i at ${i}`);
607
+ this.log(`INSERT at/after current position: keeping i at ${i}`);
589
608
  }
590
609
 
591
- console.log(`INSERT: Steps array length after: ${updatedSteps.length}`);
592
- console.log(`INSERT: Steps after operation:`, updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`));
610
+ this.log(`INSERT: Steps array length after: ${updatedSteps.length}`);
611
+ this.log(`INSERT: Steps after operation: ${updatedSteps.map((s, idx) => `${idx}: "${s.description}" (success: ${s.success})`).join(', ')}`);
593
612
  } else if (repairAction.operation === StepOperation.REMOVE) {
594
613
  // For REMOVE: stay at same index since the next step moved to current position
595
614
  // Don't increment i because the array shifted left
@@ -614,9 +633,9 @@ export class ExecutionService {
614
633
  repairSuggestion.action.operation === StepOperation.REMOVE ?
615
634
  `REMOVE: step at index ${repairSuggestion.action.stepIndex}` :
616
635
  repairSuggestion.action.operation;
617
- console.log(`Step ${i + 1} repair attempt ${attempt} failed (${commandInfo}): ${repairErrorMessage}`);
636
+ this.log(`Step ${i + 1} repair attempt ${attempt} failed (${commandInfo}): ${repairErrorMessage}`);
618
637
  if (repairError instanceof Error && repairError.stack) {
619
- console.log(` Repair stack trace: ${repairError.stack}`);
638
+ this.log(` Repair stack trace: ${repairError.stack}`);
620
639
  }
621
640
 
622
641
  // Record this attempt in history
@@ -632,7 +651,7 @@ export class ExecutionService {
632
651
  }
633
652
 
634
653
  if (!repairSuccess) {
635
- console.log(`Step ${i + 1} failed after ${maxTries} repair attempts`);
654
+ this.log(`Step ${i + 1} failed after ${maxTries} repair attempts`);
636
655
  break;
637
656
  }
638
657
  }
@@ -871,8 +890,8 @@ export class ExecutionService {
871
890
  success: false,
872
891
  error: undefined
873
892
  };
874
- console.log(`INSERT: Inserting step at index ${insertIndex} with description "${newStep.description}"`);
875
- console.log(`INSERT: Steps before insertion:`, steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`));
893
+ this.log(`INSERT: Inserting step at index ${insertIndex} with description "${newStep.description}"`);
894
+ this.log(`INSERT: Steps before insertion: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
876
895
 
877
896
  // Preserve success status of existing steps before insertion
878
897
  const successStatusMap = new Map(steps.map((step, index) => [index, { success: step.success, error: step.error }]));
@@ -893,9 +912,9 @@ export class ExecutionService {
893
912
 
894
913
  // CRITICAL FIX: Ensure the inserted step doesn't overwrite existing step data
895
914
  // The new step should only have its own description, not inherit from existing steps
896
- console.log(`INSERT: Final step array after restoration:`, steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`));
915
+ this.log(`INSERT: Final step array after restoration: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
897
916
 
898
- console.log(`INSERT: Steps after insertion:`, steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`));
917
+ this.log(`INSERT: Steps after insertion: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
899
918
  // Test the new step with current page state
900
919
  await this.executeStepCode(action.newStep.code, page);
901
920
  return { success: true, updatedContext: executionContext + action.newStep.code };
package/src/index.ts CHANGED
@@ -91,6 +91,13 @@ export class TestChimpService {
91
91
  }
92
92
  }
93
93
 
94
+ /**
95
+ * Set logger callback for capturing execution logs
96
+ */
97
+ setLogger(logger: (message: string, level?: 'log' | 'error' | 'warn') => void): void {
98
+ this.executionService.setLogger(logger);
99
+ }
100
+
94
101
  /**
95
102
  * Get current authentication configuration
96
103
  */