lexxit-automation-framework 2.0.20 → 3.0.0

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.
Files changed (61) hide show
  1. package/README.md +246 -57
  2. package/npmignore +8 -0
  3. package/package.json +32 -1
  4. package/public/dashboard.html +591 -0
  5. package/src/actions/baseHandler.ts +351 -0
  6. package/src/actions/browserManager.ts +432 -0
  7. package/src/actions/checkboxHandler.ts +276 -0
  8. package/src/actions/clickHandler.ts +513 -0
  9. package/src/actions/customcodehandler.ts +251 -0
  10. package/src/actions/dropdownHandler.ts +501 -0
  11. package/src/actions/radiobuttonHandler.ts +286 -0
  12. package/src/actions/textHandler.ts +498 -0
  13. package/src/api/server.ts +210 -0
  14. package/src/config.ts +7 -0
  15. package/src/executor/functionMap.ts +153 -0
  16. package/src/executor/mapping.ts +70 -0
  17. package/src/executor/scriptExecutor.ts +162 -0
  18. package/src/executor/stepExecutor.ts +289 -0
  19. package/src/runner/testRunner.ts +78 -0
  20. package/src/sse/sseManager.ts +130 -0
  21. package/src/store/executionStore.ts +152 -0
  22. package/src/store/testDataStore.ts +46 -0
  23. package/src/types/types.ts +159 -0
  24. package/src/utils/healingService.ts +210 -0
  25. package/src/utils/locatorService.ts +73 -0
  26. package/src/utils/logger.ts +27 -0
  27. package/src/utils/metadataService.ts +137 -0
  28. package/src/utils/waitConditions.ts +141 -0
  29. package/src/validator/validator.ts +140 -0
  30. package/tsconfig.json +16 -0
  31. package/bin/lexxit-automation-framework.js +0 -224
  32. package/dist-obf/app.js +0 -1
  33. package/dist-obf/controllers/controller.js +0 -1
  34. package/dist-obf/core/BrowserManager.js +0 -1
  35. package/dist-obf/core/PlaywrightEngine.js +0 -1
  36. package/dist-obf/core/ScreenshotManager.js +0 -1
  37. package/dist-obf/core/TestData.js +0 -1
  38. package/dist-obf/core/TestExecutor.js +0 -1
  39. package/dist-obf/core/handlers/AllHandlers.js +0 -1
  40. package/dist-obf/core/handlers/BaseHandler.js +0 -1
  41. package/dist-obf/core/handlers/ClickHandler.js +0 -1
  42. package/dist-obf/core/handlers/CustomCodeHandler.js +0 -1
  43. package/dist-obf/core/handlers/DropdownHandler.js +0 -1
  44. package/dist-obf/core/handlers/InputHandler.js +0 -1
  45. package/dist-obf/core/registry/ActionRegistry.js +0 -1
  46. package/dist-obf/installer/frameworkLauncher.js +0 -1
  47. package/dist-obf/public/dashboard.html +0 -591
  48. package/dist-obf/public/execution.html +0 -510
  49. package/dist-obf/public/queue-monitor.html +0 -408
  50. package/dist-obf/queue/ExecutionQueue.js +0 -1
  51. package/dist-obf/routes/api.routes.js +0 -1
  52. package/dist-obf/server.js +0 -1
  53. package/dist-obf/types/types.js +0 -1
  54. package/dist-obf/utils/chromefinder.js +0 -1
  55. package/dist-obf/utils/elementHighlight.js +0 -1
  56. package/dist-obf/utils/locatorHelper.js +0 -1
  57. package/dist-obf/utils/logger.js +0 -1
  58. package/dist-obf/utils/response.js +0 -1
  59. package/dist-obf/utils/responseFormatter.js +0 -1
  60. package/dist-obf/utils/sseManager.js +0 -1
  61. package/scripts/postinstall.js +0 -52
