lexxit-automation-framework 2.0.19 → 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,498 @@
1
+ import { Page } from 'playwright';
2
+ import { ScreenshotMode, StepResult } from '../types/types';
3
+ import { LocatorService } from '../utils/locatorService';
4
+ import { BaseHandler } from './baseHandler';
5
+ import * as fs from 'fs';
6
+ import { DEFAULT_SCREENSHOT_MODE } from '../config';
7
+
8
+ export class TextHandler extends BaseHandler {
9
+ protected page: Page;
10
+ // private lastApiResponse: { status: number; url: string; body: any } | null = null;
11
+
12
+ constructor(page: Page, screenshotMode: ScreenshotMode = DEFAULT_SCREENSHOT_MODE) {
13
+ super();
14
+ this.page = page;
15
+ this.screenshotMode = screenshotMode;
16
+ this.setupNetworkCapture(page);
17
+ }
18
+
19
+ // // private getLastApiResponse() {
20
+ // return this.lastApiResponse;
21
+ // }
22
+
23
+ // // ─── Helpers ───────────────────────────────────────────────────────────────
24
+
25
+ // private getStartTime(): string {
26
+ // return new Date().toISOString();
27
+ // }
28
+
29
+ // private getDuration(startMs: number): number {
30
+ // return Date.now() - startMs;
31
+ // }
32
+
33
+ // private buildResult(
34
+ // status: 'PASS' | 'FAIL',
35
+ // expected_result: string,
36
+ // comments: string,
37
+ // startMs: number,
38
+ // startTime: string,
39
+ // step_name: string = '',
40
+ // uid: string | null = null,
41
+ // obj_uid: string | null = null,
42
+ // page_uid: string | null = null
43
+ // ): StepResult {
44
+ // return {
45
+ // uid,
46
+ // obj_uid,
47
+ // page_uid,
48
+ // step_name,
49
+ // step_script: '',
50
+ // status,
51
+ // expected_result,
52
+ // comments,
53
+ // duration: this.getDuration(startMs),
54
+ // start_time: startTime
55
+ // };
56
+ // }
57
+
58
+ // private async captureScreenshot(isFailure: boolean): Promise<string | undefined> {
59
+ // if (this.screenshotMode === 'never') return undefined;
60
+ // if (this.screenshotMode === 'on_failure' && !isFailure) return undefined;
61
+ // try {
62
+ // if (!this.page || this.page.isClosed()) return undefined;
63
+ // const buf = await this.page.screenshot({ type: 'png', fullPage: false });
64
+ // return buf.toString('base64');
65
+ // } catch {
66
+ // return undefined;
67
+ // }
68
+ // }
69
+
70
+ // private classifyError(error: any): string {
71
+ // const msg: string = error?.message || String(error);
72
+
73
+ // if (msg.includes('Element not found') || msg.includes('not visible')) {
74
+ // return `Element not found or not visible: ${msg}`;
75
+ // }
76
+ // if (msg.includes('Timeout') || msg.includes('timeout')) {
77
+ // return `Timeout error - element took too long to respond: ${msg}`;
78
+ // }
79
+ // if (msg.includes('not editable') || msg.includes('not enabled')) {
80
+ // return `Element is not editable or disabled: ${msg}`;
81
+ // }
82
+ // if (msg.includes('detached')) {
83
+ // return `Element got detached from DOM: ${msg}`;
84
+ // }
85
+ // if (msg.includes('Target closed') || msg.includes('has been closed')) {
86
+ // return `Browser or page closed unexpectedly: ${msg}`;
87
+ // }
88
+ // if (msg.includes('intercepts pointer events') || msg.includes('not visible within')) {
89
+ // return `Element is covered by another element: ${msg}`;
90
+ // }
91
+
92
+ // return `Unexpected error: ${msg}`;
93
+ // }
94
+
95
+ // private appendMatchedLocatorInfo(comments: string, matchedIndex: number, matchedXPath: string): string {
96
+ // if (matchedIndex > 0) {
97
+ // return `${comments} (Matched locator at position ${matchedIndex}: ${matchedXPath})`;
98
+ // }
99
+ // return comments;
100
+ // }
101
+
102
+ // ─── enterText ─────────────────────────────────────────────────────────────
103
+
104
+ async enterText(label: string, locators: string[], value: string, step_name: string = 'Enter text'): Promise<StepResult> {
105
+ const startMs = Date.now();
106
+ const startTime = this.getStartTime();
107
+ const expected = `'${value}' entered into '${label}' successfully`;
108
+
109
+ try {
110
+ const resolved = await LocatorService.resolveElement(this.page, locators);
111
+ await resolved.element.fill(value);
112
+
113
+ const comments = this.appendMatchedLocatorInfo(expected, resolved.matchedIndex, resolved.matchedXPath);
114
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
115
+ } catch (error: any) {
116
+ // const sc = await this.captureScreenshot(true);
117
+ const msg = (error?.message || String(error)).toLowerCase();
118
+ if (
119
+ msg.includes('waiting for locator') ||
120
+ msg.includes('timeout') ||
121
+ msg.includes('no element') ||
122
+ msg.includes('not found') ||
123
+ msg.includes('not visible') ||
124
+ msg.includes('not attached')
125
+ ) {
126
+ const captured = this.getLastApiResponse();
127
+ if (captured) {
128
+ const bodyText = typeof captured.body === 'object' ? JSON.stringify(captured.body, null, 2) : String(captured.body);
129
+ const comments = `Element "${label}" not found on page.\nNetwork response [HTTP ${captured.status}] from ${captured.url}:\n${bodyText}`;
130
+ return this.buildResult('FAIL', expected, comments, startMs, startTime, step_name);
131
+ }
132
+
133
+ return this.buildResult('FAIL', expected, `"${label}" not found`, startMs, startTime, step_name);
134
+ }
135
+
136
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
137
+ }
138
+ }
139
+
140
+ // ─── clearText ─────────────────────────────────────────────────────────────
141
+
142
+ async clearText(label: string, locators: string[], step_name: string = 'Clear text'): Promise<StepResult> {
143
+ const startMs = Date.now();
144
+ const startTime = this.getStartTime();
145
+ const expected = `'${label}' cleared successfully`;
146
+
147
+ try {
148
+ const resolved = await LocatorService.resolveElement(this.page, locators);
149
+ await resolved.element.fill('');
150
+
151
+ const comments = this.appendMatchedLocatorInfo(expected, resolved.matchedIndex, resolved.matchedXPath);
152
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
153
+ } catch (error: any) {
154
+ // const sc = await this.captureScreenshot(true);
155
+ const msg = (error?.message || String(error)).toLowerCase();
156
+ if (
157
+ msg.includes('waiting for locator') ||
158
+ msg.includes('timeout') ||
159
+ msg.includes('no element') ||
160
+ msg.includes('not found') ||
161
+ msg.includes('not visible') ||
162
+ msg.includes('not attached')
163
+ ) {
164
+ const captured = this.getLastApiResponse();
165
+ if (captured) {
166
+ const bodyText = typeof captured.body === 'object' ? JSON.stringify(captured.body, null, 2) : String(captured.body);
167
+ const comments = `Element "${label}" not found on page.\nNetwork response [HTTP ${captured.status}] from ${captured.url}:\n${bodyText}`;
168
+ return this.buildResult('FAIL', expected, comments, startMs, startTime, step_name);
169
+ }
170
+
171
+ return this.buildResult('FAIL', expected, `"${label}" not found`, startMs, startTime, step_name);
172
+ }
173
+
174
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
175
+ }
176
+ }
177
+
178
+ // ─── getText ───────────────────────────────────────────────────────────────
179
+
180
+ async getText(label: string, locators: string[], step_name: string = 'Get text'): Promise<StepResult> {
181
+ const startMs = Date.now();
182
+ const startTime = this.getStartTime();
183
+ const expected = `Text retrieved from '${label}' successfully`;
184
+
185
+ try {
186
+ const resolved = await LocatorService.resolveElement(this.page, locators);
187
+ const text = await resolved.element.innerText();
188
+
189
+ const comments = this.appendMatchedLocatorInfo(`Text is: ${text}`, resolved.matchedIndex, resolved.matchedXPath);
190
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
191
+ } catch (error: any) {
192
+ // const sc = await this.captureScreenshot(true);
193
+ const msg = (error?.message || String(error)).toLowerCase();
194
+ if (
195
+ msg.includes('waiting for locator') ||
196
+ msg.includes('timeout') ||
197
+ msg.includes('no element') ||
198
+ msg.includes('not found') ||
199
+ msg.includes('not visible') ||
200
+ msg.includes('not attached')
201
+ ) {
202
+ const captured = this.getLastApiResponse();
203
+ if (captured) {
204
+ const bodyText = typeof captured.body === 'object' ? JSON.stringify(captured.body, null, 2) : String(captured.body);
205
+ const comments = `Element "${label}" not found on page.\nNetwork response [HTTP ${captured.status}] from ${captured.url}:\n${bodyText}`;
206
+ return this.buildResult('FAIL', expected, comments, startMs, startTime, step_name);
207
+ }
208
+
209
+ return this.buildResult('FAIL', expected, `"${label}" not found`, startMs, startTime, step_name);
210
+ }
211
+
212
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
213
+ }
214
+ }
215
+
216
+ // ─── appendText ────────────────────────────────────────────────────────────
217
+
218
+ async appendText(label: string, locators: string[], value: string, step_name: string = 'Append text'): Promise<StepResult> {
219
+ const startMs = Date.now();
220
+ const startTime = this.getStartTime();
221
+ const expected = `'${value}' appended to '${label}' successfully`;
222
+
223
+ try {
224
+ const resolved = await LocatorService.resolveElement(this.page, locators);
225
+ const existing = await resolved.element.inputValue();
226
+ await resolved.element.fill(existing + value);
227
+
228
+ const comments = this.appendMatchedLocatorInfo(expected, resolved.matchedIndex, resolved.matchedXPath);
229
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
230
+ } catch (error: any) {
231
+ // const sc = await this.captureScreenshot(true);
232
+ const msg = (error?.message || String(error)).toLowerCase();
233
+ if (
234
+ msg.includes('waiting for locator') ||
235
+ msg.includes('timeout') ||
236
+ msg.includes('no element') ||
237
+ msg.includes('not found') ||
238
+ msg.includes('not visible') ||
239
+ msg.includes('not attached')
240
+ ) {
241
+ const captured = this.getLastApiResponse();
242
+ if (captured) {
243
+ const bodyText = typeof captured.body === 'object' ? JSON.stringify(captured.body, null, 2) : String(captured.body);
244
+ const comments = `Element "${label}" not found on page.\nNetwork response [HTTP ${captured.status}] from ${captured.url}:\n${bodyText}`;
245
+ return this.buildResult('FAIL', expected, comments, startMs, startTime, step_name);
246
+ }
247
+
248
+ return this.buildResult('FAIL', expected, `"${label}" not found`, startMs, startTime, step_name);
249
+ }
250
+
251
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
252
+ }
253
+ }
254
+
255
+ // ─── getValue ──────────────────────────────────────────────────────────────
256
+
257
+ async getValue(label: string, locators: string[], step_name: string = 'Get value'): Promise<StepResult> {
258
+ const startMs = Date.now();
259
+ const startTime = this.getStartTime();
260
+ const expected = `Value retrieved from '${label}' successfully`;
261
+
262
+ try {
263
+ const resolved = await LocatorService.resolveElement(this.page, locators);
264
+ const value = await resolved.element.inputValue();
265
+
266
+ const comments = this.appendMatchedLocatorInfo(`Value is: ${value}`, resolved.matchedIndex, resolved.matchedXPath);
267
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
268
+ } catch (error: any) {
269
+ // const sc = await this.captureScreenshot(true);
270
+ const msg = (error?.message || String(error)).toLowerCase();
271
+ if (
272
+ msg.includes('waiting for locator') ||
273
+ msg.includes('timeout') ||
274
+ msg.includes('no element') ||
275
+ msg.includes('not found') ||
276
+ msg.includes('not visible') ||
277
+ msg.includes('not attached')
278
+ ) {
279
+ const captured = this.getLastApiResponse();
280
+ if (captured) {
281
+ const bodyText = typeof captured.body === 'object' ? JSON.stringify(captured.body, null, 2) : String(captured.body);
282
+ const comments = `Element "${label}" not found on page.\nNetwork response [HTTP ${captured.status}] from ${captured.url}:\n${bodyText}`;
283
+ return this.buildResult('FAIL', expected, comments, startMs, startTime, step_name);
284
+ }
285
+
286
+ return this.buildResult('FAIL', expected, `"${label}" not found`, startMs, startTime, step_name);
287
+ }
288
+
289
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
290
+ }
291
+ }
292
+
293
+ // ─── getPlaceholder ────────────────────────────────────────────────────────
294
+
295
+ async getPlaceholder(label: string, locators: string[], step_name: string = 'Get placeholder'): Promise<StepResult> {
296
+ const startMs = Date.now();
297
+ const startTime = this.getStartTime();
298
+ const expected = `Placeholder retrieved from '${label}' successfully`;
299
+
300
+ try {
301
+ const resolved = await LocatorService.resolveElement(this.page, locators);
302
+ const placeholder = await resolved.element.getAttribute('placeholder');
303
+
304
+ const comments = this.appendMatchedLocatorInfo(`Placeholder is: ${placeholder}`, resolved.matchedIndex, resolved.matchedXPath);
305
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
306
+ } catch (error: any) {
307
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
308
+ }
309
+ }
310
+
311
+ // ─── isTextPresent ─────────────────────────────────────────────────────────
312
+
313
+ async isTextPresent(label: string, locators: string[], expectedText: string, step_name: string = 'Check text presence'): Promise<StepResult> {
314
+ const startMs = Date.now();
315
+ const startTime = this.getStartTime();
316
+ const expected = `Text '${expectedText}' is present in '${label}'`;
317
+
318
+ try {
319
+ const resolved = await LocatorService.resolveElement(this.page, locators);
320
+ const text = await resolved.element.innerText();
321
+ const isPresent = text.includes(expectedText);
322
+
323
+ if (!isPresent) {
324
+ const failComments = this.appendMatchedLocatorInfo(
325
+ `Expected text '${expectedText}' not found. Actual text: '${text}'`,
326
+ resolved.matchedIndex,
327
+ resolved.matchedXPath
328
+ );
329
+ // const sc = await this.captureScreenshot(true);
330
+ return this.buildResult('FAIL', expected, failComments, startMs, startTime, step_name);
331
+ }
332
+
333
+ const comments = this.appendMatchedLocatorInfo(expected, resolved.matchedIndex, resolved.matchedXPath);
334
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
335
+ } catch (error: any) {
336
+ // const sc = await this.captureScreenshot(true);
337
+ const msg = (error?.message || String(error)).toLowerCase();
338
+ if (
339
+ msg.includes('waiting for locator') ||
340
+ msg.includes('timeout') ||
341
+ msg.includes('no element') ||
342
+ msg.includes('not found') ||
343
+ msg.includes('not visible') ||
344
+ msg.includes('not attached')
345
+ ) {
346
+ const captured = this.getLastApiResponse();
347
+ if (captured) {
348
+ const bodyText = typeof captured.body === 'object' ? JSON.stringify(captured.body, null, 2) : String(captured.body);
349
+ const comments = `Element "${label}" not found on page.\nNetwork response [HTTP ${captured.status}] from ${captured.url}:\n${bodyText}`;
350
+ return this.buildResult('FAIL', expected, comments, startMs, startTime, step_name);
351
+ }
352
+
353
+ return this.buildResult('FAIL', expected, `"${label}" not found`, startMs, startTime, step_name);
354
+ }
355
+
356
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
357
+ }
358
+ }
359
+
360
+ // ─── verifyText ────────────────────────────────────────────────────────────
361
+
362
+ async verifyText(label: string, locators: string[], expectedText: string, step_name: string = 'Verify text'): Promise<StepResult> {
363
+ const startMs = Date.now();
364
+ const startTime = this.getStartTime();
365
+ const expected = `Text in '${label}' matches '${expectedText}'`;
366
+
367
+ try {
368
+ const resolved = await LocatorService.resolveElement(this.page, locators);
369
+ const actualText = await resolved.element.innerText();
370
+
371
+ if (actualText.trim() !== expectedText.trim()) {
372
+ const failComments = this.appendMatchedLocatorInfo(
373
+ `Expected: '${expectedText}', Actual: '${actualText}'`,
374
+ resolved.matchedIndex,
375
+ resolved.matchedXPath
376
+ );
377
+ // const sc = await this.captureScreenshot(true);
378
+ return this.buildResult('FAIL', expected, failComments, startMs, startTime, step_name);
379
+ }
380
+
381
+ const comments = this.appendMatchedLocatorInfo(expected, resolved.matchedIndex, resolved.matchedXPath);
382
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
383
+ } catch (error: any) {
384
+ // const sc = await this.captureScreenshot(true);
385
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
386
+ }
387
+ }
388
+ // ─── fileUpload ────────────────────────────────────────────────────────────
389
+
390
+ async file_upload(label: string, locators: string[], filePath: string, step_name: string = 'File upload'): Promise<StepResult> {
391
+ const startMs = Date.now();
392
+ const startTime = this.getStartTime();
393
+ const expected = `"${filePath}" should be uploaded in "${label}"`;
394
+
395
+ if (!fs.existsSync(filePath)) {
396
+ // const sc = await this.captureScreenshot(true);
397
+ return this.buildResult('FAIL', expected, `File not found: "${filePath}"`, startMs, startTime, step_name);
398
+ }
399
+
400
+ try {
401
+ const resolved = await LocatorService.resolveElement(this.page, locators);
402
+ await resolved.element.setInputFiles(filePath);
403
+
404
+ const comments = this.appendMatchedLocatorInfo(`Uploaded file "${filePath}" into "${label}"`, resolved.matchedIndex, resolved.matchedXPath);
405
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
406
+ } catch (error: any) {
407
+ // const sc = await this.captureScreenshot(true);
408
+ const msg = (error?.message || String(error)).toLowerCase();
409
+
410
+ if (
411
+ msg.includes('waiting for locator') ||
412
+ msg.includes('timeout') ||
413
+ msg.includes('no element') ||
414
+ msg.includes('not found') ||
415
+ msg.includes('not visible') ||
416
+ msg.includes('not attached')
417
+ ) {
418
+ const captured = this.getLastApiResponse();
419
+ if (captured) {
420
+ const bodyText = typeof captured.body === 'object' ? JSON.stringify(captured.body, null, 2) : String(captured.body);
421
+ const comments = `Element "${label}" not found on page.\nNetwork response [HTTP ${captured.status}] from ${captured.url}:\n${bodyText}`;
422
+ return this.buildResult('FAIL', expected, comments, startMs, startTime, step_name);
423
+ }
424
+ return this.buildResult('FAIL', expected, `"${label}" not found`, startMs, startTime, step_name);
425
+ }
426
+
427
+ if (msg.includes('enoent') || msg.includes('no such file')) {
428
+ return this.buildResult('FAIL', expected, `File not found: "${filePath}"`, startMs, startTime, step_name);
429
+ }
430
+
431
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
432
+ }
433
+ }
434
+ // ─── setSlider ─────────────────────────────────────────────────────────────
435
+
436
+ async setSlider(label: string, locators: string[], value: string, step_name: string = 'Set slider'): Promise<StepResult> {
437
+ const startMs = Date.now();
438
+ const startTime = this.getStartTime();
439
+
440
+ const targetValue = parseFloat(value);
441
+ if (isNaN(targetValue)) {
442
+ return this.buildResult('FAIL', `Slider "${label}" set successfully`, `Invalid slider value "${value}" — must be a number`, startMs, startTime, step_name);
443
+ }
444
+
445
+ const expected = `Slider "${label}" set to ${targetValue} successfully`;
446
+
447
+ try {
448
+ const resolved = await LocatorService.resolveElement(this.page, locators);
449
+
450
+ const { min, max, step, clamped } = await resolved.element.evaluate((node: Element, val: number) => {
451
+ const input = node as HTMLInputElement;
452
+ const min = input.min !== '' ? parseFloat(input.min) : 0;
453
+ const max = input.max !== '' ? parseFloat(input.max) : 100;
454
+ const step = input.step !== '' && input.step !== 'any' ? parseFloat(input.step) : 1;
455
+ const clamped = Math.min(Math.max(val, min), max);
456
+ const snapped = Math.round((clamped - min) / step) * step + min;
457
+ return { min, max, step, clamped: parseFloat(snapped.toFixed(10)) };
458
+ }, targetValue);
459
+
460
+ await resolved.element.evaluate((node: Element, val: number) => {
461
+ const input = node as HTMLInputElement;
462
+ input.value = String(val);
463
+ input.dispatchEvent(new Event('input', { bubbles: true }));
464
+ input.dispatchEvent(new Event('change', { bubbles: true }));
465
+ }, clamped);
466
+
467
+ const comments = this.appendMatchedLocatorInfo(
468
+ `Set slider "${label}" to ${clamped} (range: ${min}–${max}, step: ${step})`,
469
+ resolved.matchedIndex,
470
+ resolved.matchedXPath
471
+ );
472
+ return this.buildResult('PASS', expected, comments, startMs, startTime, step_name);
473
+
474
+ } catch (error: any) {
475
+ // const sc = await this.captureScreenshot(true);
476
+ const msg = (error?.message || String(error)).toLowerCase();
477
+
478
+ if (
479
+ msg.includes('waiting for locator') ||
480
+ msg.includes('timeout') ||
481
+ msg.includes('no element') ||
482
+ msg.includes('not found') ||
483
+ msg.includes('not visible') ||
484
+ msg.includes('not attached')
485
+ ) {
486
+ const captured = this.getLastApiResponse();
487
+ if (captured) {
488
+ const bodyText = typeof captured.body === 'object' ? JSON.stringify(captured.body, null, 2) : String(captured.body);
489
+ const comments = `Element "${label}" not found on page.\nNetwork response [HTTP ${captured.status}] from ${captured.url}:\n${bodyText}`;
490
+ return this.buildResult('FAIL', expected, comments, startMs, startTime, step_name);
491
+ }
492
+ return this.buildResult('FAIL', expected, `"${label}" not found`, startMs, startTime, step_name);
493
+ }
494
+
495
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, step_name);
496
+ }
497
+ }
498
+ }