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,432 @@
1
+ import { Browser, BrowserContext, Page, chromium, firefox } from 'playwright';
2
+ import { StepResult } from '../types/types';
3
+ import * as path from 'path';
4
+ import * as fs from 'fs';
5
+
6
+ const VIDEO_DIR = path.resolve(__dirname, '../../videos');
7
+
8
+ export class BrowserManager {
9
+ private browser: Browser | null = null;
10
+ private context: BrowserContext | null = null;
11
+ private page: Page | null = null;
12
+ private videoEnabled: boolean = false;
13
+
14
+ // ─── Helpers ───────────────────────────────────────────────────────────────
15
+
16
+ private getStartTime(): string {
17
+ return new Date().toISOString();
18
+ }
19
+
20
+ private getDuration(startMs: number): number {
21
+ return Date.now() - startMs;
22
+ }
23
+
24
+ private buildResult(
25
+ status: 'PASS' | 'FAIL',
26
+ expected_result: string,
27
+ comments: string,
28
+ startMs: number,
29
+ startTime: string,
30
+ step_name: string = '',
31
+ step_script: string = '',
32
+ uid: string | null = null,
33
+ obj_uid: string | null = null,
34
+ page_uid: string | null = null
35
+ ): StepResult {
36
+ return {
37
+ uid,
38
+ obj_uid,
39
+ page_uid,
40
+ step_name,
41
+ step_script,
42
+ status,
43
+ expected_result,
44
+ comments,
45
+ duration: this.getDuration(startMs),
46
+ start_time: startTime
47
+ };
48
+ }
49
+ async captureScreenshot(screenshotMode: 'on_failure' | 'always' | 'never', isFailure: boolean): Promise<string> {
50
+ if (screenshotMode === 'never') return '';
51
+ if (screenshotMode === 'on_failure' && !isFailure) return '';
52
+ try {
53
+ if (!this.page || this.page.isClosed()) return '';
54
+ const buf = await this.page.screenshot({ type: 'png', fullPage: false });
55
+ return buf.toString('base64');
56
+ } catch {
57
+ return '';
58
+ }
59
+ }
60
+ private ensureVideoDir(): void {
61
+ if (!fs.existsSync(VIDEO_DIR)) {
62
+ fs.mkdirSync(VIDEO_DIR, { recursive: true });
63
+ }
64
+ }
65
+
66
+ private classifyError(error: any): string {
67
+ const msg: string = error?.message || String(error);
68
+
69
+ if (msg.includes('net::ERR_NAME_NOT_RESOLVED') || msg.includes('net::ERR_CONNECTION_REFUSED')) {
70
+ return `Network error - URL unreachable: ${msg}`;
71
+ }
72
+ if (msg.includes('net::ERR_INTERNET_DISCONNECTED')) {
73
+ return `No internet connection: ${msg}`;
74
+ }
75
+ if (msg.includes('Timeout') || msg.includes('timeout')) {
76
+ return `Timeout error - page took too long to respond: ${msg}`;
77
+ }
78
+ if (msg.includes('Target closed') || msg.includes('Target page, context or browser has been closed')) {
79
+ return `Browser or page was closed unexpectedly: ${msg}`;
80
+ }
81
+ if (msg.includes('net::ERR_CERT') || msg.includes('SSL')) {
82
+ return `SSL/Certificate error: ${msg}`;
83
+ }
84
+ if (msg.includes('executable doesn\'t exist') || msg.includes('Failed to launch')) {
85
+ return `Browser launch failed - browser executable not found: ${msg}`;
86
+ }
87
+ if (msg.includes('msedge') || msg.includes('edge')) {
88
+ return `Edge browser error - ensure Microsoft Edge is installed: ${msg}`;
89
+ }
90
+ if (msg.includes('Permission denied') || msg.includes('EACCES')) {
91
+ return `Permission denied - check folder/file permissions: ${msg}`;
92
+ }
93
+ if (msg.includes('ENOENT')) {
94
+ return `File or directory not found: ${msg}`;
95
+ }
96
+ if (msg.includes('Navigation failed')) {
97
+ return `Navigation failed - page may have crashed or redirected incorrectly: ${msg}`;
98
+ }
99
+ if (msg.includes('Page is not initialized') || msg.includes('Browser is not open')) {
100
+ return `Browser not initialized - call openBrowser first: ${msg}`;
101
+ }
102
+
103
+ return `Unexpected error: ${msg}`;
104
+ }
105
+
106
+ getPage(): Page {
107
+ if (!this.page) throw new Error('Page is not initialized. Call openBrowser first.');
108
+ return this.page;
109
+ }
110
+
111
+ // ─── openBrowser ───────────────────────────────────────────────────────────
112
+
113
+ // async openBrowser(
114
+ // browserType: 'chromium' | 'firefox' | 'edge',
115
+ // headless: boolean = false,
116
+ // videoEnabled: boolean = false
117
+ // ): Promise<StepResult> {
118
+ // const startMs = Date.now();
119
+ // const startTime = this.getStartTime();
120
+ // const expected = `${browserType} browser opened successfully`;
121
+ // this.videoEnabled = videoEnabled;
122
+
123
+ // try {
124
+ // if (browserType === 'firefox') {
125
+ // this.browser = await firefox.launch({ headless });
126
+ // } else if (browserType === 'edge') {
127
+ // this.browser = await chromium.launch({ channel: 'msedge', headless });
128
+ // } else {
129
+ // this.browser = await chromium.launch({ headless });
130
+ // }
131
+
132
+ // const contextOptions: any = {};
133
+
134
+ // if (this.videoEnabled) {
135
+ // this.ensureVideoDir();
136
+ // contextOptions.recordVideo = {
137
+ // dir: VIDEO_DIR,
138
+ // size: { width: 1280, height: 720 }
139
+ // };
140
+ // }
141
+
142
+ // this.context = await this.browser.newContext(contextOptions);
143
+ // this.page = await this.context.newPage();
144
+
145
+ // return this.buildResult('PASS', expected, expected, startMs, startTime, `Open ${browserType}`);
146
+ // } catch (error: any) {
147
+ // return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, `Open ${browserType}`);
148
+ // }
149
+ // }
150
+ async openBrowser(
151
+ browserType: 'chromium' | 'chrome' | 'firefox' | 'edge',
152
+ headless: boolean = false,
153
+ videoEnabled: boolean = false
154
+ ): Promise<StepResult> {
155
+ const startMs = Date.now();
156
+ const startTime = this.getStartTime();
157
+ const expected = `${browserType} browser opened successfully`;
158
+ this.videoEnabled = videoEnabled;
159
+
160
+ try {
161
+ if (browserType === 'firefox') {
162
+ this.browser = await firefox.launch({ headless });
163
+ } else if (browserType === 'edge') {
164
+ this.browser = await chromium.launch({ channel: 'msedge', headless });
165
+ } else if (browserType === 'chrome') {
166
+ this.browser = await chromium.launch({ channel: 'chrome', headless });
167
+ } else {
168
+ this.browser = await chromium.launch({ headless });
169
+ }
170
+
171
+ const contextOptions: any = {};
172
+
173
+ if (this.videoEnabled) {
174
+ this.ensureVideoDir();
175
+ contextOptions.recordVideo = {
176
+ dir: VIDEO_DIR,
177
+ size: { width: 1280, height: 720 }
178
+ };
179
+ }
180
+
181
+ this.context = await this.browser.newContext(contextOptions);
182
+ this.page = await this.context.newPage();
183
+
184
+ return this.buildResult('PASS', expected, expected, startMs, startTime, `Open ${browserType}`);
185
+ } catch (error: any) {
186
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, `Open ${browserType}`);
187
+ }
188
+ }
189
+ // ─── closeBrowser ──────────────────────────────────────────────────────────
190
+
191
+ async closeBrowser(testScriptUid: string = ''): Promise<StepResult> {
192
+ const startMs = Date.now();
193
+ const startTime = this.getStartTime();
194
+ const expected = 'Browser closed successfully';
195
+
196
+ try {
197
+ if (this.videoEnabled && this.page) {
198
+ const video = this.page.video();
199
+
200
+ if (video) {
201
+ const destPath = path.join(VIDEO_DIR, `${testScriptUid || 'recording'}_${Date.now()}.webm`);
202
+ await this.context?.close();
203
+ await video.saveAs(destPath);
204
+ await video.delete();
205
+ this.savedVideoPath = destPath;
206
+ } else {
207
+ await this.context?.close();
208
+ }
209
+ } else {
210
+ await this.context?.close();
211
+ }
212
+
213
+ await this.browser?.close();
214
+ this.browser = null;
215
+ this.context = null;
216
+ this.page = null;
217
+
218
+ return this.buildResult('PASS', expected, expected, startMs, startTime, 'Close browser');
219
+ } catch (error: any) {
220
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Close browser');
221
+ }
222
+
223
+ }
224
+
225
+
226
+ // ─── navigateToURL ─────────────────────────────────────────────────────────
227
+
228
+ async navigateToURL(url: string): Promise<StepResult> {
229
+ const startMs = Date.now();
230
+ const startTime = this.getStartTime();
231
+ const expected = `${url} opened successfully`;
232
+
233
+ try {
234
+ if (!this.page) throw new Error('Browser is not open');
235
+ await this.page.goto(url, { waitUntil: 'load', timeout: 30000 });
236
+
237
+ return this.buildResult('PASS', expected, expected, startMs, startTime, 'Navigate to URL');
238
+ } catch (error: any) {
239
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Navigate to URL');
240
+ }
241
+ }
242
+
243
+ // ─── getTitle ──────────────────────────────────────────────────────────────
244
+
245
+ async getTitle(): Promise<StepResult> {
246
+ const startMs = Date.now();
247
+ const startTime = this.getStartTime();
248
+ const expected = 'Page title retrieved successfully';
249
+
250
+ try {
251
+ if (!this.page) throw new Error('Browser is not open');
252
+ const title = await this.page.title();
253
+
254
+ return this.buildResult('PASS', expected, `Page title is: ${title}`, startMs, startTime, 'Get title');
255
+ } catch (error: any) {
256
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Get title');
257
+ }
258
+ }
259
+
260
+ // ─── waitUntilLoaded ───────────────────────────────────────────────────────
261
+
262
+ async waitUntilLoaded(): Promise<StepResult> {
263
+ const startMs = Date.now();
264
+ const startTime = this.getStartTime();
265
+ const expected = 'Page loaded successfully';
266
+
267
+ try {
268
+ if (!this.page) throw new Error('Browser is not open');
269
+ await this.page.waitForLoadState('load', { timeout: 30000 });
270
+
271
+ return this.buildResult('PASS', expected, expected, startMs, startTime, 'Wait until loaded');
272
+ } catch (error: any) {
273
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Wait until loaded');
274
+ }
275
+ }
276
+
277
+ // ─── waitForNetworkIdle ────────────────────────────────────────────────────
278
+
279
+ async waitForNetworkIdle(): Promise<StepResult> {
280
+ const startMs = Date.now();
281
+ const startTime = this.getStartTime();
282
+ const expected = 'Network is idle';
283
+
284
+ try {
285
+ if (!this.page) throw new Error('Browser is not open');
286
+ await this.page.waitForLoadState('networkidle', { timeout: 30000 });
287
+
288
+ return this.buildResult('PASS', expected, expected, startMs, startTime, 'Wait for network idle');
289
+ } catch (error: any) {
290
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Wait for network idle');
291
+ }
292
+ }
293
+
294
+ // ─── reloadPage ────────────────────────────────────────────────────────────
295
+
296
+ async reloadPage(): Promise<StepResult> {
297
+ const startMs = Date.now();
298
+ const startTime = this.getStartTime();
299
+ const expected = 'Page reloaded successfully';
300
+
301
+ try {
302
+ if (!this.page) throw new Error('Browser is not open');
303
+ await this.page.reload({ waitUntil: 'load', timeout: 30000 });
304
+
305
+ return this.buildResult('PASS', expected, expected, startMs, startTime, 'Reload page');
306
+ } catch (error: any) {
307
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Reload page');
308
+ }
309
+ }
310
+
311
+ // ─── goBack ────────────────────────────────────────────────────────────────
312
+
313
+ async goBack(): Promise<StepResult> {
314
+ const startMs = Date.now();
315
+ const startTime = this.getStartTime();
316
+ const expected = 'Navigated back successfully';
317
+
318
+ try {
319
+ if (!this.page) throw new Error('Browser is not open');
320
+ await this.page.goBack({ waitUntil: 'load', timeout: 30000 });
321
+
322
+ return this.buildResult('PASS', expected, expected, startMs, startTime, 'Go back');
323
+ } catch (error: any) {
324
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Go back');
325
+ }
326
+ }
327
+
328
+ // ─── goForward ─────────────────────────────────────────────────────────────
329
+
330
+ async goForward(): Promise<StepResult> {
331
+ const startMs = Date.now();
332
+ const startTime = this.getStartTime();
333
+ const expected = 'Navigated forward successfully';
334
+
335
+ try {
336
+ if (!this.page) throw new Error('Browser is not open');
337
+ await this.page.goForward({ waitUntil: 'load', timeout: 30000 });
338
+
339
+ return this.buildResult('PASS', expected, expected, startMs, startTime, 'Go forward');
340
+ } catch (error: any) {
341
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Go forward');
342
+ }
343
+ }
344
+
345
+ // ─── getCurrentURL ─────────────────────────────────────────────────────────
346
+
347
+ async getCurrentURL(): Promise<StepResult> {
348
+ const startMs = Date.now();
349
+ const startTime = this.getStartTime();
350
+ const expected = 'Current URL retrieved successfully';
351
+
352
+ try {
353
+ if (!this.page) throw new Error('Browser is not open');
354
+ const url = this.page.url();
355
+
356
+ return this.buildResult('PASS', expected, `Current URL is: ${url}`, startMs, startTime, 'Get current URL');
357
+ } catch (error: any) {
358
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Get current URL');
359
+ }
360
+ }
361
+
362
+ // ─── maximizeWindow ────────────────────────────────────────────────────────
363
+
364
+ async maximizeWindow(): Promise<StepResult> {
365
+ const startMs = Date.now();
366
+ const startTime = this.getStartTime();
367
+ const expected = 'Browser window maximized successfully';
368
+
369
+ try {
370
+ if (!this.page) throw new Error('Browser is not open');
371
+ await this.page.setViewportSize({ width: 1920, height: 1080 });
372
+
373
+ return this.buildResult('PASS', expected, expected, startMs, startTime, 'Maximize window');
374
+ } catch (error: any) {
375
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Maximize window');
376
+ }
377
+ }
378
+
379
+ // ─── takeScreenshot ────────────────────────────────────────────────────────
380
+
381
+ async takeScreenshot(screenshotPath: string): Promise<StepResult> {
382
+ const startMs = Date.now();
383
+ const startTime = this.getStartTime();
384
+ const expected = `Screenshot saved at ${screenshotPath}`;
385
+
386
+ try {
387
+ if (!this.page) throw new Error('Browser is not open');
388
+ const dir = path.dirname(screenshotPath);
389
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
390
+ await this.page.screenshot({ path: screenshotPath, fullPage: true });
391
+
392
+ return this.buildResult('PASS', expected, expected, startMs, startTime, 'Take screenshot');
393
+ } catch (error: any) {
394
+ return this.buildResult('FAIL', expected, this.classifyError(error), startMs, startTime, 'Take screenshot');
395
+ }
396
+ }
397
+
398
+ // ─── getPageSource ────────────────────────────────────────────────────────
399
+
400
+ /**
401
+ * Get the HTML page source (for auto-healing comparison)
402
+ */
403
+ async getPageSourceContent(): Promise<string | null> {
404
+ try {
405
+ if (!this.page) throw new Error('Browser is not open');
406
+ return await this.page.content();
407
+ } catch (error: any) {
408
+ console.error(`Failed to get page source: ${error.message}`);
409
+ return null;
410
+ }
411
+ }
412
+ private savedVideoPath: string | null = null;
413
+
414
+ getSavedVideoPath(): string | null {
415
+ return this.savedVideoPath;
416
+ }
417
+
418
+ // ─── getCurrentURLString ────────────────────────────────────────────────────
419
+
420
+ /**
421
+ * Get the current URL as a string (for auto-healing context)
422
+ */
423
+ async getCurrentURLString(): Promise<string> {
424
+ try {
425
+ if (!this.page) throw new Error('Browser is not open');
426
+ return this.page.url();
427
+ } catch (error: any) {
428
+ console.error(`Failed to get current URL: ${error.message}`);
429
+ return 'unknown';
430
+ }
431
+ }
432
+ }