pw-automation-framework 2.0.1

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 (111) hide show
  1. package/README.md +93 -0
  2. package/bin/lexxit-automation-framework.js +427 -0
  3. package/dist/app.d.ts +2 -0
  4. package/dist/app.js +26 -0
  5. package/dist/app.js.map +1 -0
  6. package/dist/controllers/controller.d.ts +57 -0
  7. package/dist/controllers/controller.js +263 -0
  8. package/dist/controllers/controller.js.map +1 -0
  9. package/dist/core/BrowserManager.d.ts +46 -0
  10. package/dist/core/BrowserManager.js +377 -0
  11. package/dist/core/BrowserManager.js.map +1 -0
  12. package/dist/core/PlaywrightEngine.d.ts +16 -0
  13. package/dist/core/PlaywrightEngine.js +246 -0
  14. package/dist/core/PlaywrightEngine.js.map +1 -0
  15. package/dist/core/ScreenshotManager.d.ts +10 -0
  16. package/dist/core/ScreenshotManager.js +28 -0
  17. package/dist/core/ScreenshotManager.js.map +1 -0
  18. package/dist/core/TestData.d.ts +12 -0
  19. package/dist/core/TestData.js +29 -0
  20. package/dist/core/TestData.js.map +1 -0
  21. package/dist/core/TestExecutor.d.ts +16 -0
  22. package/dist/core/TestExecutor.js +355 -0
  23. package/dist/core/TestExecutor.js.map +1 -0
  24. package/dist/core/handlers/AllHandlers.d.ts +116 -0
  25. package/dist/core/handlers/AllHandlers.js +648 -0
  26. package/dist/core/handlers/AllHandlers.js.map +1 -0
  27. package/dist/core/handlers/BaseHandler.d.ts +16 -0
  28. package/dist/core/handlers/BaseHandler.js +27 -0
  29. package/dist/core/handlers/BaseHandler.js.map +1 -0
  30. package/dist/core/handlers/ClickHandler.d.ts +34 -0
  31. package/dist/core/handlers/ClickHandler.js +359 -0
  32. package/dist/core/handlers/ClickHandler.js.map +1 -0
  33. package/dist/core/handlers/CustomCodeHandler.d.ts +35 -0
  34. package/dist/core/handlers/CustomCodeHandler.js +102 -0
  35. package/dist/core/handlers/CustomCodeHandler.js.map +1 -0
  36. package/dist/core/handlers/DropdownHandler.d.ts +43 -0
  37. package/dist/core/handlers/DropdownHandler.js +304 -0
  38. package/dist/core/handlers/DropdownHandler.js.map +1 -0
  39. package/dist/core/handlers/InputHandler.d.ts +24 -0
  40. package/dist/core/handlers/InputHandler.js +197 -0
  41. package/dist/core/handlers/InputHandler.js.map +1 -0
  42. package/dist/core/registry/ActionRegistry.d.ts +8 -0
  43. package/dist/core/registry/ActionRegistry.js +35 -0
  44. package/dist/core/registry/ActionRegistry.js.map +1 -0
  45. package/dist/installer/frameworkLauncher.d.ts +31 -0
  46. package/dist/installer/frameworkLauncher.js +198 -0
  47. package/dist/installer/frameworkLauncher.js.map +1 -0
  48. package/dist/queue/ExecutionQueue.d.ts +52 -0
  49. package/dist/queue/ExecutionQueue.js +175 -0
  50. package/dist/queue/ExecutionQueue.js.map +1 -0
  51. package/dist/routes/api.routes.d.ts +2 -0
  52. package/dist/routes/api.routes.js +16 -0
  53. package/dist/routes/api.routes.js.map +1 -0
  54. package/dist/server.d.ts +1 -0
  55. package/dist/server.js +30 -0
  56. package/dist/server.js.map +1 -0
  57. package/dist/types/types.d.ts +135 -0
  58. package/dist/types/types.js +4 -0
  59. package/dist/types/types.js.map +1 -0
  60. package/dist/utils/elementHighlight.d.ts +35 -0
  61. package/dist/utils/elementHighlight.js +136 -0
  62. package/dist/utils/elementHighlight.js.map +1 -0
  63. package/dist/utils/locatorHelper.d.ts +7 -0
  64. package/dist/utils/locatorHelper.js +53 -0
  65. package/dist/utils/locatorHelper.js.map +1 -0
  66. package/dist/utils/logger.d.ts +12 -0
  67. package/dist/utils/logger.js +35 -0
  68. package/dist/utils/logger.js.map +1 -0
  69. package/dist/utils/response.d.ts +4 -0
  70. package/dist/utils/response.js +25 -0
  71. package/dist/utils/response.js.map +1 -0
  72. package/dist/utils/responseFormatter.d.ts +78 -0
  73. package/dist/utils/responseFormatter.js +123 -0
  74. package/dist/utils/responseFormatter.js.map +1 -0
  75. package/dist/utils/sseManager.d.ts +32 -0
  76. package/dist/utils/sseManager.js +122 -0
  77. package/dist/utils/sseManager.js.map +1 -0
  78. package/lexxit-automation-framework-2.0.0.tgz +0 -0
  79. package/npmignore +5 -0
  80. package/package.json +36 -0
  81. package/scripts/postinstall.js +52 -0
  82. package/src/app.ts +27 -0
  83. package/src/controllers/controller.ts +282 -0
  84. package/src/core/BrowserManager.ts +398 -0
  85. package/src/core/PlaywrightEngine.ts +371 -0
  86. package/src/core/ScreenshotManager.ts +25 -0
  87. package/src/core/TestData.ts +25 -0
  88. package/src/core/TestExecutor.ts +436 -0
  89. package/src/core/handlers/AllHandlers.ts +626 -0
  90. package/src/core/handlers/BaseHandler.ts +41 -0
  91. package/src/core/handlers/ClickHandler.ts +482 -0
  92. package/src/core/handlers/CustomCodeHandler.ts +123 -0
  93. package/src/core/handlers/DropdownHandler.ts +438 -0
  94. package/src/core/handlers/InputHandler.ts +192 -0
  95. package/src/core/registry/ActionRegistry.ts +31 -0
  96. package/src/installer/frameworkLauncher.ts +242 -0
  97. package/src/installer/install.sh +107 -0
  98. package/src/public/dashboard.html +540 -0
  99. package/src/public/queue-monitor.html +190 -0
  100. package/src/queue/ExecutionQueue.ts +200 -0
  101. package/src/routes/api.routes.ts +16 -0
  102. package/src/server.ts +29 -0
  103. package/src/types/types.ts +169 -0
  104. package/src/utils/elementHighlight.ts +174 -0
  105. package/src/utils/locatorHelper.ts +49 -0
  106. package/src/utils/logger.ts +40 -0
  107. package/src/utils/response.ts +27 -0
  108. package/src/utils/responseFormatter.ts +167 -0
  109. package/src/utils/sseManager.ts +127 -0
  110. package/tsconfig.json +18 -0
  111. package/videos/fb1b94b6-6639-4c9a-82bb-63572606f403/page@5bd5c6c8b62baa700e9810cdd64f5c49.webm +0 -0
