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,282 @@
1
+ import { Request, Response } from "express";
2
+ import { executionQueue } from "../queue/ExecutionQueue";
3
+ import { sseManager } from "../utils/sseManager";
4
+ import { formatExecutionResult, formatScriptResult } from "../utils/responseFormatter";
5
+ import { Logger } from "../utils/logger";
6
+
7
+ const log = Logger.create("ApiController");
8
+
9
+ export class ApiController {
10
+
11
+ /**
12
+ * POST /api/run-test
13
+ * ------------------
14
+ * Enqueues execution, returns 202 + executionId immediately.
15
+ * The caller opens the dashboard and subscribes to SSE for live updates.
16
+ * Call GET /api/result/:executionId when done to get the full formatted result.
17
+ */
18
+ static async runTest(req: Request, res: Response): Promise<void> {
19
+ const payload = req.body;
20
+ if (!payload?.scripts?.length) {
21
+ res.status(400).json({ error: "'scripts' array is required." });
22
+ return;
23
+ }
24
+
25
+ // Validate that steps have step_script
26
+ const missingSteps: string[] = [];
27
+ for (const script of payload.scripts) {
28
+ for (const step of (script.steps || [])) {
29
+ if (!step.step_script || step.step_script.trim() === "") {
30
+ missingSteps.push(step.step_name || "unnamed step");
31
+ }
32
+ }
33
+ }
34
+ if (missingSteps.length > 0) {
35
+ res.status(400).json({
36
+ error: "Steps are missing step_script field.",
37
+ detail: `Steps without step_script: ${missingSteps.slice(0, 5).join(", ")}`,
38
+ fix: "Call prepareSteps(uid) in your main app to resolve obj_uid → step_script before sending to the framework.",
39
+ });
40
+ return;
41
+ }
42
+
43
+ const executionId = executionQueue.enqueue(payload);
44
+ const dashboardURL = `http://localhost:${process.env.PORT || 3000}/dashboard/${executionId}`;
45
+ const resultURL = `http://localhost:${process.env.PORT || 3000}/api/result/${executionId}`;
46
+ const streamURL = `http://localhost:${process.env.PORT || 3000}/api/stream/${executionId}`;
47
+ // const videoURL = `http://localhost:${process.env.PORT || 3000}/api/video/${executionId}`;
48
+
49
+ log.info(`Enqueued: ${executionId} | scripts=${payload.scripts.length}`);
50
+
51
+ res.status(202).json({
52
+ executionId,
53
+ dashboardURL,
54
+ resultURL, // Poll this for final result
55
+ streamURL, // Subscribe to this for live events
56
+ // videoURL,
57
+ status: "queued",
58
+ message: "Execution queued. Dashboard opening. Subscribe to streamURL for live updates, or poll resultURL for final result.",
59
+ });
60
+ }
61
+
62
+ /**
63
+ * GET /api/stream/:executionId
64
+ * ----------------------------
65
+ * SSE endpoint — dashboard subscribes here for live step events.
66
+ * Replays all past events immediately for late-connecting clients.
67
+ */
68
+ static stream(req: Request, res: Response): void {
69
+ const { executionId } = req.params;
70
+ sseManager.addClient(executionId, res);
71
+ }
72
+
73
+ /**
74
+ * GET /api/result/:executionId
75
+ * ----------------------------
76
+ * Returns the FULL formatted result once execution is complete.
77
+ * Returns 202 if still running (poll until 200).
78
+ *
79
+ * Response matches the standard format:
80
+ * {
81
+ * "status": "pass|fail",
82
+ * "results": [{ "test_script_uid": "...", "status": "...", "results": [...] }]
83
+ * }
84
+ */
85
+ static getResult(req: Request, res: Response): void {
86
+ const { executionId } = req.params;
87
+ const job = executionQueue.getJob(executionId);
88
+
89
+ if (!job) {
90
+ res.status(404).json({ error: `Execution ${executionId} not found` });
91
+ return;
92
+ }
93
+
94
+ if (job.status === "queued" || job.status === "running") {
95
+ res.status(202).json({
96
+ executionId,
97
+ status: job.status,
98
+ message: "Execution still in progress",
99
+ progress: job.progress,
100
+ });
101
+ return;
102
+ }
103
+
104
+ if (job.status === "cancelled") {
105
+ res.status(200).json({
106
+ executionId,
107
+ status: "cancelled",
108
+ message: "Execution was cancelled",
109
+ });
110
+ return;
111
+ }
112
+
113
+ if (!job.result) {
114
+ res.status(500).json({
115
+ executionId,
116
+ status: "failed",
117
+ error: job.error || "Execution failed with no result",
118
+ });
119
+ return;
120
+ }
121
+
122
+ // Return formatted result matching the standard API format
123
+ res.status(200).json(formatExecutionResult(job.result));
124
+ // console.log(formatExecutionResult(job.result));
125
+ }
126
+
127
+ /**
128
+ * GET /api/status/:executionId
129
+ * ----------------------------
130
+ * Lightweight status check (progress only, no full results).
131
+ */
132
+ static getStatus(req: Request, res: Response): void {
133
+ const { executionId } = req.params;
134
+ const job = executionQueue.getJob(executionId);
135
+
136
+ if (!job) {
137
+ res.status(404).json({ error: `Execution ${executionId} not found` });
138
+ return;
139
+ }
140
+
141
+ res.json({
142
+ executionId: job.executionId,
143
+ status: job.status,
144
+ queuedAt: job.queuedAt,
145
+ startedAt: job.startedAt,
146
+ completedAt: job.completedAt,
147
+ cancelledAt: job.cancelledAt,
148
+ progress: job.progress,
149
+ cancelRequested: job.cancelRequested,
150
+ error: job.error,
151
+ });
152
+ }
153
+
154
+ /**
155
+ * DELETE /api/cancel/:executionId
156
+ */
157
+ static cancelExecution(req: Request, res: Response): void {
158
+ const { executionId } = req.params;
159
+ const result = executionQueue.cancel(executionId);
160
+ res.status(result.success ? 200 : 400).json({ executionId, ...result });
161
+ }
162
+
163
+ /**
164
+ * DELETE /api/cancel-all
165
+ */
166
+ static cancelAll(_req: Request, res: Response): void {
167
+ res.json(executionQueue.cancelAll());
168
+ }
169
+
170
+ /**
171
+ * GET /api/queue
172
+ */
173
+ static getQueue(_req: Request, res: Response): void {
174
+ res.json(executionQueue.getQueueStats());
175
+ }
176
+
177
+ /**
178
+ * GET /api/health
179
+ */
180
+ static health(_req: Request, res: Response): void {
181
+ res.json({
182
+ status: "ok",
183
+ version: "2.0.0",
184
+ ts: new Date().toISOString(),
185
+ queue: executionQueue.getQueueStats(),
186
+ });
187
+ }
188
+
189
+ /**
190
+ * GET /api/actions
191
+ */
192
+ static listActions(_req: Request, res: Response): void {
193
+ res.json({ total: ALL_ACTIONS.length, actions: ALL_ACTIONS });
194
+ }
195
+ }
196
+
197
+ const ALL_ACTIONS = [
198
+ // Browser
199
+ { name: "openBrowser", category: "browser", example: "tSetup.openBrowser('chrome')" },
200
+ { name: "closeBrowser", category: "browser", example: "tSetup.closeBrowser()" },
201
+ { name: "navigateToURL", category: "browser", example: "tSetup.navigateToURL('https://example.com')" },
202
+ { name: "navigateBack", category: "browser", example: "tSetup.navigateBack()" },
203
+ { name: "navigateForward", category: "browser", example: "tSetup.navigateForward()" },
204
+ { name: "refreshPage", category: "browser", example: "tSetup.refreshPage()" },
205
+ { name: "maximizeWindow", category: "browser", example: "tSetup.maximizeWindow()" },
206
+ { name: "setWindowSize", category: "browser", example: "tSetup.setWindowSize('1920', '1080')" },
207
+ { name: "switchToTab", category: "browser", example: "tSetup.switchToTab('1')" },
208
+ { name: "openNewTab", category: "browser", example: "tSetup.openNewTab('https://example.com')" },
209
+ { name: "closeCurrentTab", category: "browser", example: "tSetup.closeCurrentTab()" },
210
+ { name: "acceptAlert", category: "browser", example: "tSetup.acceptAlert()" },
211
+ { name: "dismissAlert", category: "browser", example: "tSetup.dismissAlert()" },
212
+ { name: "getAlertText", category: "browser", example: "tSetup.getAlertText('storeKey')" },
213
+ { name: "switchToFrame", category: "browser", example: "tSetup.switchToFrame('frameName')" },
214
+ { name: "executeScript", category: "browser", example: "tSetup.executeScript('return document.title')" },
215
+ { name: "scrollToBottom", category: "browser", example: "tSetup.scrollToBottom()" },
216
+ { name: "scrollToTop", category: "browser", example: "tSetup.scrollToTop()" },
217
+ { name: "deleteAllCookies", category: "browser", example: "tSetup.deleteAllCookies()" },
218
+ { name: "clearLocalStorage", category: "browser", example: "tSetup.clearLocalStorage()" },
219
+ { name: "clearSessionStorage", category: "browser", example: "tSetup.clearSessionStorage()" },
220
+ { name: "takeScreenshot", category: "browser", example: "tSetup.takeScreenshot()" },
221
+ { name: "getTitle", category: "browser", example: "tSetup.getTitle('storeKey')" },
222
+ { name: "verifyTitle", category: "browser", example: "tSetup.verifyTitle('Expected Title')" },
223
+ { name: "verifyPartialTitle", category: "browser", example: "tSetup.verifyPartialTitle('Expected')" },
224
+ { name: "getCurrentURL", category: "browser", example: "tSetup.getCurrentURL()" },
225
+ // Input
226
+ { name: "enterText", category: "input", example: "tSetup.enterText('xpath', '//input[...]', 'value')" },
227
+ { name: "typeText", category: "input", example: "tSetup.typeText('xpath', '//input[...]', 'value', '50')" },
228
+ { name: "clearText", category: "input", example: "tSetup.clearText('xpath', '//input[...]')" },
229
+ { name: "appendText", category: "input", example: "tSetup.appendText('xpath', '//input[...]', ' extra')" },
230
+ { name: "getInputValue", category: "input", example: "tSetup.getInputValue('xpath', '//input', 'storeKey')" },
231
+ { name: "setInputValue", category: "input", example: "tSetup.setInputValue('xpath', '//input', 'value')" },
232
+ { name: "uploadFile", category: "input", example: "tSetup.uploadFile('xpath', '//input[@type=\"file\"]', '/path/file.pdf')" },
233
+ // Click
234
+ { name: "clickElement", category: "click", example: "tSetup.clickElement('xpath', '//button[...]')" },
235
+ { name: "doubleClick", category: "click", example: "tSetup.doubleClick('xpath', '//div[...]')" },
236
+ { name: "rightClick", category: "click", example: "tSetup.rightClick('xpath', '//div[...]')" },
237
+ { name: "hover", category: "click", example: "tSetup.hover('xpath', '//div[...]')" },
238
+ { name: "clickByJS", category: "click", example: "tSetup.clickByJS('xpath', '//button[...]')" },
239
+ { name: "clickIfPresent", category: "click", example: "tSetup.clickIfPresent('xpath', '//button[...]')" },
240
+ { name: "dragAndDrop", category: "click", example: "tSetup.dragAndDrop('xpath', '//src', 'xpath', '//target')" },
241
+ { name: "clickByText", category: "click", example: "tSetup.clickByText('Sign In')" },
242
+ { name: "clickNthElement", category: "click", example: "tSetup.clickNthElement('xpath', '//li', '2')" },
243
+ { name: "scrollAndClick", category: "click", example: "tSetup.scrollAndClick('xpath', '//button[...]')" },
244
+ // Dropdown
245
+ { name: "selectDropdown", category: "dropdown", example: "tSetup.selectDropdown('xpath', '//select', 'Option Label')" },
246
+ { name: "selectByIndex", category: "dropdown", example: "tSetup.selectByIndex('xpath', '//select', '2')" },
247
+ { name: "selectByValue", category: "dropdown", example: "tSetup.selectByValue('xpath', '//select', 'optValue')" },
248
+ { name: "selectCustomDropdown", category: "dropdown", example: "tSetup.selectCustomDropdown('xpath', '//div.dd', 'xpath', '//li')" },
249
+ { name: "selectAutocomplete", category: "dropdown", example: "tSetup.selectAutocomplete('xpath', '//input', 'New York', 'xpath', '//li')" },
250
+ // Assertion
251
+ { name: "verifyText", category: "assertion", example: "tSetup.verifyText('xpath', '//h1', 'Welcome')" },
252
+ { name: "verifyTextContains", category: "assertion", example: "tSetup.verifyTextContains('xpath', '//p', 'success')" },
253
+ { name: "verifyVisible", category: "assertion", example: "tSetup.verifyVisible('xpath', '//div[...]')" },
254
+ { name: "verifyHidden", category: "assertion", example: "tSetup.verifyHidden('xpath', '//div[...]')" },
255
+ { name: "verifyEnabled", category: "assertion", example: "tSetup.verifyEnabled('xpath', '//button[...]')" },
256
+ { name: "verifyDisabled", category: "assertion", example: "tSetup.verifyDisabled('xpath', '//button[...]')" },
257
+ { name: "verifyURL", category: "assertion", example: "tSetup.verifyURL('https://example.com/dashboard')" },
258
+ { name: "verifyPartialURL", category: "assertion", example: "tSetup.verifyPartialURL('dashboard')" },
259
+ { name: "verifyElementCount", category: "assertion", example: "tSetup.verifyElementCount('xpath', '//li', '5')" },
260
+ { name: "verifyAttribute", category: "assertion", example: "tSetup.verifyAttribute('xpath', '//input', 'placeholder', 'Email')" },
261
+ { name: "getText", category: "assertion", example: "tSetup.getText('xpath', '//h1', 'storeKey')" },
262
+ // Wait
263
+ { name: "waitForElement", category: "wait", example: "tSetup.waitForElement('xpath', '//div', '10000')" },
264
+ { name: "waitForElementHidden", category: "wait", example: "tSetup.waitForElementHidden('xpath', '//div[@id=\"loader\"]', '10000')" },
265
+ { name: "waitForURL", category: "wait", example: "tSetup.waitForURL('dashboard', '10000')" },
266
+ { name: "waitForPageLoad", category: "wait", example: "tSetup.waitForPageLoad('10000')" },
267
+ { name: "waitForNetworkIdle", category: "wait", example: "tSetup.waitForNetworkIdle('10000')" },
268
+ { name: "sleep", category: "wait", example: "tSetup.sleep('2000')" },
269
+ // Keyboard
270
+ { name: "pressKey", category: "keyboard", example: "tSetup.pressKey('Enter')" },
271
+ { name: "pressKeyOn", category: "keyboard", example: "tSetup.pressKeyOn('xpath', '//input', 'Enter')" },
272
+ { name: "keyCombo", category: "keyboard", example: "tSetup.keyCombo('Control+A')" },
273
+ // Checkbox
274
+ { name: "checkCheckbox", category: "checkbox", example: "tSetup.checkCheckbox('xpath', '//input[@type=\"checkbox\"]')" },
275
+ { name: "uncheckCheckbox", category: "checkbox", example: "tSetup.uncheckCheckbox('xpath', '//input[@type=\"checkbox\"]')" },
276
+ { name: "selectRadio", category: "checkbox", example: "tSetup.selectRadio('xpath', '//input[@type=\"radio\"][@value=\"opt1\"]')" },
277
+ // Table
278
+ { name: "getTableRowCount", category: "table", example: "tSetup.getTableRowCount('xpath', '//table/tbody/tr', 'rowCount')" },
279
+ { name: "verifyTableCell", category: "table", example: "tSetup.verifyTableCell('xpath', '//table/tbody/tr[1]/td[2]', 'Expected')" },
280
+ { name: "clickTableRow", category: "table", example: "tSetup.clickTableRow('xpath', '//table/tbody/tr', '3')" },
281
+ { name: "runCustomCode", category: "customcode", example: "tSetup.runCustomCode('const result = \"LN\" + Math.floor(Math.random()*1000);', 'result', '//input[@id=\"ref\"]')" },,
282
+ ];