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.
- package/dist/execution-service.d.ts +9 -0
- package/dist/execution-service.d.ts.map +1 -1
- package/dist/execution-service.js +71 -52
- package/dist/execution-service.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/execution-service.ts +71 -52
- package/src/index.ts +7 -0
- package/tsc +1029 -0
package/src/execution-service.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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
|
-
|
|
373
|
+
this.log('Attempting LLM-based script parsing...');
|
|
349
374
|
const result = await this.llmFacade.parseScriptIntoSteps(script, model);
|
|
350
|
-
|
|
375
|
+
this.log(`LLM parsing successful, got ${result.length} steps`);
|
|
351
376
|
return result;
|
|
352
377
|
} catch (error) {
|
|
353
|
-
|
|
378
|
+
this.log(`LLM parsing failed, falling back to code parsing: ${error}`);
|
|
354
379
|
const fallbackResult = this.parseScriptIntoStepsFallback(script);
|
|
355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
436
|
-
|
|
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
|
-
|
|
440
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
505
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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
|
-
|
|
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
|
-
|
|
607
|
+
this.log(`INSERT at/after current position: keeping i at ${i}`);
|
|
589
608
|
}
|
|
590
609
|
|
|
591
|
-
|
|
592
|
-
|
|
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
|
-
|
|
636
|
+
this.log(`Step ${i + 1} repair attempt ${attempt} failed (${commandInfo}): ${repairErrorMessage}`);
|
|
618
637
|
if (repairError instanceof Error && repairError.stack) {
|
|
619
|
-
|
|
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
|
-
|
|
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
|
-
|
|
875
|
-
|
|
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
|
-
|
|
915
|
+
this.log(`INSERT: Final step array after restoration: ${steps.map((s, i) => `${i}: "${s.description}" (success: ${s.success})`).join(', ')}`);
|
|
897
916
|
|
|
898
|
-
|
|
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
|
*/
|