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,12 @@
1
+ export declare class Logger {
2
+ private executionId;
3
+ private context;
4
+ constructor(context: string, executionId?: string);
5
+ private log;
6
+ debug(msg: string, ctx?: Record<string, any>): void;
7
+ info(msg: string, ctx?: Record<string, any>): void;
8
+ warn(msg: string, ctx?: Record<string, any>): void;
9
+ error(msg: string, ctx?: Record<string, any>): void;
10
+ child(context: string): Logger;
11
+ static create(context: string, executionId?: string): Logger;
12
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = void 0;
4
+ const COLORS = {
5
+ DEBUG: "\x1b[36m", // cyan
6
+ INFO: "\x1b[32m", // green
7
+ WARN: "\x1b[33m", // yellow
8
+ ERROR: "\x1b[31m", // red
9
+ };
10
+ const RESET = "\x1b[0m";
11
+ class Logger {
12
+ constructor(context, executionId = "global") {
13
+ this.context = context;
14
+ this.executionId = executionId;
15
+ }
16
+ log(level, message, extra) {
17
+ const ts = new Date().toISOString();
18
+ const color = COLORS[level];
19
+ const tag = `[${level}][${this.context}]`;
20
+ const extra_str = extra ? ` ${JSON.stringify(extra)}` : "";
21
+ console.log(`${color}${ts} ${tag}${RESET} ${message}${extra_str}`);
22
+ }
23
+ debug(msg, ctx) { this.log("DEBUG", msg, ctx); }
24
+ info(msg, ctx) { this.log("INFO", msg, ctx); }
25
+ warn(msg, ctx) { this.log("WARN", msg, ctx); }
26
+ error(msg, ctx) { this.log("ERROR", msg, ctx); }
27
+ child(context) {
28
+ return new Logger(`${this.context}:${context}`, this.executionId);
29
+ }
30
+ static create(context, executionId) {
31
+ return new Logger(context, executionId);
32
+ }
33
+ }
34
+ exports.Logger = Logger;
35
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":";;;AAEA,MAAM,MAAM,GAA6B;IACvC,KAAK,EAAE,UAAU,EAAG,OAAO;IAC3B,IAAI,EAAG,UAAU,EAAG,QAAQ;IAC5B,IAAI,EAAG,UAAU,EAAG,SAAS;IAC7B,KAAK,EAAE,UAAU,EAAG,MAAM;CAC3B,CAAC;AACF,MAAM,KAAK,GAAG,SAAS,CAAC;AAExB,MAAa,MAAM;IAIjB,YAAY,OAAe,EAAE,cAAsB,QAAQ;QACzD,IAAI,CAAC,OAAO,GAAO,OAAO,CAAC;QAC3B,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAEO,GAAG,CAAC,KAAe,EAAE,OAAe,EAAE,KAA2B;QACvE,MAAM,EAAE,GAAM,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,GAAG,GAAK,IAAI,KAAK,KAAK,IAAI,CAAC,OAAO,GAAG,CAAC;QAC5C,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,EAAE,IAAI,GAAG,GAAG,KAAK,IAAI,OAAO,GAAG,SAAS,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,KAAK,CAAC,GAAW,EAAE,GAAyB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9E,IAAI,CAAE,GAAW,EAAE,GAAyB,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAG,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9E,IAAI,CAAE,GAAW,EAAE,GAAyB,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAG,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAC9E,KAAK,CAAC,GAAW,EAAE,GAAyB,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;IAE9E,KAAK,CAAC,OAAe;QACnB,OAAO,IAAI,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,IAAI,OAAO,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,CAAC,MAAM,CAAC,OAAe,EAAE,WAAoB;QACjD,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC1C,CAAC;CACF;AA7BD,wBA6BC"}
@@ -0,0 +1,4 @@
1
+ import { ActionResponse } from "../types/types";
2
+ export declare function pass(comments: string, screenshot?: string, data?: any): ActionResponse;
3
+ export declare function fail(comments: string, errorDetail?: string, screenshot?: string, data?: any): ActionResponse;
4
+ export declare function failElementNotFound(displayLabel: string, screenshot?: string, extraData?: Record<string, any>): ActionResponse;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pass = pass;
4
+ exports.fail = fail;
5
+ exports.failElementNotFound = failElementNotFound;
6
+ function pass(comments, screenshot, data) {
7
+ return { status: "Pass", comments, screenshot, data };
8
+ }
9
+ function fail(comments, errorDetail, screenshot, data) {
10
+ const message = errorDetail ? `${comments} — ${errorDetail}` : comments;
11
+ return { status: "Fail", comments: message, screenshot, data };
12
+ }
13
+ function failElementNotFound(displayLabel, screenshot, extraData) {
14
+ return {
15
+ status: "Fail",
16
+ comments: `"${displayLabel}" not found`,
17
+ screenshot,
18
+ data: {
19
+ expected_result: `"${displayLabel}" found and interacted with`,
20
+ failure_type: "ELEMENT_NOT_FOUND",
21
+ ...extraData,
22
+ },
23
+ };
24
+ }
25
+ //# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/utils/response.ts"],"names":[],"mappings":";;AAEA,oBAEC;AAED,oBAGC;AACD,kDAeC;AAvBD,SAAgB,IAAI,CAAC,QAAgB,EAAE,UAAmB,EAAE,IAAU;IACpE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACxD,CAAC;AAED,SAAgB,IAAI,CAAC,QAAgB,EAAE,WAAoB,EAAE,UAAmB,EAAE,IAAU;IAC1F,MAAM,OAAO,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,QAAQ,MAAM,WAAW,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AACjE,CAAC;AACD,SAAgB,mBAAmB,CACjC,YAAoB,EACpB,UAAmB,EACnB,SAA+B;IAE/B,OAAO;QACL,MAAM,EAAE,MAAM;QACd,QAAQ,EAAE,IAAI,YAAY,aAAa;QACvC,UAAU;QACV,IAAI,EAAE;YACJ,eAAe,EAAE,IAAI,YAAY,6BAA6B;YAC9D,YAAY,EAAE,mBAAmB;YACjC,GAAG,SAAS;SACb;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * responseFormatter.ts
3
+ * ====================
4
+ * Converts internal framework types → the standard API response format
5
+ * that matches what the main application expects.
6
+ *
7
+ * Standard format example:
8
+ * {
9
+ * "status": "fail",
10
+ * "results": [
11
+ * {
12
+ * "test_script_uid": "...",
13
+ * "app_id": "...",
14
+ * "status": "fail",
15
+ * "results": [
16
+ * {
17
+ * "uid": null,
18
+ * "obj_uid": "...",
19
+ * "step_name": "...",
20
+ * "page_uid": null,
21
+ * "comments": "...",
22
+ * "screenshot": "base64...",
23
+ * "duration": "8 seconds",
24
+ * "start_time": "2026-04-04 20:25:34",
25
+ * "expected_result": "...",
26
+ * "status": "pass"
27
+ * }
28
+ * ],
29
+ * "duration": "42 seconds",
30
+ * "script_start_time": "2026-04-04 20:25:34"
31
+ * }
32
+ * ]
33
+ * }
34
+ */
35
+ import { ExecutionResult, ScriptResult, StepResult } from "../types/types";
36
+ export interface FormattedStepResult {
37
+ uid: string | null;
38
+ obj_uid: string | null;
39
+ step_name: string;
40
+ step_script?: string;
41
+ page_uid: string | null;
42
+ comments: string;
43
+ screenshot?: string;
44
+ duration: string;
45
+ start_time: string;
46
+ expected_result: string;
47
+ status: "pass" | "fail" | "skip";
48
+ comparison_code?: any;
49
+ }
50
+ export interface FormattedScriptResult {
51
+ test_script_uid: string;
52
+ app_id: string | undefined;
53
+ status: "pass" | "fail";
54
+ results: FormattedStepResult[];
55
+ duration: string;
56
+ script_start_time: string;
57
+ comparison_code?: any;
58
+ }
59
+ export interface FormattedExecutionResponse {
60
+ status: "pass" | "fail";
61
+ executionId: string;
62
+ results: FormattedScriptResult[];
63
+ total_scripts: number;
64
+ passed_scripts: number;
65
+ failed_scripts: number;
66
+ total_duration: string;
67
+ }
68
+ /**
69
+ * Format milliseconds → "X seconds" or "less than 1 second"
70
+ */
71
+ export declare function formatDuration(ms: number): string;
72
+ /**
73
+ * Format ISO timestamp → "YYYY-MM-DD HH:MM:SS"
74
+ */
75
+ export declare function formatDateTime(isoString: string): string;
76
+ export declare function formatStepResult(step: StepResult): FormattedStepResult;
77
+ export declare function formatScriptResult(script: ScriptResult): FormattedScriptResult;
78
+ export declare function formatExecutionResult(result: ExecutionResult): FormattedExecutionResponse;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ /**
3
+ * responseFormatter.ts
4
+ * ====================
5
+ * Converts internal framework types → the standard API response format
6
+ * that matches what the main application expects.
7
+ *
8
+ * Standard format example:
9
+ * {
10
+ * "status": "fail",
11
+ * "results": [
12
+ * {
13
+ * "test_script_uid": "...",
14
+ * "app_id": "...",
15
+ * "status": "fail",
16
+ * "results": [
17
+ * {
18
+ * "uid": null,
19
+ * "obj_uid": "...",
20
+ * "step_name": "...",
21
+ * "page_uid": null,
22
+ * "comments": "...",
23
+ * "screenshot": "base64...",
24
+ * "duration": "8 seconds",
25
+ * "start_time": "2026-04-04 20:25:34",
26
+ * "expected_result": "...",
27
+ * "status": "pass"
28
+ * }
29
+ * ],
30
+ * "duration": "42 seconds",
31
+ * "script_start_time": "2026-04-04 20:25:34"
32
+ * }
33
+ * ]
34
+ * }
35
+ */
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.formatDuration = formatDuration;
38
+ exports.formatDateTime = formatDateTime;
39
+ exports.formatStepResult = formatStepResult;
40
+ exports.formatScriptResult = formatScriptResult;
41
+ exports.formatExecutionResult = formatExecutionResult;
42
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
43
+ /**
44
+ * Format milliseconds → "X seconds" or "less than 1 second"
45
+ */
46
+ function formatDuration(ms) {
47
+ if (ms < 1000)
48
+ return "less than 1 second";
49
+ const secs = Math.round(ms / 1000);
50
+ return `${secs} second${secs !== 1 ? "s" : ""}`;
51
+ }
52
+ /**
53
+ * Format ISO timestamp → "YYYY-MM-DD HH:MM:SS"
54
+ */
55
+ function formatDateTime(isoString) {
56
+ try {
57
+ const d = new Date(isoString);
58
+ const pad = (n) => String(n).padStart(2, "0");
59
+ return (`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
60
+ `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`);
61
+ }
62
+ catch {
63
+ return isoString;
64
+ }
65
+ }
66
+ /**
67
+ * Map internal StepStatus to lowercase API status
68
+ */
69
+ function mapStepStatus(status) {
70
+ switch (status?.toUpperCase()) {
71
+ case "PASS": return "pass";
72
+ case "FAIL": return "fail";
73
+ case "SKIP":
74
+ case "RUNNING":
75
+ case "PENDING": return "skip";
76
+ default: return "skip";
77
+ }
78
+ }
79
+ // ─── Formatters ──────────────────────────────────────────────────────────────
80
+ function formatStepResult(step) {
81
+ return {
82
+ uid: step.uid ?? null,
83
+ obj_uid: step.obj_uid ?? null,
84
+ step_name: step.step_name,
85
+ step_script: step.step_script,
86
+ page_uid: step.page_uid ?? null,
87
+ comments: step.skip_reason
88
+ ? `Skipped — ${step.skip_reason}`
89
+ : (step.comments || ""),
90
+ screenshot: step.screenshot,
91
+ duration: formatDuration(step.duration_ms),
92
+ start_time: formatDateTime(step.start_time),
93
+ expected_result: step.expected_result || "",
94
+ status: mapStepStatus(step.status),
95
+ // comparison_code: step.comparison_code,
96
+ };
97
+ }
98
+ function formatScriptResult(script) {
99
+ const failedStep = script.step_results.find(s => s.comparison_code); // ← this line
100
+ return {
101
+ test_script_uid: script.test_script_uid,
102
+ app_id: script.app_id,
103
+ status: script.overall_status === "PASS" ? "pass" : "fail",
104
+ results: script.step_results.map(formatStepResult),
105
+ duration: formatDuration(script.duration_ms),
106
+ script_start_time: formatDateTime(script.start_time),
107
+ comparison_code: failedStep?.comparison_code,
108
+ };
109
+ }
110
+ function formatExecutionResult(result) {
111
+ const formattedResults = result.results.map(formatScriptResult);
112
+ const overallPass = formattedResults.every(r => r.status === "pass");
113
+ return {
114
+ status: overallPass ? "pass" : "fail",
115
+ executionId: result.executionId,
116
+ results: formattedResults,
117
+ total_scripts: result.total_scripts,
118
+ passed_scripts: result.passed,
119
+ failed_scripts: result.failed,
120
+ total_duration: formatDuration(result.duration_ms),
121
+ };
122
+ }
123
+ //# sourceMappingURL=responseFormatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseFormatter.js","sourceRoot":"","sources":["../../src/utils/responseFormatter.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;;AAgDH,wCAIC;AAKD,wCAWC;AAmBD,4CAiBC;AAED,gDAYC;AAED,sDAaC;AA1FD,gFAAgF;AAEhF;;GAEG;AACH,SAAgB,cAAc,CAAC,EAAU;IACvC,IAAI,EAAE,GAAG,IAAI;QAAE,OAAO,oBAAoB,CAAC;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;IACnC,OAAO,GAAG,IAAI,UAAU,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,SAAiB;IAC9C,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;QAC9B,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QACtD,OAAO,CACL,GAAG,CAAC,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG;YAClE,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,EAAE,CACrE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAGD;;GAEG;AACH,SAAS,aAAa,CAAC,MAAc;IACnC,QAAQ,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC;QAC9B,KAAK,MAAM,CAAC,CAAI,OAAO,MAAM,CAAC;QAC9B,KAAK,MAAM,CAAC,CAAI,OAAO,MAAM,CAAC;QAC9B,KAAK,MAAM,CAAC;QACZ,KAAK,SAAS,CAAC;QACf,KAAK,SAAS,CAAC,CAAC,OAAO,MAAM,CAAC;QAC9B,OAAO,CAAC,CAAQ,OAAO,MAAM,CAAC;IAChC,CAAC;AACH,CAAC;AAED,gFAAgF;AAEhF,SAAgB,gBAAgB,CAAC,IAAgB;IAC/C,OAAO;QACL,GAAG,EAAc,IAAI,CAAC,GAAG,IAAS,IAAI;QACtC,OAAO,EAAU,IAAI,CAAC,OAAO,IAAK,IAAI;QACtC,SAAS,EAAQ,IAAI,CAAC,SAAS;QAC/B,WAAW,EAAM,IAAI,CAAC,WAAW;QACjC,QAAQ,EAAS,IAAI,CAAC,QAAQ,IAAI,IAAI;QACtC,QAAQ,EAAS,IAAI,CAAC,WAAW;YACd,CAAC,CAAC,aAAa,IAAI,CAAC,WAAW,EAAE;YACjC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;QAC1C,UAAU,EAAO,IAAI,CAAC,UAAU;QAChC,QAAQ,EAAS,cAAc,CAAC,IAAI,CAAC,WAAW,CAAC;QACjD,UAAU,EAAO,cAAc,CAAC,IAAI,CAAC,UAAU,CAAC;QAChD,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,EAAE;QAC3C,MAAM,EAAW,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;QAC3C,0CAA0C;KAC3C,CAAC;AACJ,CAAC;AAED,SAAgB,kBAAkB,CAAC,MAAoB;IACrD,MAAM,UAAU,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAE,cAAc;IACpF,OAAO;QACL,eAAe,EAAI,MAAM,CAAC,eAAe;QACzC,MAAM,EAAa,MAAM,CAAC,MAAM;QAChC,MAAM,EAAa,MAAM,CAAC,cAAc,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QACrE,OAAO,EAAY,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,CAAC;QAC5D,QAAQ,EAAW,cAAc,CAAC,MAAM,CAAC,WAAW,CAAC;QACrD,iBAAiB,EAAE,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC;QACnD,eAAe,EAAI,UAAU,EAAE,eAAe;KAEhD,CAAC;AACJ,CAAC;AAED,SAAgB,qBAAqB,CAAC,MAAuB;IAC3D,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChE,MAAM,WAAW,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IAErE,OAAO;QACL,MAAM,EAAW,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;QAC9C,WAAW,EAAM,MAAM,CAAC,WAAW;QACnC,OAAO,EAAU,gBAAgB;QACjC,aAAa,EAAI,MAAM,CAAC,aAAa;QACrC,cAAc,EAAG,MAAM,CAAC,MAAM;QAC9B,cAAc,EAAG,MAAM,CAAC,MAAM;QAC9B,cAAc,EAAG,cAAc,CAAC,MAAM,CAAC,WAAW,CAAC;KACpD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { Response } from "express";
2
+ import { SSEEvent, SSEEventType } from "../types/types";
3
+ /**
4
+ * SSEManager — v2 with Event Replay Buffer
5
+ * =========================================
6
+ * ROOT CAUSE FIX:
7
+ * The dashboard opens AFTER POST /api/run-test returns. Since execution is
8
+ * now non-blocking, it often completes before the browser even renders the
9
+ * dashboard page and the SSE connection is established. This means the
10
+ * dashboard misses all events and shows "Waiting for execution to begin..."
11
+ * even though the run is already done.
12
+ *
13
+ * FIX: Store every emitted event in a per-execution replay buffer.
14
+ * When a new SSE client connects, immediately replay all past events.
15
+ * This way late-connecting dashboards see the full execution history.
16
+ */
17
+ export declare class SSEManager {
18
+ private clients;
19
+ private eventBuffer;
20
+ private readonly MAX_BUFFER;
21
+ private readonly BUFFER_TTL;
22
+ addClient(executionId: string, res: Response): void;
23
+ emit(executionId: string, type: SSEEventType, data: any): void;
24
+ /** Broadcast to ALL connections (e.g. queue updates, global health) */
25
+ broadcast(type: SSEEventType, data: any): void;
26
+ /** Replay buffer for a given executionId (used by status endpoint) */
27
+ getBuffer(executionId: string): SSEEvent[];
28
+ hasClients(executionId: string): boolean;
29
+ private sendToOne;
30
+ private removeClient;
31
+ }
32
+ export declare const sseManager: SSEManager;
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sseManager = exports.SSEManager = void 0;
4
+ const logger_1 = require("./logger");
5
+ const log = logger_1.Logger.create("SSEManager");
6
+ /**
7
+ * SSEManager — v2 with Event Replay Buffer
8
+ * =========================================
9
+ * ROOT CAUSE FIX:
10
+ * The dashboard opens AFTER POST /api/run-test returns. Since execution is
11
+ * now non-blocking, it often completes before the browser even renders the
12
+ * dashboard page and the SSE connection is established. This means the
13
+ * dashboard misses all events and shows "Waiting for execution to begin..."
14
+ * even though the run is already done.
15
+ *
16
+ * FIX: Store every emitted event in a per-execution replay buffer.
17
+ * When a new SSE client connects, immediately replay all past events.
18
+ * This way late-connecting dashboards see the full execution history.
19
+ */
20
+ class SSEManager {
21
+ constructor() {
22
+ this.clients = new Map();
23
+ // ✅ Replay buffer — stores all events per execution
24
+ this.eventBuffer = new Map();
25
+ // Max events to buffer per execution (prevents memory leak)
26
+ this.MAX_BUFFER = 500;
27
+ // How long to keep buffer after execution completes (ms)
28
+ this.BUFFER_TTL = 2 * 60 * 60 * 1000; // 2 hours
29
+ }
30
+ addClient(executionId, res) {
31
+ res.setHeader("Content-Type", "text/event-stream");
32
+ res.setHeader("Cache-Control", "no-cache");
33
+ res.setHeader("Connection", "keep-alive");
34
+ res.setHeader("X-Accel-Buffering", "no");
35
+ res.flushHeaders();
36
+ if (!this.clients.has(executionId))
37
+ this.clients.set(executionId, new Set());
38
+ this.clients.get(executionId).add(res);
39
+ log.info(`SSE client connected: ${executionId}`);
40
+ // ✅ REPLAY: Send all past events immediately to this late-joining client
41
+ const pastEvents = this.eventBuffer.get(executionId) ?? [];
42
+ if (pastEvents.length > 0) {
43
+ log.info(`Replaying ${pastEvents.length} past events to late client: ${executionId}`);
44
+ pastEvents.forEach((ev) => this.sendToOne(res, ev));
45
+ }
46
+ else {
47
+ // Only send connect message if no replay (otherwise connect msg mid-replay is confusing)
48
+ this.sendToOne(res, {
49
+ type: "log", executionId,
50
+ timestamp: new Date().toISOString(),
51
+ data: { message: "Connected to execution stream" },
52
+ });
53
+ }
54
+ // Heartbeat
55
+ const hb = setInterval(() => {
56
+ if (!res.writableEnded)
57
+ res.write(": heartbeat\n\n");
58
+ else
59
+ clearInterval(hb);
60
+ }, 20000);
61
+ res.on("close", () => {
62
+ clearInterval(hb);
63
+ this.removeClient(executionId, res);
64
+ });
65
+ }
66
+ emit(executionId, type, data) {
67
+ const event = {
68
+ type, executionId,
69
+ timestamp: new Date().toISOString(),
70
+ data,
71
+ };
72
+ // ✅ Buffer event for replay
73
+ if (!this.eventBuffer.has(executionId)) {
74
+ this.eventBuffer.set(executionId, []);
75
+ }
76
+ const buf = this.eventBuffer.get(executionId);
77
+ if (buf.length < this.MAX_BUFFER)
78
+ buf.push(event);
79
+ // Broadcast to all currently connected clients
80
+ const clients = this.clients.get(executionId);
81
+ if (clients && clients.size > 0) {
82
+ clients.forEach((res) => this.sendToOne(res, event));
83
+ }
84
+ if (type === "execution_complete") {
85
+ // Clean up connections, keep buffer for replay
86
+ setTimeout(() => this.clients.delete(executionId), 5000);
87
+ // Clean up buffer after TTL
88
+ setTimeout(() => this.eventBuffer.delete(executionId), this.BUFFER_TTL);
89
+ }
90
+ }
91
+ /** Broadcast to ALL connections (e.g. queue updates, global health) */
92
+ broadcast(type, data) {
93
+ const event = {
94
+ type, executionId: "global",
95
+ timestamp: new Date().toISOString(),
96
+ data,
97
+ };
98
+ this.clients.forEach((clients) => clients.forEach((res) => this.sendToOne(res, event)));
99
+ }
100
+ /** Replay buffer for a given executionId (used by status endpoint) */
101
+ getBuffer(executionId) {
102
+ return this.eventBuffer.get(executionId) ?? [];
103
+ }
104
+ hasClients(executionId) {
105
+ return (this.clients.get(executionId)?.size ?? 0) > 0;
106
+ }
107
+ sendToOne(res, event) {
108
+ try {
109
+ if (!res.writableEnded)
110
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
111
+ }
112
+ catch { /* client gone */ }
113
+ }
114
+ removeClient(executionId, res) {
115
+ this.clients.get(executionId)?.delete(res);
116
+ if (this.clients.get(executionId)?.size === 0)
117
+ this.clients.delete(executionId);
118
+ }
119
+ }
120
+ exports.SSEManager = SSEManager;
121
+ exports.sseManager = new SSEManager();
122
+ //# sourceMappingURL=sseManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sseManager.js","sourceRoot":"","sources":["../../src/utils/sseManager.ts"],"names":[],"mappings":";;;AAEA,qCAAkC;AAElC,MAAM,GAAG,GAAG,eAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;AAExC;;;;;;;;;;;;;GAaG;AACH,MAAa,UAAU;IAAvB;QACU,YAAO,GAA+B,IAAI,GAAG,EAAE,CAAC;QACxD,oDAAoD;QAC5C,gBAAW,GAA4B,IAAI,GAAG,EAAE,CAAC;QACzD,4DAA4D;QAC3C,eAAU,GAAI,GAAG,CAAC;QACnC,yDAAyD;QACxC,eAAU,GAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,UAAU;IAiG/D,CAAC;IA/FC,SAAS,CAAC,WAAmB,EAAE,GAAa;QAC1C,GAAG,CAAC,SAAS,CAAC,cAAc,EAAO,mBAAmB,CAAC,CAAC;QACxD,GAAG,CAAC,SAAS,CAAC,eAAe,EAAM,UAAU,CAAC,CAAC;QAC/C,GAAG,CAAC,SAAS,CAAC,YAAY,EAAS,YAAY,CAAC,CAAC;QACjD,GAAG,CAAC,SAAS,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;QACzC,GAAG,CAAC,YAAY,EAAE,CAAC;QAEnB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC;QAC7E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAExC,GAAG,CAAC,IAAI,CAAC,yBAAyB,WAAW,EAAE,CAAC,CAAC;QAEjD,yEAAyE;QACzE,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QAC3D,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,GAAG,CAAC,IAAI,CAAC,aAAa,UAAU,CAAC,MAAM,gCAAgC,WAAW,EAAE,CAAC,CAAC;YACtF,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QACtD,CAAC;aAAM,CAAC;YACN,yFAAyF;YACzF,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE;gBAClB,IAAI,EAAE,KAAK,EAAE,WAAW;gBACxB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,IAAI,EAAE,EAAE,OAAO,EAAE,+BAA+B,EAAE;aACnD,CAAC,CAAC;QACL,CAAC;QAED,YAAY;QACZ,MAAM,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE;YAC1B,IAAI,CAAC,GAAG,CAAC,aAAa;gBAAE,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;;gBAChD,aAAa,CAAC,EAAE,CAAC,CAAC;QACzB,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACnB,aAAa,CAAC,EAAE,CAAC,CAAC;YAClB,IAAI,CAAC,YAAY,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,WAAmB,EAAE,IAAkB,EAAE,IAAS;QACrD,MAAM,KAAK,GAAa;YACtB,IAAI,EAAE,WAAW;YACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,IAAI;SACL,CAAC;QAEF,4BAA4B;QAC5B,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACxC,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAE,CAAC;QAC/C,IAAI,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU;YAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAElD,+CAA+C;QAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;QAC9C,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QACvD,CAAC;QAED,IAAI,IAAI,KAAK,oBAAoB,EAAE,CAAC;YAClC,+CAA+C;YAC/C,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,CAAC;YACzD,4BAA4B;YAC5B,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,uEAAuE;IACvE,SAAS,CAAC,IAAkB,EAAE,IAAS;QACrC,MAAM,KAAK,GAAa;YACtB,IAAI,EAAE,WAAW,EAAE,QAAQ;YAC3B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,IAAI;SACL,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1F,CAAC;IAED,sEAAsE;IACtE,SAAS,CAAC,WAAmB;QAC3B,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;IACjD,CAAC;IAED,UAAU,CAAC,WAAmB;QAC5B,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACxD,CAAC;IAEO,SAAS,CAAC,GAAa,EAAE,KAAe;QAC9C,IAAI,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,aAAa;gBAAE,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1E,CAAC;QAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC/B,CAAC;IAEO,YAAY,CAAC,WAAmB,EAAE,GAAa;QACrD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,IAAI,KAAK,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAClF,CAAC;CACF;AAxGD,gCAwGC;AAEY,QAAA,UAAU,GAAG,IAAI,UAAU,EAAE,CAAC"}
package/npmignore ADDED
@@ -0,0 +1,5 @@
1
+ src/
2
+ dist/
3
+ node_modules/
4
+ .git/
5
+ *.ts
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "pw-automation-framework",
3
+ "version": "2.0.1",
4
+ "description": "Enterprise Playwright Automation Framework — Parallel, Non-blocking, Self-installing",
5
+ "main": "dist/server.js",
6
+ "bin": {
7
+ "lexxit-automation-framework": "./bin/lexxit-automation-framework.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/server.js",
12
+ "dev": "ts-node-dev --respawn --transpile-only src/server.ts",
13
+ "install:browsers": "npx playwright install chromium firefox webkit",
14
+ "clean": "rimraf dist",
15
+ "postinstall": "node scripts/postinstall.js"
16
+ },
17
+ "dependencies": {
18
+ "cors": "^2.8.5",
19
+ "dotenv": "^16.3.1",
20
+ "express": "^4.18.2",
21
+ "open": "^9.1.0",
22
+ "p-limit": "^4.0.0",
23
+ "playwright": "^1.40.0",
24
+ "say": "^0.16.0",
25
+ "uuid": "^9.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/cors": "^2.8.17",
29
+ "@types/express": "^4.17.21",
30
+ "@types/node": "^20.10.0",
31
+ "@types/uuid": "^9.0.7",
32
+ "rimraf": "^5.0.5",
33
+ "ts-node-dev": "^2.0.0",
34
+ "typescript": "^5.3.2"
35
+ }
36
+ }
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * postinstall.js
5
+ * Runs automatically after: npm install -g lexxit-automation-framework
6
+ * Installs Playwright Chromium silently.
7
+ */
8
+
9
+ const { execSync } = require("child_process");
10
+
11
+ const COLORS = {
12
+ reset: "\x1b[0m",
13
+ green: "\x1b[32m",
14
+ yellow: "\x1b[33m",
15
+ red: "\x1b[31m",
16
+ cyan: "\x1b[36m",
17
+ };
18
+
19
+ function log(color, prefix, msg) {
20
+ console.log(`${color}${prefix}${COLORS.reset} ${msg}`);
21
+ }
22
+
23
+ console.log(`
24
+ ${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
25
+ lexxit-automation-framework — Post Install Setup
26
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}
27
+ `);
28
+
29
+ // ─── INSTALL PLAYWRIGHT CHROMIUM ─────────────────────────────────────────────
30
+
31
+ log(COLORS.cyan, "[1/1]", "Installing Playwright Chromium browser...");
32
+
33
+ try {
34
+ execSync("npx playwright install chromium", {
35
+ stdio: "inherit",
36
+ timeout: 120000, // 2 minutes
37
+ });
38
+
39
+ log(COLORS.green, "✔", "Playwright Chromium installed successfully.\n");
40
+ } catch (err) {
41
+ log(
42
+ COLORS.yellow,
43
+ "⚠",
44
+ "Playwright Chromium install failed. You can run it manually:"
45
+ );
46
+ console.log(" npx playwright install chromium\n");
47
+ // Don't exit(1) — don't block the npm install itself
48
+ }
49
+
50
+ console.log(`${COLORS.green}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
51
+ console.log(` Setup complete. Run: lexxit-automation-framework start`);
52
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}\n`);
package/src/app.ts ADDED
@@ -0,0 +1,27 @@
1
+ import express from "express";
2
+ import cors from "cors";
3
+ import path from "path";
4
+ import apiRoutes from "./routes/api.routes";
5
+
6
+ const app = express();
7
+
8
+ app.use(cors({ origin: "*" }));
9
+ app.use(express.json({ limit: "20mb" }));
10
+ app.use(express.urlencoded({ extended: true }));
11
+ app.use(express.static(path.join(__dirname, "public")));
12
+
13
+ // Live dashboard — serves the HTML shell; JS reads executionId from URL
14
+ app.get("/dashboard/:executionId", (_req, res) => {
15
+ res.sendFile(path.join(__dirname, "public", "dashboard.html"));
16
+ });
17
+
18
+ // Multi-execution queue monitor
19
+ app.get("/queue-monitor", (_req, res) => {
20
+ res.sendFile(path.join(__dirname, "public", "queue-monitor.html"));
21
+ });
22
+
23
+ app.use("/api", apiRoutes);
24
+
25
+ app.use((_req, res) => res.status(404).json({ error: "Not found" }));
26
+
27
+ export default app;