@@ -0,0 +1,159 @@
1
+ // ─── Execution State (for live dashboard) ─────────────────────────────────────
2
+
3
+ export type ExecutionOverallStatus = 'RUNNING' | 'PASS' | 'FAIL';
4
+ export type StepLiveStatus = 'PENDING' | 'RUNNING' | 'PASS' | 'FAIL' | 'SKIP';
5
+
6
+ export type ScreenshotMode = 'on_failure' | 'always' | 'never';
7
+
8
+ export interface LiveStepState {
9
+ step_name: string;
10
+ status: StepLiveStatus;
11
+ expected_result: string | null;
12
+ comments: string | null;
13
+ duration: number | null;
14
+ }
15
+
16
+ export interface LiveScriptState {
17
+ test_script_uid: string;
18
+ test_case_name: string;
19
+ status: ExecutionOverallStatus;
20
+ video_path: string | null;
21
+ steps: LiveStepState[];
22
+ }
23
+
24
+ export interface ExecutionState {
25
+ execution_id: string;
26
+ test_set_name: string;
27
+ status: ExecutionOverallStatus;
28
+ start_time: string;
29
+ end_time: string | null;
30
+ scripts: LiveScriptState[];
31
+ final_result: TestSetResult | null;
32
+ }
33
+
34
+ // ─── Step Result ──────────────────────────────────────────────────────────────
35
+
36
+ export interface StepResult {
37
+ uid: string | null;
38
+ obj_uid: string | null;
39
+ page_uid: string | null;
40
+ step_name: string;
41
+ step_script: string;
42
+ status: 'PASS' | 'FAIL' | 'SKIP';
43
+ expected_result: string;
44
+ comments: string;
45
+ duration: number; // milliseconds
46
+ start_time: string; // ISO string e.g. 2026-06-23T11:25:32.487Z
47
+ screenshot?: string;
48
+ pageSource?: string;
49
+ autoHealed?: boolean;
50
+ failureType?: 'ELEMENT_NOT_FOUND' | 'TIMEOUT' | 'NAVIGATION_TIMEOUT' | 'OTHER';
51
+ comparisonCode?: any;
52
+ }
53
+
54
+ // ─── Failure Metadata for API ─────────────────────────────────────────────────
55
+
56
+ export interface FailureMetadata {
57
+ test_script_uid: string;
58
+ test_case_name: string;
59
+ step_name: string;
60
+ step_index: number;
61
+ failure_reason: string;
62
+ screenshot?: string;
63
+ pageSource?: string;
64
+ browser_type: string;
65
+ timestamp: string;
66
+ app_id?: string;
67
+ }
68
+
69
+ // ─── Step Request ─────────────────────────────────────────────────────────────
70
+
71
+ export interface StepRequest {
72
+ step_name: string;
73
+ step_script: string;
74
+ label: string;
75
+ locators?: string[];
76
+ obj_uid?: string | null;
77
+ page_uid?: string | null;
78
+ status?: string;
79
+ value?: string;
80
+ }
81
+
82
+ // ─── Test Script Request ──────────────────────────────────────────────────────
83
+
84
+ export interface TestScriptRequest {
85
+ test_script_uid: string;
86
+ test_case_name: string;
87
+ voice_enabled: boolean;
88
+ app_id: string;
89
+ browser: 'chromium' | 'chrome' | 'firefox' | 'edge';
90
+ headless: boolean;
91
+ screenshot_mode: 'on_failure' | 'always' | 'never';
92
+ stop_on_failure: boolean;
93
+ steps: StepRequest[];
94
+ }
95
+
96
+ // ─── Test Set Request ─────────────────────────────────────────────────────────
97
+
98
+ export interface TestSetRequest {
99
+ test_set_name: string;
100
+ open_dashboard: boolean;
101
+ parallel: boolean;
102
+ stop_on_failure: boolean;
103
+ video_enabled: boolean;
104
+ exec_mode?: ExecMode;
105
+ scripts: TestScriptRequest[];
106
+ }
107
+
108
+ // ─── Test Script Result ───────────────────────────────────────────────────────
109
+
110
+ export interface TestScriptResult {
111
+ test_script_uid: string;
112
+ test_case_name: string;
113
+ app_id: string;
114
+ browser: string;
115
+ status: 'PASS' | 'FAIL';
116
+ results: StepResult[];
117
+ // flat counts — no summary object
118
+ total_steps: number;
119
+ passed_steps: number;
120
+ failed_steps: number;
121
+ skipped_steps: number;
122
+ duration: number; // milliseconds
123
+ start_time: string; // ISO string
124
+ }
125
+
126
+ // ─── Test Set Result ──────────────────────────────────────────────────────────
127
+
128
+ export interface TestSetResult {
129
+ executionId: string;
130
+ status: 'PASS' | 'FAIL';
131
+ total_scripts: number;
132
+ passed: number;
133
+ failed: number;
134
+ duration: number; // milliseconds
135
+ results: TestScriptResult[];
136
+ }
137
+
138
+ export interface ExecMode {
139
+ mode: 'fast' | 'medium' | 'slow';
140
+ delay_ms?: number;
141
+ }
142
+
143
+ // ─── SSE ─────────────────────────────────────────────────────────────────────
144
+
145
+ export type SSEEventType =
146
+ | 'execution_start'
147
+ | 'script_start'
148
+ | 'step_start'
149
+ | 'step_complete'
150
+ | 'script_complete'
151
+ | 'execution_complete'
152
+ | 'log';
153
+
154
+ export interface SSEEvent {
155
+ type: SSEEventType;
156
+ executionId: string;
157
+ timestamp: string;
158
+ data: any;
159
+ }
@@ -0,0 +1,210 @@
1
+ import { StepResult } from '../types/types';
2
+ import { TRIAGE, COMPARE } from '../config';
3
+ /**
4
+ * Shape of a single healed entry from api/compare response
5
+ */
6
+ export interface HealedEntry {
7
+ name: string;
8
+ obj_uid: string;
9
+ step_script: string;
10
+ }
11
+
12
+ /**
13
+ * Service for handling advanced auto-healing via triage + compare APIs
14
+ */
15
+ export class HealingService {
16
+
17
+ private triageApiUrl = TRIAGE;
18
+ private compareApiUrl = COMPARE;
19
+
20
+ /**
21
+ * Classify failure type from error message
22
+ */
23
+ static classifyFailure(comments: string, result?: any): string {
24
+ const msg = comments.toLowerCase();
25
+
26
+ if (msg.includes('element not found') || msg.includes('not visible')) {
27
+ return 'ELEMENT_NOT_FOUND';
28
+ }
29
+ if (msg.includes('timeout') && msg.includes('navigation')) {
30
+ return 'NAVIGATION_TIMEOUT';
31
+ }
32
+ if (msg.includes('timeout')) {
33
+ return 'TIMEOUT';
34
+ }
35
+
36
+ return 'OTHER';
37
+ }
38
+
39
+ /**
40
+ * Call triage API to determine if auto-healing should be attempted
41
+ */
42
+ async callTriageAPI(triagePayload: any): Promise<{ callCompareScript: boolean; rootCause?: string; confidence?: number; reason?: string }> {
43
+ try {
44
+ console.log(`🩺 Calling triage API at ${this.triageApiUrl}...`);
45
+ const response = await fetch(this.triageApiUrl, {
46
+ method: 'POST',
47
+ headers: { 'Content-Type': 'application/json' },
48
+ body: JSON.stringify(triagePayload)
49
+ });
50
+
51
+ if (!response.ok) {
52
+ console.warn(`⚠️ Triage API returned ${response.status} — skipping compare`);
53
+ return { callCompareScript: false };
54
+ }
55
+
56
+ const result = await response.json();
57
+ console.log(`🩺 Triage result:`, JSON.stringify(result));
58
+ console.log(` Root cause: ${result?.root_cause} | Confidence: ${result?.confidence} | Call compare: ${result?.call_compare_script}`);
59
+
60
+ return {
61
+ callCompareScript: result?.call_compare_script === true,
62
+ rootCause: result?.root_cause,
63
+ confidence: result?.confidence,
64
+ reason: result?.reason
65
+ };
66
+ } catch (error: any) {
67
+ console.error(`❌ Triage API call failed: ${error.message} — skipping compare`);
68
+ return { callCompareScript: false };
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Call compare API to get healed selectors/scripts
74
+ */
75
+ async callCompareAPI(comparePayload: any): Promise<any> {
76
+ try {
77
+ console.log(`📡 Calling compare API at ${this.compareApiUrl}...`);
78
+ const response = await fetch(this.compareApiUrl, {
79
+ method: 'POST',
80
+ headers: { 'Content-Type': 'application/json' },
81
+ body: JSON.stringify(comparePayload)
82
+ });
83
+
84
+ if (!response.ok) {
85
+ console.warn(`⚠️ Compare API returned ${response.status}`);
86
+ return null;
87
+ }
88
+
89
+ const result = await response.json();
90
+ console.log(`📦 Compare result:`, JSON.stringify(result));
91
+
92
+ const healedChanged: HealedEntry[] = result?.diff_report?.self_healing?.healed_changed ?? [];
93
+ const healedRemoved: HealedEntry[] = result?.diff_report?.self_healing?.healed_removed ?? [];
94
+ const allHealed: HealedEntry[] = [...healedChanged, ...healedRemoved];
95
+
96
+ console.log(`🔬 healed_changed: ${healedChanged.length} | healed_removed: ${healedRemoved.length} | total: ${allHealed.length}`);
97
+
98
+ if (allHealed.length > 0) {
99
+ console.log(`🗃️ Found ${allHealed.length} healed entr${allHealed.length === 1 ? 'y' : 'ies'}: [${allHealed.map(e => e.name).join(', ')}]`);
100
+ }
101
+
102
+ return result;
103
+ } catch (error: any) {
104
+ console.error(`❌ Compare API call failed: ${error.message}`);
105
+ return null;
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Build triage payload from execution context
111
+ */
112
+ static buildTriagePayload(
113
+ stepResults: StepResult[],
114
+ currentStep: any,
115
+ stepIndex: number,
116
+ scriptUid: string,
117
+ appId?: string
118
+ ): any {
119
+ const comments = currentStep.comments || 'Unknown error';
120
+ const failureType = HealingService.classifyFailure(comments);
121
+
122
+ return {
123
+ status: 'fail',
124
+ app_id: appId,
125
+ total_scripts: 1,
126
+ results: [
127
+ {
128
+ test_script_uid: scriptUid,
129
+ app_id: appId,
130
+ status: 'fail',
131
+ results: [
132
+ // All completed steps
133
+ ...stepResults.map((r, idx) => ({
134
+ uid: r.uid ?? null,
135
+ obj_uid: r.obj_uid ?? null,
136
+ step_name: r.step_name,
137
+ step_script: r.expected_result || '',
138
+ page_uid: r.page_uid ?? null,
139
+ comments: r.comments,
140
+ screenshot: r.screenshot ?? '',
141
+ duration: r.duration,
142
+ start_time: r.start_time.replace('T', ' ').substring(0, 19),
143
+ expected_result: r.expected_result ?? '',
144
+ status: r.status.toLowerCase()
145
+ })),
146
+ // Current failing step
147
+ {
148
+ uid: currentStep.uid ?? null,
149
+ obj_uid: currentStep.obj_uid ?? null,
150
+ step_name: currentStep.step_name,
151
+ step_script: currentStep.step_script,
152
+ page_uid: currentStep.page_uid ?? null,
153
+ comments: comments,
154
+ screenshot: currentStep.screenshot ?? '',
155
+ duration: currentStep.duration || '0 seconds',
156
+ start_time: new Date().toISOString().replace('T', ' ').substring(0, 19),
157
+ expected_result: currentStep.expected_result ?? '',
158
+ status: 'fail',
159
+ data: {
160
+ failure_type: failureType,
161
+ error: comments
162
+ }
163
+ }
164
+ ]
165
+ }
166
+ ]
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Build compare payload from page context
172
+ */
173
+ static buildComparePayload(
174
+ appId: string,
175
+ pageUid: string,
176
+ objUid: string,
177
+ pageSource: string,
178
+ currentUrl: string,
179
+ stepIndex: number
180
+ ): any {
181
+ return {
182
+ app_id: appId,
183
+ page_uid: pageUid,
184
+ obj_uid: objUid,
185
+ pgContent: pageSource,
186
+ sub_url: currentUrl,
187
+ mode: 'html',
188
+ failed_step_index: stepIndex
189
+ };
190
+ }
191
+
192
+ /**
193
+ * Replace $variable placeholders with actual values
194
+ */
195
+ static replaceVariables(healedScript: string, stepValue?: string): string {
196
+ if (!healedScript.includes("'$")) {
197
+ return healedScript;
198
+ }
199
+
200
+ if (stepValue !== undefined && stepValue !== null && stepValue !== '') {
201
+ const replaced = healedScript.replace(/'(\$[^']+)'/, `'${stepValue}'`);
202
+ console.log(`🔧 $variable replaced with value: "${stepValue}"`);
203
+ console.log(`📝 Final healed script: ${replaced}`);
204
+ return replaced;
205
+ }
206
+
207
+ console.log(`⚠️ Step value is empty — $variable left unreplaced`);
208
+ return healedScript;
209
+ }
210
+ }
@@ -0,0 +1,73 @@
1
+ import { Page, Locator } from 'playwright';
2
+
3
+ export interface ResolvedElement {
4
+ element: Locator;
5
+ matchedIndex: number;
6
+ matchedXPath: string;
7
+ }
8
+
9
+ export class LocatorService {
10
+ /**
11
+ * Resolves an element using a list of xpath locators.
12
+ * Combines all xpaths into a single Playwright locator using OR-combined xpath expressions,
13
+ * waits for the first visible + attached match, then determines which specific
14
+ * locator (by position) actually matched.
15
+ */
16
+ static async resolveElement(page: Page, locators: string[], timeout: number = 10000): Promise<ResolvedElement> {
17
+ if (!locators || locators.length === 0) {
18
+ throw new Error('No locators provided');
19
+ }
20
+
21
+ const xpathLocators = locators.filter(l => l.trim().startsWith('/') || l.trim().startsWith('('));
22
+ const combinedXPath = xpathLocators.join(' | ');
23
+ const element = page.locator(`xpath=${combinedXPath}`).first();
24
+
25
+ try {
26
+ await element.waitFor({ state: 'visible', timeout });
27
+ } catch (error: any) {
28
+ throw new Error(`Element not found or not visible using locators: [${locators.join(', ')}] - ${error.message}`);
29
+ }
30
+
31
+ const matchedIndex = await this.findMatchedIndex(page, element, xpathLocators);
32
+
33
+ return {
34
+ element,
35
+ matchedIndex,
36
+ matchedXPath: matchedIndex >= 0 ? xpathLocators[matchedIndex] : combinedXPath
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Determines which specific locator (by position) matches the resolved element,
42
+ * by checking each individual xpath separately and comparing element identity.
43
+ */
44
+ private static async findMatchedIndex(page: Page, resolvedElement: Locator, locators: string[]): Promise<number> {
45
+ try {
46
+ const resolvedHandle = await resolvedElement.elementHandle();
47
+ if (!resolvedHandle) return -1;
48
+
49
+ for (let i = 0; i < locators.length; i++) {
50
+ const candidate = page.locator(`xpath=${locators[i]}`).first();
51
+ const count = await candidate.count().catch(() => 0);
52
+
53
+ if (count === 0) continue;
54
+
55
+ const candidateHandle = await candidate.elementHandle().catch(() => null);
56
+ if (!candidateHandle) continue;
57
+
58
+ const isSameElement = await page.evaluate(
59
+ ({ a, b }) => a === b,
60
+ { a: resolvedHandle, b: candidateHandle }
61
+ ).catch(() => false);
62
+
63
+ if (isSameElement) {
64
+ return i;
65
+ }
66
+ }
67
+
68
+ return -1;
69
+ } catch {
70
+ return -1;
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,27 @@
1
+ export class Logger {
2
+ private context: string;
3
+
4
+ private constructor(context: string) {
5
+ this.context = context;
6
+ }
7
+
8
+ static create(context: string): Logger {
9
+ return new Logger(context);
10
+ }
11
+
12
+ info(msg: string): void {
13
+ console.log(`[${this.timestamp()}] [INFO] [${this.context}] ${msg}`);
14
+ }
15
+
16
+ error(msg: string): void {
17
+ console.error(`[${this.timestamp()}] [ERROR] [${this.context}] ${msg}`);
18
+ }
19
+
20
+ warn(msg: string): void {
21
+ console.warn(`[${this.timestamp()}] [WARN] [${this.context}] ${msg}`);
22
+ }
23
+
24
+ private timestamp(): string {
25
+ return new Date().toISOString().replace('T', ' ').split('.')[0];
26
+ }
27
+ }
@@ -0,0 +1,137 @@
1
+ import { FailureMetadata, StepResult } from '../types/types';
2
+
3
+ const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:5501';
4
+
5
+ /**
6
+ * Service for handling failure metadata collection and transmission to API
7
+ */
8
+
9
+ export class MetadataService {
10
+ /**
11
+ * Capture page source from Playwright page
12
+ */
13
+ static async capturePageSource(page: any): Promise<string | undefined> {
14
+ try {
15
+ const content = await page.content();
16
+ return content;
17
+ } catch (error) {
18
+ console.warn('Failed to capture page source:', error);
19
+ return undefined;
20
+ }
21
+ }
22
+
23
+
24
+ static buildFailureMetadata(
25
+ testScriptUid: string,
26
+ testCaseName: string,
27
+ stepIndex: number,
28
+ step: StepResult,
29
+ browserType: string,
30
+ appId?: string
31
+ ): FailureMetadata {
32
+ return {
33
+ test_script_uid: testScriptUid,
34
+ test_case_name: testCaseName,
35
+ step_name: step.step_name,
36
+ step_index: stepIndex,
37
+ failure_reason: step.comments,
38
+ screenshot: step.screenshot,
39
+ pageSource: step.pageSource,
40
+ browser_type: browserType,
41
+ timestamp: new Date().toISOString(),
42
+ app_id: appId
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Send failure metadata to API for analysis and auto-healing
48
+ */
49
+ static async sendFailureMetadata(metadata: FailureMetadata): Promise<{ autoHealSuggestion?: string; success: boolean }> {
50
+ try {
51
+ const response = await fetch(`${API_BASE_URL}/api/failure-metadata`, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json' },
54
+ body: JSON.stringify(metadata)
55
+ });
56
+
57
+ if (!response.ok) {
58
+ console.warn(`Failed to send metadata: ${response.statusText}`);
59
+ return { success: false };
60
+ }
61
+
62
+ const data = await response.json();
63
+ return { autoHealSuggestion: data.autoHealSuggestion, success: true };
64
+ } catch (error: any) {
65
+ console.warn('Error sending failure metadata:', error.message);
66
+ return { success: false };
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Apply auto-healing suggestion if available
72
+ * This could involve retrying with adjusted selectors, different wait conditions, etc.
73
+ */
74
+ static async applyAutoHealing(
75
+ suggestion: string,
76
+ originalStep: any,
77
+ page: any
78
+ ): Promise<boolean> {
79
+ try {
80
+ // Parse and apply healing suggestion
81
+ // Example: suggestion could be "{\"action\": \"retry\", \"adjustedSelector\": \"...\"}"
82
+ const healingData = JSON.parse(suggestion);
83
+
84
+ if (healingData.action === 'retry') {
85
+ console.log('Auto-healing: Retrying step with adjusted selector');
86
+ // Implementation would depend on step type
87
+ return true;
88
+ }
89
+
90
+ if (healingData.action === 'navigate') {
91
+ console.log('Auto-healing: Navigating to URL:', healingData.url);
92
+ await page.goto(healingData.url, { waitUntil: 'networkidle' });
93
+ return true;
94
+ }
95
+
96
+ return false;
97
+ } catch (error: any) {
98
+ console.warn('Failed to apply auto-healing:', error.message);
99
+ return false;
100
+ }
101
+ }
102
+
103
+
104
+ static async processScriptFailure(
105
+ results: StepResult[],
106
+ testScriptUid: string,
107
+ testCaseName: string,
108
+ browserType: string,
109
+ appId?: string
110
+ ): Promise<void> {
111
+ const failedSteps = results.filter((r) => r.status === 'FAIL');
112
+
113
+ for (let i = 0; i < failedSteps.length; i++) {
114
+ const step = failedSteps[i];
115
+ const stepIndex = results.indexOf(step);
116
+
117
+ const metadata = this.buildFailureMetadata(
118
+ testScriptUid,
119
+ testCaseName,
120
+ stepIndex,
121
+ step,
122
+ browserType,
123
+ appId
124
+ );
125
+
126
+ const response = await this.sendFailureMetadata(metadata);
127
+
128
+ if (response.success) {
129
+ console.log(`Failure metadata sent for step: ${step.step_name}`);
130
+ if (response.autoHealSuggestion) {
131
+ console.log('Auto-healing suggestion received:', response.autoHealSuggestion);
132
+ // Auto-healing would be applied during re-execution
133
+ }
134
+ }
135
+ }
136
+ }
137
+ }