@@ -0,0 +1,436 @@
1
+
2
+ import {
3
+ RequestPayload, Script, Step,
4
+ ScriptResult, StepResult, StepStatus,
5
+ TestCaseConfig, ExecutionResult,
6
+ } from "../types/types";
7
+ import { TestData } from "./TestData";
8
+ import { PlaywrightEngine } from "./PlaywrightEngine";
9
+ import { sseManager } from "../utils/sseManager";
10
+ import { Logger } from "../utils/logger";
11
+
12
+ type ProgressCallback = (update: Partial<{
13
+ completed_scripts: number;
14
+ completed_steps: number;
15
+ passed_steps: number;
16
+ failed_steps: number;
17
+ skipped_steps: number;
18
+ }>) => void;
19
+
20
+ type CancelCheck = () => boolean;
21
+
22
+ const log = Logger.create("TestExecutor");
23
+
24
+ export class TestExecutor {
25
+
26
+ static async run(
27
+ payload: RequestPayload,
28
+ executionId: string,
29
+ onProgress?: ProgressCallback,
30
+ isCancelled?: CancelCheck
31
+ ): Promise<ExecutionResult> {
32
+
33
+ const startTime = Date.now();
34
+ const stopOnFailure = payload.stop_on_failure ?? true;
35
+ const runParallel = payload.parallel ?? false;
36
+ const maxParallel = payload.max_parallel ?? 5;
37
+ const checkCancel = isCancelled ?? (() => false);
38
+
39
+ sseManager.emit(executionId, "execution_start", {
40
+ message: "Execution started",
41
+ total_scripts: payload.scripts.length,
42
+ parallel: runParallel,
43
+ });
44
+
45
+ let scriptResults: ScriptResult[];
46
+
47
+ if (runParallel) {
48
+ scriptResults = await TestExecutor.runParallel(
49
+ payload.scripts, executionId, stopOnFailure, maxParallel, onProgress, checkCancel
50
+ );
51
+ } else {
52
+ scriptResults = await TestExecutor.runSequential(
53
+ payload.scripts, executionId, stopOnFailure, onProgress, checkCancel
54
+ );
55
+ }
56
+
57
+ const wasCancelled = checkCancel();
58
+ const overall_status = wasCancelled
59
+ ? "CANCELLED"
60
+ : scriptResults.every(s => s.overall_status === "PASS") ? "PASS" : "FAIL";
61
+
62
+ const duration_ms = Date.now() - startTime;
63
+
64
+ const result: ExecutionResult = {
65
+ executionId,
66
+ overall_status: overall_status as any,
67
+ total_scripts: scriptResults.length,
68
+ passed: scriptResults.filter(s => s.overall_status === "PASS").length,
69
+ failed: scriptResults.filter(s => s.overall_status === "FAIL").length,
70
+ duration_ms,
71
+ results: scriptResults,
72
+ };
73
+
74
+ sseManager.emit(executionId, "execution_complete", result);
75
+ log.info(`Execution ${executionId} → ${overall_status} (${duration_ms}ms)`);
76
+ return result;
77
+ }
78
+
79
+ // ─── Sequential ────────────────────────────────────────────────────────────
80
+
81
+ private static async runSequential(
82
+ scripts: Script[],
83
+ executionId: string,
84
+ stopOnFailure: boolean,
85
+ onProgress?: ProgressCallback,
86
+ isCancelled?: CancelCheck
87
+ ): Promise<ScriptResult[]> {
88
+ const results: ScriptResult[] = [];
89
+ let completed = 0;
90
+
91
+ for (const script of scripts) {
92
+ if (isCancelled?.()) break;
93
+ const result = await TestExecutor.runScript(script, executionId, stopOnFailure, onProgress, isCancelled);
94
+ results.push(result);
95
+ onProgress?.({ completed_scripts: ++completed });
96
+ }
97
+ return results;
98
+ }
99
+
100
+ // ─── Parallel ──────────────────────────────────────────────────────────────
101
+
102
+ private static async runParallel(
103
+ scripts: Script[],
104
+ executionId: string,
105
+ stopOnFailure: boolean,
106
+ maxParallel: number,
107
+ onProgress?: ProgressCallback,
108
+ isCancelled?: CancelCheck
109
+ ): Promise<ScriptResult[]> {
110
+ const results: ScriptResult[] = new Array(scripts.length);
111
+ let running = 0, ptr = 0, completed = 0;
112
+
113
+ return new Promise((resolve) => {
114
+ const tryNext = () => {
115
+ while (running < maxParallel && ptr < scripts.length) {
116
+ if (isCancelled?.()) { resolve(results.filter(Boolean)); return; }
117
+ const script = scripts[ptr];
118
+ const idx = ptr++;
119
+ running++;
120
+
121
+ TestExecutor.runScript(script, executionId, stopOnFailure, onProgress, isCancelled)
122
+ .then((result) => {
123
+ results[idx] = result;
124
+ running--;
125
+ completed++;
126
+ onProgress?.({ completed_scripts: completed });
127
+ tryNext();
128
+ if (completed === scripts.length) resolve(results);
129
+ })
130
+ .catch(() => {
131
+ running--;
132
+ completed++;
133
+ tryNext();
134
+ if (completed === scripts.length) resolve(results.filter(Boolean));
135
+ });
136
+ }
137
+ };
138
+ tryNext();
139
+ });
140
+ }
141
+
142
+ // ─── Single Script ─────────────────────────────────────────────────────────
143
+
144
+ private static async runScript(
145
+ script: Script,
146
+ executionId: string,
147
+ stopOnFailure: boolean,
148
+ onProgress?: ProgressCallback,
149
+ isCancelled?: CancelCheck
150
+ ): Promise<ScriptResult> {
151
+
152
+ const scriptName = script.test_case_name || script.test_script_uid;
153
+ const startTime = Date.now();
154
+ const scriptStop = script.stop_on_failure ?? stopOnFailure;
155
+
156
+ const tConfig: TestCaseConfig = {
157
+ testData: new TestData(),
158
+ screenshot_mode: script.screenshot_mode || "on_failure",
159
+ record_video: script.record_video || false,
160
+ headless: script.headless || false,
161
+ executionId,
162
+ timeout: 30000,
163
+ };
164
+
165
+ const engine = new PlaywrightEngine(tConfig);
166
+ const stepResults: StepResult[] = [];
167
+
168
+ sseManager.emit(executionId, "script_start", {
169
+ test_script_uid: script.test_script_uid,
170
+ test_case_name: scriptName,
171
+ total_steps: script.steps.length,
172
+ browser: script.browser || "chromium",
173
+ });
174
+
175
+ log.info(`Script "${scriptName}" | ${script.steps.length} steps`);
176
+
177
+ let failureOccurred = false;
178
+ let failedStepName = "";
179
+ let passed = 0, failed = 0, skipped = 0;
180
+
181
+ for (let i = 0; i < script.steps.length; i++) {
182
+ const step = script.steps[i] as Step;
183
+ const stepNumber = i + 1;
184
+ const stepStart = Date.now();
185
+
186
+ // ── Cancellation check ─────────────────────────────────────────────────
187
+ if (isCancelled?.()) {
188
+ // Mark remaining steps as SKIP with cancel reason
189
+ const remaining = script.steps.slice(i);
190
+ for (let j = 0; j < remaining.length; j++) {
191
+ const s = remaining[j];
192
+ const r: StepResult = {
193
+ uid: s.uid,
194
+ step_name: s.step_name,
195
+ step_script: s.step_script,
196
+ status: "SKIP",
197
+ comments: "Skipped — execution was cancelled",
198
+ skip_reason: "CANCELLED",
199
+ duration_ms: 0,
200
+ start_time: new Date().toISOString(),
201
+ label: s.label,
202
+ value: s.value,
203
+
204
+ };
205
+ stepResults.push(r);
206
+ skipped++;
207
+ sseManager.emit(executionId, "step_complete", {
208
+ step_number: i + j + 1,
209
+ total_steps: script.steps.length,
210
+ step_name: s.step_name,
211
+ status: "SKIP",
212
+ comments: r.comments,
213
+ skip_reason: r.skip_reason,
214
+ duration_ms: 0,
215
+ label: s.label,
216
+ value: s.value,
217
+ });
218
+ }
219
+ break;
220
+ }
221
+
222
+ // ── Failure skip ───────────────────────────────────────────────────────
223
+ if (failureOccurred && stopOnFailure) {
224
+ const r: StepResult = {
225
+ uid: step.uid,
226
+ step_name: step.step_name,
227
+ step_script: step.step_script,
228
+ status: "SKIP",
229
+ comments: `Skipped — "${failedStepName}" failed`,
230
+ skip_reason: `Predecessor "${failedStepName}" failed`,
231
+ duration_ms: 0,
232
+ start_time: new Date().toISOString(),
233
+ label: step.label,
234
+ value: step.value,
235
+ };
236
+ stepResults.push(r);
237
+ skipped++;
238
+ onProgress?.({ completed_steps: passed + failed + skipped, skipped_steps: skipped });
239
+
240
+ sseManager.emit(executionId, "step_complete", {
241
+ step_number: stepNumber,
242
+ total_steps: script.steps.length,
243
+ step_name: step.step_name,
244
+ step_script: step.step_script,
245
+ label: step.label,
246
+ status: "SKIP",
247
+ comments: r.comments,
248
+ skip_reason: r.skip_reason,
249
+ duration_ms: 0,
250
+ });
251
+
252
+ log.warn(`SKIP #${stepNumber}: "${step.step_name}"`);
253
+ continue;
254
+ }
255
+
256
+ // ── Execute ────────────────────────────────────────────────────────────
257
+ sseManager.emit(executionId, "step_start", {
258
+ step_number: stepNumber,
259
+ step_name: step.step_name,
260
+ step_script: step.step_script,
261
+ obj_uid: step.obj_uid,
262
+ page_uid: step.page_uid,
263
+ });
264
+
265
+ // let comments = "";
266
+ // let screenshot = "";
267
+ // let status: StepStatus = "FAIL";
268
+ // let expectedResult: string | undefined = undefined;
269
+
270
+ // try {
271
+ // const result = await engine.execute(step.step_script,step.label)
272
+ // comments = result?.comments || "";
273
+ // screenshot = result?.screenshot || "";
274
+ // status = result?.status === "Pass" ? "PASS" : "FAIL";
275
+ // expectedResult = result?.data?.expected_result || undefined;
276
+ // } catch (error: any) {
277
+ // comments = `Execution error: ${error instanceof Error ? error.message : String(error)}`;
278
+ // status = "FAIL";
279
+ // log.error(`CRASH #${stepNumber}: ${error.message}`);
280
+ // }
281
+ let comments = "";
282
+ let screenshot = "";
283
+ let status: StepStatus = "FAIL";
284
+ let expectedResult: string | undefined = undefined;
285
+ let result: any = undefined;
286
+
287
+ try {
288
+ result = await engine.execute(step.step_script, step.label);
289
+ comments = result?.comments || "";
290
+ screenshot = result?.screenshot || "";
291
+ status = result?.status === "Pass" ? "PASS" : "FAIL";
292
+ expectedResult = result?.data?.expected_result || undefined;
293
+ } catch (error: any) {
294
+ comments = `Execution error: ${error instanceof Error ? error.message : String(error)}`;
295
+ status = "FAIL";
296
+ log.error(`CRASH #${stepNumber}: ${error.message}`);
297
+ }
298
+ console.log("🔴 status:", status, "| failure_type:", result?.data?.failure_type); // ← ADD HERE
299
+
300
+ const duration_ms = Date.now() - stepStart;
301
+
302
+ if (status === "PASS") passed++;
303
+ else {
304
+ failed++;
305
+ if (!failureOccurred) { failureOccurred = true; failedStepName = step.step_name; }
306
+ }
307
+
308
+ onProgress?.({
309
+ completed_steps: passed + failed + skipped,
310
+ passed_steps: passed,
311
+ failed_steps: failed,
312
+ skipped_steps: skipped,
313
+ });
314
+
315
+ const stepResult: StepResult = {
316
+ uid: step.uid,
317
+ obj_uid: step.obj_uid,
318
+ step_name: step.step_name,
319
+ step_script: step.step_script,
320
+ status,
321
+ page_uid: step.page_uid,
322
+ comments,
323
+ screenshot,
324
+ duration_ms,
325
+ start_time: new Date(Date.now() - duration_ms).toISOString(),
326
+ expected_result: expectedResult,
327
+ };
328
+
329
+ // ── ELEMENT_NOT_FOUND: capture page source + call comparison API ────────
330
+ if (status === "FAIL" && result?.data?.failure_type === "ELEMENT_NOT_FOUND") {
331
+ console.log("🔍 page_uid:", step.page_uid, "| obj_uid:", step.obj_uid, "| app_id:", script.app_id);
332
+ try {
333
+ const pageSource = await engine.getPageSource();
334
+ const subUrl = await engine.getCurrentURL();
335
+
336
+ console.log(`🌐 Current URL: ${subUrl}`);
337
+
338
+ // if (pageSource) {
339
+ // const fs = require("fs");
340
+ // const path = require("path");
341
+ // const folder = "captured_pagesource";
342
+ // fs.mkdirSync(folder, { recursive: true });
343
+ // const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
344
+ // const fileName = path.join(folder, `pagesource_${step.uid}_${timestamp}.html`);
345
+ // fs.writeFileSync(fileName, pageSource, "utf-8");
346
+ // console.log(`✅ Page source saved: ${fileName}`);
347
+ // }
348
+
349
+ if (step.page_uid && pageSource) {
350
+ console.log(`🔍 Step failed at index: ${i} | uid: ${step.uid} | obj_uid: ${step.obj_uid}`);
351
+
352
+ const diffResp = await fetch("http://localhost:5005/api/ui/compare", {
353
+ method: "POST",
354
+ headers: { "Content-Type": "application/json" },
355
+ body: JSON.stringify({
356
+ app_id: script.app_id,
357
+ uid: step.page_uid,
358
+ obj_uid: step.obj_uid,
359
+ new_pagesource: pageSource,
360
+ sub_url: subUrl,
361
+ mode: "html",
362
+ failed_step_index: i,
363
+ }),
364
+ signal: AbortSignal.timeout(60000),
365
+ });
366
+ console.log("📡 comparison API status:", diffResp.status);
367
+ const comparisonResult = await diffResp.json();
368
+ console.log("📦 comparison result:", JSON.stringify(comparisonResult));
369
+ stepResult.comparison_code = comparisonResult;
370
+ console.log("✅ comparison_code set:", JSON.stringify(stepResult.comparison_code));
371
+
372
+ // stepResult.comparison_code = await diffResp.json();
373
+ }
374
+
375
+ } catch (err: any) {
376
+ stepResult.comparison_code = { success: false, error: err.message };
377
+ console.error(`❌ Page source capture failed: ${err.message}`);
378
+ }
379
+ }
380
+ // ────────────────────────────────────────────────────────────────────────
381
+ console.log("📦 stepResult before push:", JSON.stringify(stepResult));
382
+
383
+ stepResults.push(stepResult);
384
+
385
+ sseManager.emit(executionId, "step_complete", {
386
+ step_number: stepNumber,
387
+ total_steps: script.steps.length,
388
+ step_name: step.step_name,
389
+ step_script: step.step_script,
390
+ status,
391
+ comments,
392
+ screenshot,
393
+ duration_ms,
394
+ obj_uid: step.obj_uid,
395
+ comparison_code: stepResult.comparison_code,
396
+ });
397
+
398
+ log.info(`${status.padEnd(4)} #${stepNumber}: "${step.step_name}" (${duration_ms}ms)`);
399
+ }
400
+
401
+ await engine.forceClose();
402
+
403
+ const overall_status = isCancelled?.() ? "FAIL"
404
+ : failed > 0 ? "FAIL" : "PASS";
405
+ const duration_ms = Date.now() - startTime;
406
+
407
+ const scriptResult: ScriptResult = {
408
+ test_script_uid: script.test_script_uid || "",
409
+ test_case_name: scriptName,
410
+ step_results: stepResults,
411
+ overall_status: overall_status as any,
412
+ app_id: script.app_id,
413
+ duration_ms,
414
+ start_time: new Date(Date.now() - duration_ms).toISOString(),
415
+ browser: script.browser,
416
+ passed_steps: passed,
417
+ failed_steps: failed,
418
+ skipped_steps: skipped,
419
+ total_steps: script.steps.length,
420
+ };
421
+
422
+ sseManager.emit(executionId, "script_complete", {
423
+ test_script_uid: script.test_script_uid,
424
+ test_case_name: scriptName,
425
+ overall_status,
426
+ duration_ms,
427
+ passed_steps: passed,
428
+ failed_steps: failed,
429
+ skipped_steps: skipped,
430
+ total_steps: script.steps.length,
431
+ });
432
+
433
+ log.info(`Script "${scriptName}" → ${overall_status} | ✅${passed} ❌${failed} ⏭${skipped}`);
434
+ return scriptResult;
435
+ }
436
+ }