testchimp-runner-core 0.0.16 → 0.0.18

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
  */
@@ -172,7 +179,7 @@ export class TestChimpService {
172
179
  return this.creditUsageService.reportAIRepairCredit(jobId);
173
180
  }
174
181
 
175
- // Find TestChimp managed tests
182
+ // Find TestChimp smart/managed tests (prioritizes Smart Test detection)
176
183
  findTestChimpTests(directory: string, recursive: boolean = true): string[] {
177
184
  const fs = require('fs');
178
185
  const path = require('path');
@@ -189,7 +196,7 @@ export class TestChimpService {
189
196
 
190
197
  if (stat.isDirectory() && recursive) {
191
198
  scanDir(fullPath);
192
- } else if (stat.isFile() && isTestChimpManaged(fullPath)) {
199
+ } else if (stat.isFile() && isTestChimpTest(fullPath)) {
193
200
  testFiles.push(fullPath);
194
201
  }
195
202
  }
@@ -198,7 +205,7 @@ export class TestChimpService {
198
205
  }
199
206
  }
200
207
 
201
- function isTestChimpManaged(filePath: string): boolean {
208
+ function isTestChimpTest(filePath: string): boolean {
202
209
  // Check if file is a test file
203
210
  if (!filePath.match(/\.(spec|test)\.(js|ts)$/)) {
204
211
  return false;
@@ -208,15 +215,10 @@ export class TestChimpService {
208
215
  // Read file content
209
216
  const content = fs.readFileSync(filePath, 'utf8');
210
217
 
211
- // Check for TestChimp markers
218
+ // Check for TestChimp markers - only exact comment phrases
212
219
  const testChimpMarkers = [
213
- /\/\/ TestChimp:.*step/i,
214
- /\/\* TestChimp:.*step \*\//i,
215
- /\/\/ Step \d+:/i,
216
- /\/\* Step \d+: \*\//i,
217
- /testchimp.*step/i,
218
- /\/\/ AI.*repair/i,
219
- /\/\* AI.*repair \*\//i
220
+ /This is a TestChimp Smart Test/i,
221
+ /This is a TestChimp Managed Test/i
220
222
  ];
221
223
 
222
224
  return testChimpMarkers.some(marker => marker.test(content));
package/src/llm-facade.ts CHANGED
@@ -166,6 +166,29 @@ export class LLMFacade {
166
166
  }
167
167
  }
168
168
 
169
+ /**
170
+ * Generate hashtags for semantic grouping
171
+ */
172
+ async generateHashtags(scenario: string, model: string = 'gpt-4o-mini'): Promise<string[]> {
173
+ console.log('Generating hashtags with LLM...');
174
+
175
+ const request: CallLLMRequest = {
176
+ model,
177
+ system_prompt: PROMPTS.HASHTAG_GENERATION.SYSTEM,
178
+ user_prompt: PROMPTS.HASHTAG_GENERATION.USER(scenario)
179
+ };
180
+
181
+ try {
182
+ const response = await this.callLLM(request);
183
+ const hashtagResponse = JSON.parse(response) as { hashtags: string[] };
184
+ return hashtagResponse.hashtags || [];
185
+ } catch (error) {
186
+ console.error('Failed to generate hashtags:', error);
187
+ // Fallback to empty array
188
+ return [];
189
+ }
190
+ }
191
+
169
192
  /**
170
193
  * Break down scenario into steps
171
194
  */
package/src/prompts.ts CHANGED
@@ -10,6 +10,13 @@ export const PROMPTS = {
10
10
  USER: (scenario: string) => `Analyze this scenario description and generate a meaningful test name:\n\n"${scenario}"\n\nInstructions:\n1. Look for ANY hints or indicators in the text that suggest what this test should be called:\n - Explicit mentions: "Test: ...", "Scenario: ...", "Check: ...", "Verify: ..."\n - Descriptive phrases: "...flow", "...process", "...journey", "...workflow"\n - Action-focused terms: "login", "registration", "purchase", "messaging", "search"\n - Business context: "user onboarding", "checkout process", "team collaboration"\n2. If you find such indicators, use them as the basis for the test name\n3. If not found, analyze the user journey and business purpose\n4. Generate a concise name under 30 characters in camelCase\n\nExamples:\n- "Test: User login and messaging flow" -> "userLoginAndMessagingFlow"\n- "Checkout process with payment" -> "checkoutProcess"\n- "User registration and email verification" -> "userRegistration"\n- "Team messaging and collaboration" -> "teamMessaging"`
11
11
  },
12
12
 
13
+ // Hashtag generation for semantic grouping
14
+ HASHTAG_GENERATION: {
15
+ SYSTEM: 'You are an AI assistant that generates relevant hashtags for test scenarios to enable semantic grouping across test files. Analyze the scenario description and generate 2-5 hashtags that capture the key aspects of the test such as functionality, user journey, or domain.',
16
+
17
+ USER: (scenario: string) => `Analyze this scenario description and generate relevant hashtags for semantic grouping:\n\n"${scenario}"\n\nInstructions:\n1. Identify the main functionality being tested (e.g., #login, #checkout, #messaging)\n2. Identify the user journey type (e.g., #registration, #onboarding, #payment)\n3. Identify the domain/area (e.g., #ecommerce, #social, #admin)\n4. Identify any specific features (e.g., #search, #upload, #notification)\n5. Generate 2-5 hashtags that are:\n - Concise and descriptive\n - Lowercase with no spaces or special characters\n - Relevant for grouping similar tests\n\nExamples:\n- "User login and messaging flow" -> ["#login", "#messaging", "#social"]\n- "Checkout process with payment" -> ["#checkout", "#payment", "#ecommerce"]\n- "User registration and email verification" -> ["#registration", "#verification", "#onboarding"]\n\nRespond with JSON: {"hashtags": ["#tag1", "#tag2", "#tag3"]}`
18
+ },
19
+
13
20
  // Scenario breakdown
14
21
  SCENARIO_BREAKDOWN: {
15
22
  SYSTEM: `You are an expert test automation engineer that breaks down user scenarios into precise, actionable Playwright steps.
@@ -185,6 +185,9 @@ export class ScenarioWorker {
185
185
 
186
186
  // Generate test name if not provided
187
187
  const testName = job.testName || await this.llmFacade.generateTestName(job.scenario, job.model);
188
+
189
+ // Generate hashtags for semantic grouping
190
+ const hashtags = await this.llmFacade.generateHashtags(job.scenario, job.model);
188
191
 
189
192
  // Generate preferred filename
190
193
  let preferredFileName: string;
@@ -211,7 +214,7 @@ export class ScenarioWorker {
211
214
  }
212
215
 
213
216
  // Generate clean script with TestChimp comment and code
214
- generatedScript = generateTestScript(testName, steps);
217
+ generatedScript = generateTestScript(testName, steps, undefined, hashtags);
215
218
 
216
219
  // Generate detailed execution log
217
220
  const logLines: string[] = [];
@@ -6,53 +6,69 @@
6
6
  */
7
7
 
8
8
  /**
9
- * TestChimp managed test comment that should be added to all generated scripts
9
+ * TestChimp smart test comment that should be added to all generated scripts
10
10
  */
11
- export const TESTCHIMP_MANAGED_COMMENT = `/*
11
+ export const TESTCHIMP_SMART_COMMENT = `/*
12
12
 
13
- This is a TestChimp Managed Test.
13
+ This is a TestChimp Smart Test.
14
+ Version: 1.0
14
15
 
15
16
  */`;
16
17
 
17
18
  /**
18
- * Generates TestChimp managed test comment with optional repair advice
19
+ * Generates TestChimp smart test comment with optional repair advice and hashtags
19
20
  * @param repairAdvice Optional repair advice to include in the comment
21
+ * @param hashtags Optional hashtags for semantic grouping
20
22
  * @returns The complete comment block
21
23
  */
22
- export function generateTestChimpComment(repairAdvice?: string): string {
24
+ export function generateTestChimpComment(repairAdvice?: string, hashtags?: string[]): string {
25
+ const hashtagString = hashtags ? hashtags.map(tag => tag.startsWith('#') ? tag : `#${tag}`).join(' ') : '';
26
+
23
27
  if (repairAdvice) {
24
28
  return `/*
25
29
 
26
- This is a TestChimp Managed Test.
30
+ This is a TestChimp Smart Test.
31
+ Version: 1.0
32
+
33
+ ${hashtagString}
27
34
 
28
35
  Repair Advice:
29
36
  ${repairAdvice}
30
37
 
31
38
  */`;
32
39
  }
33
- return TESTCHIMP_MANAGED_COMMENT;
40
+ return `/*
41
+
42
+ This is a TestChimp Smart Test.
43
+ Version: 1.0
44
+
45
+ ${hashtagString}
46
+
47
+ */`;
34
48
  }
35
49
 
36
50
  /**
37
- * Adds the TestChimp managed test comment to the beginning of a script
51
+ * Adds the TestChimp smart test comment to the beginning of a script
38
52
  * @param script The original script content
39
53
  * @param repairAdvice Optional repair advice to include in the comment
54
+ * @param hashtags Optional hashtags for semantic grouping
40
55
  * @returns The script with TestChimp comment prepended
41
56
  */
42
- export function addTestChimpComment(script: string, repairAdvice?: string): string {
57
+ export function addTestChimpComment(script: string, repairAdvice?: string, hashtags?: string[]): string {
43
58
  // If the script already has the TestChimp comment, update it with repair advice if provided
44
- if (script.includes('This is a TestChimp Managed Test')) {
59
+ // Prioritize Smart Test detection, fallback to Managed Test for backward compatibility
60
+ if (script.includes('This is a TestChimp Smart Test') || script.includes('This is a TestChimp Managed Test')) {
45
61
  if (repairAdvice) {
46
62
  // Replace existing comment with new one that includes repair advice
47
- const commentRegex = /\/\*[\s\S]*?This is a TestChimp Managed Test\.[\s\S]*?\*\//;
48
- const newComment = generateTestChimpComment(repairAdvice);
63
+ const commentRegex = /\/\*[\s\S]*?This is a TestChimp (Managed|Smart) Test\.[\s\S]*?\*\//;
64
+ const newComment = generateTestChimpComment(repairAdvice, hashtags);
49
65
  return script.replace(commentRegex, newComment);
50
66
  }
51
67
  return script;
52
68
  }
53
69
 
54
70
  // Add the comment at the beginning of the script
55
- const comment = generateTestChimpComment(repairAdvice);
71
+ const comment = generateTestChimpComment(repairAdvice, hashtags);
56
72
  return `${comment}\n\n${script}`;
57
73
  }
58
74
 
@@ -60,24 +76,22 @@ export function addTestChimpComment(script: string, repairAdvice?: string): stri
60
76
  * Generates a complete test script with TestChimp comment, imports, and test structure
61
77
  * @param testName The name of the test
62
78
  * @param steps Array of test steps with descriptions and commands
63
- * @param includeTestChimpComment Whether to include the TestChimp managed test comment
64
79
  * @param repairAdvice Optional repair advice to include in the comment
80
+ * @param hashtags Optional hashtags for semantic grouping
65
81
  * @returns The complete test script
66
82
  */
67
83
  export function generateTestScript(
68
84
  testName: string,
69
85
  steps: Array<{ stepNumber: number; description: string; playwrightCommand?: string; success?: boolean }>,
70
- includeTestChimpComment: boolean = true,
71
- repairAdvice?: string
86
+ repairAdvice?: string,
87
+ hashtags?: string[]
72
88
  ): string {
73
89
  const scriptLines: string[] = [];
74
90
 
75
- // Add TestChimp comment if requested
76
- if (includeTestChimpComment) {
77
- const comment = generateTestChimpComment(repairAdvice);
78
- scriptLines.push(comment);
79
- scriptLines.push('');
80
- }
91
+ // Always add TestChimp comment
92
+ const comment = generateTestChimpComment(repairAdvice, hashtags);
93
+ scriptLines.push(comment);
94
+ scriptLines.push('');
81
95
 
82
96
  // Add imports
83
97
  scriptLines.push(`import { test, expect } from '@playwright/test';`);
@@ -100,10 +114,12 @@ export function generateTestScript(
100
114
  }
101
115
 
102
116
  /**
103
- * Checks if a script is a TestChimp managed test
117
+ * Checks if a script is a TestChimp managed/smart test
118
+ * Prioritizes detection of "Smart Test" format while maintaining backward compatibility with "Managed Test"
104
119
  * @param script The script content to check
105
- * @returns True if the script contains the TestChimp managed test comment
120
+ * @returns True if the script contains the TestChimp smart/managed test comment
106
121
  */
107
122
  export function isTestChimpManagedTest(script: string): boolean {
108
- return script.includes('This is a TestChimp Managed Test');
123
+ // Prioritize Smart Test detection, fallback to Managed Test for backward compatibility
124
+ return script.includes('This is a TestChimp Smart Test') || script.includes('This is a TestChimp Managed Test');
109
125
  }