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.
- package/README.md +93 -0
- package/bin/lexxit-automation-framework.js +427 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +26 -0
- package/dist/app.js.map +1 -0
- package/dist/controllers/controller.d.ts +57 -0
- package/dist/controllers/controller.js +263 -0
- package/dist/controllers/controller.js.map +1 -0
- package/dist/core/BrowserManager.d.ts +46 -0
- package/dist/core/BrowserManager.js +377 -0
- package/dist/core/BrowserManager.js.map +1 -0
- package/dist/core/PlaywrightEngine.d.ts +16 -0
- package/dist/core/PlaywrightEngine.js +246 -0
- package/dist/core/PlaywrightEngine.js.map +1 -0
- package/dist/core/ScreenshotManager.d.ts +10 -0
- package/dist/core/ScreenshotManager.js +28 -0
- package/dist/core/ScreenshotManager.js.map +1 -0
- package/dist/core/TestData.d.ts +12 -0
- package/dist/core/TestData.js +29 -0
- package/dist/core/TestData.js.map +1 -0
- package/dist/core/TestExecutor.d.ts +16 -0
- package/dist/core/TestExecutor.js +355 -0
- package/dist/core/TestExecutor.js.map +1 -0
- package/dist/core/handlers/AllHandlers.d.ts +116 -0
- package/dist/core/handlers/AllHandlers.js +648 -0
- package/dist/core/handlers/AllHandlers.js.map +1 -0
- package/dist/core/handlers/BaseHandler.d.ts +16 -0
- package/dist/core/handlers/BaseHandler.js +27 -0
- package/dist/core/handlers/BaseHandler.js.map +1 -0
- package/dist/core/handlers/ClickHandler.d.ts +34 -0
- package/dist/core/handlers/ClickHandler.js +359 -0
- package/dist/core/handlers/ClickHandler.js.map +1 -0
- package/dist/core/handlers/CustomCodeHandler.d.ts +35 -0
- package/dist/core/handlers/CustomCodeHandler.js +102 -0
- package/dist/core/handlers/CustomCodeHandler.js.map +1 -0
- package/dist/core/handlers/DropdownHandler.d.ts +43 -0
- package/dist/core/handlers/DropdownHandler.js +304 -0
- package/dist/core/handlers/DropdownHandler.js.map +1 -0
- package/dist/core/handlers/InputHandler.d.ts +24 -0
- package/dist/core/handlers/InputHandler.js +197 -0
- package/dist/core/handlers/InputHandler.js.map +1 -0
- package/dist/core/registry/ActionRegistry.d.ts +8 -0
- package/dist/core/registry/ActionRegistry.js +35 -0
- package/dist/core/registry/ActionRegistry.js.map +1 -0
- package/dist/installer/frameworkLauncher.d.ts +31 -0
- package/dist/installer/frameworkLauncher.js +198 -0
- package/dist/installer/frameworkLauncher.js.map +1 -0
- package/dist/queue/ExecutionQueue.d.ts +52 -0
- package/dist/queue/ExecutionQueue.js +175 -0
- package/dist/queue/ExecutionQueue.js.map +1 -0
- package/dist/routes/api.routes.d.ts +2 -0
- package/dist/routes/api.routes.js +16 -0
- package/dist/routes/api.routes.js.map +1 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +30 -0
- package/dist/server.js.map +1 -0
- package/dist/types/types.d.ts +135 -0
- package/dist/types/types.js +4 -0
- package/dist/types/types.js.map +1 -0
- package/dist/utils/elementHighlight.d.ts +35 -0
- package/dist/utils/elementHighlight.js +136 -0
- package/dist/utils/elementHighlight.js.map +1 -0
- package/dist/utils/locatorHelper.d.ts +7 -0
- package/dist/utils/locatorHelper.js +53 -0
- package/dist/utils/locatorHelper.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +35 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/response.d.ts +4 -0
- package/dist/utils/response.js +25 -0
- package/dist/utils/response.js.map +1 -0
- package/dist/utils/responseFormatter.d.ts +78 -0
- package/dist/utils/responseFormatter.js +123 -0
- package/dist/utils/responseFormatter.js.map +1 -0
- package/dist/utils/sseManager.d.ts +32 -0
- package/dist/utils/sseManager.js +122 -0
- package/dist/utils/sseManager.js.map +1 -0
- package/lexxit-automation-framework-2.0.0.tgz +0 -0
- package/npmignore +5 -0
- package/package.json +36 -0
- package/scripts/postinstall.js +52 -0
- package/src/app.ts +27 -0
- package/src/controllers/controller.ts +282 -0
- package/src/core/BrowserManager.ts +398 -0
- package/src/core/PlaywrightEngine.ts +371 -0
- package/src/core/ScreenshotManager.ts +25 -0
- package/src/core/TestData.ts +25 -0
- package/src/core/TestExecutor.ts +436 -0
- package/src/core/handlers/AllHandlers.ts +626 -0
- package/src/core/handlers/BaseHandler.ts +41 -0
- package/src/core/handlers/ClickHandler.ts +482 -0
- package/src/core/handlers/CustomCodeHandler.ts +123 -0
- package/src/core/handlers/DropdownHandler.ts +438 -0
- package/src/core/handlers/InputHandler.ts +192 -0
- package/src/core/registry/ActionRegistry.ts +31 -0
- package/src/installer/frameworkLauncher.ts +242 -0
- package/src/installer/install.sh +107 -0
- package/src/public/dashboard.html +540 -0
- package/src/public/queue-monitor.html +190 -0
- package/src/queue/ExecutionQueue.ts +200 -0
- package/src/routes/api.routes.ts +16 -0
- package/src/server.ts +29 -0
- package/src/types/types.ts +169 -0
- package/src/utils/elementHighlight.ts +174 -0
- package/src/utils/locatorHelper.ts +49 -0
- package/src/utils/logger.ts +40 -0
- package/src/utils/response.ts +27 -0
- package/src/utils/responseFormatter.ts +167 -0
- package/src/utils/sseManager.ts +127 -0
- package/tsconfig.json +18 -0
- package/videos/fb1b94b6-6639-4c9a-82bb-63572606f403/page@5bd5c6c8b62baa700e9810cdd64f5c49.webm +0 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { Page } from "playwright";
|
|
2
|
+
import { ActionResponse, TestCaseConfig } from "../../types/types";
|
|
3
|
+
import { pass, fail, failElementNotFound } from "../../utils/response";
|
|
4
|
+
import { buildLocatorString } from "../../utils/locatorHelper";
|
|
5
|
+
import { BaseHandler } from "./BaseHandler";
|
|
6
|
+
|
|
7
|
+
export class ClickHandler extends BaseHandler {
|
|
8
|
+
constructor(tConfig: TestCaseConfig, page: Page) { super(tConfig, page); }
|
|
9
|
+
|
|
10
|
+
getActions() {
|
|
11
|
+
return {
|
|
12
|
+
clickelement: this.clickElement.bind(this),
|
|
13
|
+
selectRadioButton: this.selectRadioButton.bind(this),
|
|
14
|
+
check_checkbox: this.check_checkbox.bind(this),
|
|
15
|
+
doubleclick: this.doubleClick.bind(this),
|
|
16
|
+
rightclick: this.rightClick.bind(this),
|
|
17
|
+
hover: this.hover.bind(this),
|
|
18
|
+
clickbyjs: this.clickByJS.bind(this),
|
|
19
|
+
clickifpresent: this.clickIfPresent.bind(this),
|
|
20
|
+
draganddrop: this.dragAndDrop.bind(this),
|
|
21
|
+
clickandwait: this.clickAndWait.bind(this),
|
|
22
|
+
clickbytext: this.clickByText.bind(this),
|
|
23
|
+
clicknthelement: this.clickNthElement.bind(this),
|
|
24
|
+
scrollandclick: this.scrollAndClick.bind(this),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async clickElement(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
29
|
+
const [strategy, locator] = args;
|
|
30
|
+
const displayLabel = slabel || "unknown";
|
|
31
|
+
|
|
32
|
+
if (!strategy || !locator) {
|
|
33
|
+
return fail("clickElement needs: strategy, locator", "Missing args");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ls = buildLocatorString(strategy, locator);
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const el = this.page.locator(ls);
|
|
40
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
41
|
+
|
|
42
|
+
await this.highlight(ls, "click", `Clicking`);
|
|
43
|
+
|
|
44
|
+
await el.click({ timeout: this.timeout });
|
|
45
|
+
|
|
46
|
+
await this.unhighlight();
|
|
47
|
+
|
|
48
|
+
const sc = await this.shots.onPass(this.page);
|
|
49
|
+
|
|
50
|
+
return pass(
|
|
51
|
+
`Clicked ${displayLabel}`,
|
|
52
|
+
sc,
|
|
53
|
+
{ expected_result: `"${displayLabel}" clicked successfully` }
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
} catch (error: any) {
|
|
57
|
+
await this.unhighlight();
|
|
58
|
+
const sc = await this.shots.onFail(this.page);
|
|
59
|
+
|
|
60
|
+
const msg = error.message.toLowerCase();
|
|
61
|
+
if (msg.includes("waiting for locator") || msg.includes("timeout") || msg.includes("no element")) {
|
|
62
|
+
return failElementNotFound(displayLabel, sc);
|
|
63
|
+
}
|
|
64
|
+
return fail(`Failed click on "${displayLabel}" — ${error.message}`, undefined, sc, { expected_result: `"${displayLabel}" clicked successfully` });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// async clickElement(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
68
|
+
// const [strategy, locator] = args;
|
|
69
|
+
// const displayLabel = slabel || "unknown";
|
|
70
|
+
|
|
71
|
+
// if (!strategy || !locator) {
|
|
72
|
+
// return fail("clickElement needs: strategy, locator", "Missing args");
|
|
73
|
+
// }
|
|
74
|
+
|
|
75
|
+
// const ls = buildLocatorString(strategy, locator);
|
|
76
|
+
|
|
77
|
+
// try {
|
|
78
|
+
// const el = this.page.locator(ls);
|
|
79
|
+
// await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
80
|
+
|
|
81
|
+
// await this.highlight(ls, "click", "Clicking");
|
|
82
|
+
|
|
83
|
+
// await el.click({ timeout: this.timeout });
|
|
84
|
+
|
|
85
|
+
// await this.unhighlight();
|
|
86
|
+
|
|
87
|
+
// const sc = await this.shots.onPass(this.page);
|
|
88
|
+
|
|
89
|
+
// return pass(
|
|
90
|
+
// `Clicked ${displayLabel}`,
|
|
91
|
+
// sc,
|
|
92
|
+
// { expected_result: `"${displayLabel}" clicked successfully` }
|
|
93
|
+
// );
|
|
94
|
+
|
|
95
|
+
// } catch (error: any) {
|
|
96
|
+
// await this.unhighlight();
|
|
97
|
+
// const sc = await this.shots.onFail(this.page);
|
|
98
|
+
|
|
99
|
+
// const msg = error.message.toLowerCase();
|
|
100
|
+
// let errorMsg = "";
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
// if (msg.includes("waiting for locator") || msg.includes("no element")) {
|
|
104
|
+
// errorMsg = `"${displayLabel}" not found`;
|
|
105
|
+
|
|
106
|
+
// } else if (msg.includes("not visible")) {
|
|
107
|
+
// errorMsg = `"${displayLabel}" is not visible`;
|
|
108
|
+
|
|
109
|
+
// } else if (msg.includes("not enabled")) {
|
|
110
|
+
// errorMsg = `"${displayLabel}" is disabled`;
|
|
111
|
+
|
|
112
|
+
// } else if (msg.includes("not attached")) {
|
|
113
|
+
// errorMsg = `"${displayLabel}" is not attached to DOM`;
|
|
114
|
+
|
|
115
|
+
// } else if (msg.includes("strict mode")) {
|
|
116
|
+
// errorMsg = `Multiple elements found for "${displayLabel}"`;
|
|
117
|
+
|
|
118
|
+
// } else if (msg.includes("timeout")) {
|
|
119
|
+
// errorMsg = `Timeout while clicking "${displayLabel}"`;
|
|
120
|
+
|
|
121
|
+
// } else {
|
|
122
|
+
// errorMsg = `Failed click on "${displayLabel}" — ${error.message}`;
|
|
123
|
+
// }
|
|
124
|
+
|
|
125
|
+
// return fail(
|
|
126
|
+
// errorMsg,
|
|
127
|
+
// undefined,
|
|
128
|
+
// sc,
|
|
129
|
+
// { expected_result: `"${displayLabel}" clicked successfully` }
|
|
130
|
+
// );
|
|
131
|
+
// }
|
|
132
|
+
// }
|
|
133
|
+
async selectRadioButton(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
134
|
+
const [strategy, locator] = args;
|
|
135
|
+
const displayLabel = slabel || "unknown";
|
|
136
|
+
|
|
137
|
+
if (!strategy || !locator) {
|
|
138
|
+
return fail("selectRadioButton needs: strategy, locator", "Missing args");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const ls = buildLocatorString(strategy, locator);
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const el = this.page.locator(ls);
|
|
145
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
146
|
+
|
|
147
|
+
await this.highlight(ls, "checkbox", "Selecting radio button");
|
|
148
|
+
|
|
149
|
+
await el.check({ timeout: this.timeout });
|
|
150
|
+
|
|
151
|
+
await this.unhighlight();
|
|
152
|
+
|
|
153
|
+
const sc = await this.shots.onPass(this.page);
|
|
154
|
+
|
|
155
|
+
return pass(
|
|
156
|
+
`Selected radio button ${displayLabel}`,
|
|
157
|
+
sc,
|
|
158
|
+
{ expected_result: `"${displayLabel}" radio button selected successfully` }
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
} catch (error: any) {
|
|
162
|
+
await this.unhighlight();
|
|
163
|
+
const sc = await this.shots.onFail(this.page);
|
|
164
|
+
|
|
165
|
+
// ✅ YOUR LOGIC (as requested)
|
|
166
|
+
const isNotFound = /timeout|waiting for locator|not visible|not found/i.test(error.message);
|
|
167
|
+
|
|
168
|
+
const errorMsg = isNotFound
|
|
169
|
+
? `"${displayLabel}" not found`
|
|
170
|
+
: `Failed selectRadioButton "${displayLabel}" — ${error.message}`;
|
|
171
|
+
|
|
172
|
+
return fail(
|
|
173
|
+
errorMsg,
|
|
174
|
+
undefined,
|
|
175
|
+
sc,
|
|
176
|
+
{ expected_result: `"${displayLabel}" radio button selected successfully` }
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
async check_checkbox(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
181
|
+
const [strategy, locator] = args;
|
|
182
|
+
const displayLabel = slabel || "unknown";
|
|
183
|
+
|
|
184
|
+
if (!strategy || !locator) {
|
|
185
|
+
return fail("check_checkbox needs: strategy, locator", "Missing args");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const ls = buildLocatorString(strategy, locator);
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const el = this.page.locator(ls);
|
|
192
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
193
|
+
|
|
194
|
+
await this.highlight(ls, "checkbox", "Checking checkbox");
|
|
195
|
+
|
|
196
|
+
await el.check({ timeout: this.timeout });
|
|
197
|
+
|
|
198
|
+
await this.unhighlight();
|
|
199
|
+
|
|
200
|
+
const sc = await this.shots.onPass(this.page);
|
|
201
|
+
|
|
202
|
+
return pass(
|
|
203
|
+
`Checked checkbox ${displayLabel}`,
|
|
204
|
+
sc,
|
|
205
|
+
{ expected_result: `"${displayLabel}" checkbox checked successfully` }
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
} catch (error: any) {
|
|
209
|
+
await this.unhighlight();
|
|
210
|
+
const sc = await this.shots.onFail(this.page);
|
|
211
|
+
|
|
212
|
+
// ✅ SAME LOGIC (as you asked)
|
|
213
|
+
const isNotFound = /timeout|waiting for locator|not visible|not found/i.test(error.message);
|
|
214
|
+
|
|
215
|
+
const errorMsg = isNotFound
|
|
216
|
+
? `"${displayLabel}" not found`
|
|
217
|
+
: `Failed check_checkbox "${displayLabel}" — ${error.message}`;
|
|
218
|
+
|
|
219
|
+
return fail(
|
|
220
|
+
errorMsg,
|
|
221
|
+
undefined,
|
|
222
|
+
sc,
|
|
223
|
+
{ expected_result: `"${displayLabel}" checkbox checked successfully` }
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async doubleClick(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
228
|
+
const [strategy, locator] = args;
|
|
229
|
+
const displayLabel = slabel || "unknown";
|
|
230
|
+
|
|
231
|
+
if (!strategy || !locator) {
|
|
232
|
+
return fail("doubleClick needs: strategy, locator", "Missing args");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const ls = buildLocatorString(strategy, locator);
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const el = this.page.locator(ls);
|
|
239
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
240
|
+
|
|
241
|
+
await this.highlight(ls, "click", "Double clicking");
|
|
242
|
+
|
|
243
|
+
await el.dblclick({ timeout: this.timeout });
|
|
244
|
+
|
|
245
|
+
await this.unhighlight();
|
|
246
|
+
|
|
247
|
+
const sc = await this.shots.onPass(this.page);
|
|
248
|
+
|
|
249
|
+
return pass(`Double-clicked ${displayLabel}`, sc);
|
|
250
|
+
|
|
251
|
+
} catch (error: any) {
|
|
252
|
+
await this.unhighlight();
|
|
253
|
+
const sc = await this.shots.onFail(this.page);
|
|
254
|
+
|
|
255
|
+
const isNotFound = /timeout|waiting for locator|not visible|not found/i.test(error.message);
|
|
256
|
+
|
|
257
|
+
return fail(
|
|
258
|
+
isNotFound ? `"${displayLabel}" not found` : `Failed doubleClick ${displayLabel} — ${error.message}`,
|
|
259
|
+
undefined,
|
|
260
|
+
sc
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async rightClick(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
266
|
+
const [strategy, locator] = args;
|
|
267
|
+
const displayLabel = slabel || "unknown";
|
|
268
|
+
|
|
269
|
+
if (!strategy || !locator) {
|
|
270
|
+
return fail("rightClick needs: strategy, locator", "Missing args");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const ls = buildLocatorString(strategy, locator);
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const el = this.page.locator(ls);
|
|
277
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
278
|
+
|
|
279
|
+
await this.highlight(ls, "click", "Right clicking");
|
|
280
|
+
|
|
281
|
+
await el.click({ button: "right" });
|
|
282
|
+
|
|
283
|
+
await this.unhighlight();
|
|
284
|
+
|
|
285
|
+
const sc = await this.shots.onPass(this.page);
|
|
286
|
+
|
|
287
|
+
return pass(`Right-clicked ${displayLabel}`, sc);
|
|
288
|
+
|
|
289
|
+
} catch (error: any) {
|
|
290
|
+
await this.unhighlight();
|
|
291
|
+
const sc = await this.shots.onFail(this.page);
|
|
292
|
+
|
|
293
|
+
const isNotFound = /timeout|waiting for locator|not visible|not found/i.test(error.message);
|
|
294
|
+
|
|
295
|
+
return fail(
|
|
296
|
+
isNotFound ? `"${displayLabel}" not found` : `Failed rightClick ${displayLabel} — ${error.message}`,
|
|
297
|
+
undefined,
|
|
298
|
+
sc
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async hover(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
303
|
+
const [strategy, locator] = args;
|
|
304
|
+
const displayLabel = slabel || "unknown";
|
|
305
|
+
|
|
306
|
+
if (!strategy || !locator) {
|
|
307
|
+
return fail("hover needs: strategy, locator", "Missing args");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const ls = buildLocatorString(strategy, locator);
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
const el = this.page.locator(ls);
|
|
314
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
315
|
+
|
|
316
|
+
await this.highlight(ls, "click", "Hovering");
|
|
317
|
+
|
|
318
|
+
await el.hover();
|
|
319
|
+
|
|
320
|
+
await this.unhighlight();
|
|
321
|
+
|
|
322
|
+
const sc = await this.shots.onPass(this.page);
|
|
323
|
+
|
|
324
|
+
return pass(`Hovered ${displayLabel}`, sc);
|
|
325
|
+
|
|
326
|
+
} catch (error: any) {
|
|
327
|
+
await this.unhighlight();
|
|
328
|
+
const sc = await this.shots.onFail(this.page);
|
|
329
|
+
|
|
330
|
+
const isNotFound = /timeout|waiting for locator|not visible|not found/i.test(error.message);
|
|
331
|
+
|
|
332
|
+
return fail(
|
|
333
|
+
isNotFound ? `"${displayLabel}" not found` : `Failed hover ${displayLabel} — ${error.message}`,
|
|
334
|
+
undefined,
|
|
335
|
+
sc
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async clickByJS(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
341
|
+
const [strategy, locator] = args;
|
|
342
|
+
const displayLabel = slabel || "unknown";
|
|
343
|
+
|
|
344
|
+
if (!strategy || !locator) {
|
|
345
|
+
return fail("clickByJS needs: strategy, locator", "Missing args");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const ls = buildLocatorString(strategy, locator);
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const el = this.page.locator(ls);
|
|
352
|
+
await el.waitFor({ state: "attached", timeout: this.timeout });
|
|
353
|
+
|
|
354
|
+
await this.highlight(ls, "click", "JS Click");
|
|
355
|
+
|
|
356
|
+
await el.evaluate((node: HTMLElement) => node.click());
|
|
357
|
+
|
|
358
|
+
await this.unhighlight();
|
|
359
|
+
|
|
360
|
+
const sc = await this.shots.onPass(this.page);
|
|
361
|
+
|
|
362
|
+
return pass(`JS clicked ${displayLabel}`, sc);
|
|
363
|
+
|
|
364
|
+
} catch (error: any) {
|
|
365
|
+
await this.unhighlight();
|
|
366
|
+
const sc = await this.shots.onFail(this.page);
|
|
367
|
+
|
|
368
|
+
const isNotFound = /timeout|waiting for locator|not visible|not found/i.test(error.message);
|
|
369
|
+
|
|
370
|
+
return fail(
|
|
371
|
+
isNotFound ? `"${displayLabel}" not found` : `Failed clickByJS ${displayLabel} — ${error.message}`,
|
|
372
|
+
undefined,
|
|
373
|
+
sc
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// tSetup.clickIfPresent('xpath', '//button[...]')
|
|
378
|
+
async clickIfPresent(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
379
|
+
const [strategy, locator] = args;
|
|
380
|
+
if (!strategy || !locator) return fail("clickIfPresent needs: strategy, locator", "Missing args");
|
|
381
|
+
try {
|
|
382
|
+
const el = this.page.locator(buildLocatorString(strategy, locator));
|
|
383
|
+
const count = await el.count();
|
|
384
|
+
if (count > 0 && await el.first().isVisible()) {
|
|
385
|
+
await el.first().click({ timeout: this.timeout });
|
|
386
|
+
const sc = await this.shots.onPass(this.page);
|
|
387
|
+
return pass(`Clicked (was present): [${strategy}=${locator}] in ${slabel || 'unknown'}`, sc);
|
|
388
|
+
}
|
|
389
|
+
return pass(`Element not present, skipped: [${strategy}=${locator}] in ${slabel || 'unknown'}`);
|
|
390
|
+
} catch {
|
|
391
|
+
return pass(`Element not clickable, skipped: [${strategy}=${locator}] in ${slabel || 'unknown'}`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// tSetup.dragAndDrop('xpath', '//src', 'xpath', '//target')
|
|
396
|
+
async dragAndDrop(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
397
|
+
const [ss, sl, ts, tl] = args;
|
|
398
|
+
if (!ss || !sl || !ts || !tl) return fail("dragAndDrop needs: srcStrategy, srcLocator, tgtStrategy, tgtLocator", "Missing args");
|
|
399
|
+
try {
|
|
400
|
+
const src = this.page.locator(buildLocatorString(ss, sl));
|
|
401
|
+
const tgt = this.page.locator(buildLocatorString(ts, tl));
|
|
402
|
+
await src.waitFor({ state: "visible", timeout: this.timeout });
|
|
403
|
+
await src.dragTo(tgt, { timeout: this.timeout });
|
|
404
|
+
const sc = await this.shots.onPass(this.page);
|
|
405
|
+
return pass(`Dragged [${ss}=${sl}] → [${ts}=${tl}] in ${slabel || 'unknown'}`, sc);
|
|
406
|
+
} catch (error: any) {
|
|
407
|
+
const sc = await this.shots.onFail(this.page);
|
|
408
|
+
return fail("Failed dragAndDrop", error.message, sc);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// tSetup.clickAndWait('xpath', '//button[...]', 'networkidle')
|
|
413
|
+
async clickAndWait(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
414
|
+
const [strategy, locator, waitState] = args;
|
|
415
|
+
if (!strategy || !locator) return fail("clickAndWait needs: strategy, locator, [waitState]", "Missing args");
|
|
416
|
+
try {
|
|
417
|
+
const el = this.page.locator(buildLocatorString(strategy, locator));
|
|
418
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
419
|
+
const state = (waitState as any) || "domcontentloaded";
|
|
420
|
+
await Promise.all([
|
|
421
|
+
this.page.waitForLoadState(state, { timeout: this.timeout }),
|
|
422
|
+
el.click({ timeout: this.timeout }),
|
|
423
|
+
]);
|
|
424
|
+
const sc = await this.shots.onPass(this.page);
|
|
425
|
+
return pass(`Clicked and waited for ${state} in ${slabel || 'unknown'}`, sc);
|
|
426
|
+
} catch (error: any) {
|
|
427
|
+
const sc = await this.shots.onFail(this.page);
|
|
428
|
+
return fail(`Failed clickAndWait ${slabel || 'unknown'}`, error.message, sc);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// tSetup.clickByText('Sign In')
|
|
433
|
+
async clickByText(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
434
|
+
const [text, exact] = args;
|
|
435
|
+
if (!text) return fail("clickByText needs: text, [exact=true|false]", "Missing args");
|
|
436
|
+
try {
|
|
437
|
+
const isExact = exact !== "false";
|
|
438
|
+
const el = this.page.getByText(text, { exact: isExact });
|
|
439
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
440
|
+
await el.click({ timeout: this.timeout });
|
|
441
|
+
const sc = await this.shots.onPass(this.page);
|
|
442
|
+
return pass(`Clicked by text: "${text}" in ${slabel || 'unknown'}`, sc);
|
|
443
|
+
} catch (error: any) {
|
|
444
|
+
const sc = await this.shots.onFail(this.page);
|
|
445
|
+
return fail(`Failed clickByText: "${text}" in ${slabel || 'unknown'}`, error.message, sc);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// tSetup.clickNthElement('xpath', '//li', '2')
|
|
450
|
+
async clickNthElement(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
451
|
+
const [strategy, locator, nthStr] = args;
|
|
452
|
+
const nth = parseInt(nthStr || "0", 10);
|
|
453
|
+
if (!strategy || !locator) return fail("clickNthElement needs: strategy, locator, nth", "Missing args");
|
|
454
|
+
try {
|
|
455
|
+
const el = this.page.locator(buildLocatorString(strategy, locator)).nth(nth);
|
|
456
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
457
|
+
await el.click({ timeout: this.timeout });
|
|
458
|
+
const sc = await this.shots.onPass(this.page);
|
|
459
|
+
return pass(`Clicked nth(${nth}) in ${slabel || 'unknown'}`, sc);
|
|
460
|
+
} catch (error: any) {
|
|
461
|
+
const sc = await this.shots.onFail(this.page);
|
|
462
|
+
return fail(`Failed clickNthElement in ${slabel || 'unknown'}`, error.message, sc);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// tSetup.scrollAndClick('xpath', '//button[...]')
|
|
467
|
+
async scrollAndClick(args: string[], slabel?: string): Promise<ActionResponse> {
|
|
468
|
+
const [strategy, locator] = args;
|
|
469
|
+
if (!strategy || !locator) return fail("scrollAndClick needs: strategy, locator", "Missing args");
|
|
470
|
+
try {
|
|
471
|
+
const el = this.page.locator(buildLocatorString(strategy, locator));
|
|
472
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
473
|
+
await el.scrollIntoViewIfNeeded({ timeout: this.timeout });
|
|
474
|
+
await el.click({ timeout: this.timeout });
|
|
475
|
+
const sc = await this.shots.onPass(this.page);
|
|
476
|
+
return pass(`Scrolled and clicked ${slabel || 'unknown'}`, sc);
|
|
477
|
+
} catch (error: any) {
|
|
478
|
+
const sc = await this.shots.onFail(this.page);
|
|
479
|
+
return fail(`Failed scrollAndClick ${slabel || 'unknown'}`, error.message, sc);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Page } from "playwright";
|
|
2
|
+
import { ActionResponse, TestCaseConfig } from "../../types/types";
|
|
3
|
+
import { pass, fail } from "../../utils/response";
|
|
4
|
+
import { buildLocatorString } from "../../utils/locatorHelper";
|
|
5
|
+
import { BaseHandler } from "./BaseHandler";
|
|
6
|
+
import { Logger } from "../../utils/logger";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* CustomCodeHandler
|
|
10
|
+
* -----------------
|
|
11
|
+
* Mirrors the Python runCustomCode() behaviour:
|
|
12
|
+
*
|
|
13
|
+
* tSetup.runCustomCode('<JS code>', '<returnVariable>', '<optional xpath>')
|
|
14
|
+
*
|
|
15
|
+
* The code string runs inside an async Function with two injected helpers:
|
|
16
|
+
* - tSetup : the Page object (so you can call Playwright APIs from code)
|
|
17
|
+
* - testData: the shared TestData store (read/write mid-script variables)
|
|
18
|
+
*
|
|
19
|
+
* Rules:
|
|
20
|
+
* 1. Declare a variable whose name matches returnVariable — its value is
|
|
21
|
+
* captured, stored in TestData under that key, and returned in data.value.
|
|
22
|
+
* 2. If an xpath is supplied the captured value is also typed into that field.
|
|
23
|
+
* 3. If no returnVariable is supplied the code still runs; pass is returned.
|
|
24
|
+
* 4. On any exception the step fails with the error message + screenshot.
|
|
25
|
+
*
|
|
26
|
+
* Example step_script values:
|
|
27
|
+
* tSetup.runCustomCode('const result = "LN" + Math.floor(Math.random()*1000);', 'result', '//input[@id="ref"]')
|
|
28
|
+
* tSetup.runCustomCode('const otp = String(1000 + Math.floor(Math.random()*9000));', 'otp')
|
|
29
|
+
* tSetup.runCustomCode('await page.waitForTimeout(500); const ts = Date.now();', 'ts')
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
export class CustomCodeHandler extends BaseHandler {
|
|
33
|
+
protected log: Logger;
|
|
34
|
+
|
|
35
|
+
constructor(tConfig: TestCaseConfig, page: Page) {
|
|
36
|
+
super(tConfig, page);
|
|
37
|
+
this.log = Logger.create("CustomCodeHandler", tConfig.executionId);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getActions() {
|
|
41
|
+
return {
|
|
42
|
+
runcustomcode: this.runCustomCode.bind(this),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── runCustomCode(codeStr, returnVariable?, xpath?) ──────────────────────
|
|
47
|
+
|
|
48
|
+
async runCustomCode(args: string[]): Promise<ActionResponse> {
|
|
49
|
+
const [codeStr, returnVariable, xpath] = args;
|
|
50
|
+
|
|
51
|
+
if (!codeStr || !codeStr.trim()) {
|
|
52
|
+
return fail("runCustomCode: codeStr (arg 1) is required");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.log.info(`runCustomCode | returnVar="${returnVariable || "none"}" | xpath="${xpath || "none"}"`);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
// Build context — same idea as Python's context = {"tSetup": self}
|
|
59
|
+
const context: Record<string, unknown> = {
|
|
60
|
+
page: this.page,
|
|
61
|
+
tSetup: this.page, // alias so people can write tSetup.xxx inside the code
|
|
62
|
+
testData: this.testData,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Execute the code string as an async function so `await` works inside
|
|
66
|
+
const wrappedCode = `
|
|
67
|
+
"use strict";
|
|
68
|
+
const { page, tSetup, testData } = __ctx__;
|
|
69
|
+
${codeStr}
|
|
70
|
+
return typeof ${returnVariable || "__undefined__"} !== "undefined"
|
|
71
|
+
? ${returnVariable || "undefined"}
|
|
72
|
+
: undefined;
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
// eslint-disable-next-line no-new-func
|
|
76
|
+
const fn = new Function("__ctx__", `return (async (__ctx__) => { ${wrappedCode} })(__ctx__)`);
|
|
77
|
+
const value = await fn(context);
|
|
78
|
+
|
|
79
|
+
// ── Capture return value ───────────────────────────────────────────────
|
|
80
|
+
if (returnVariable && value !== undefined && value !== null) {
|
|
81
|
+
// Store in shared TestData so later steps can reference it
|
|
82
|
+
this.testData.set(returnVariable, value);
|
|
83
|
+
|
|
84
|
+
const strValue = String(value);
|
|
85
|
+
this.log.info(`runCustomCode → ${returnVariable} = ${strValue}`);
|
|
86
|
+
|
|
87
|
+
// If xpath provided, type the value into the field
|
|
88
|
+
if (xpath && xpath.trim()) {
|
|
89
|
+
try {
|
|
90
|
+
const el = this.page.locator(buildLocatorString("xpath", xpath));
|
|
91
|
+
await el.waitFor({ state: "visible", timeout: this.timeout });
|
|
92
|
+
await el.clear();
|
|
93
|
+
await el.fill(strValue);
|
|
94
|
+
this.log.info(`runCustomCode: typed "${strValue}" into [xpath=${xpath}]`);
|
|
95
|
+
} catch (fillErr: any) {
|
|
96
|
+
// Typing failed — still pass the code execution, but warn
|
|
97
|
+
this.log.warn(`runCustomCode: value generated but fill failed — ${fillErr.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const sc = await this.shots.onPass(this.page);
|
|
102
|
+
return pass(
|
|
103
|
+
`Custom code executed. ${returnVariable} = ${strValue}`,
|
|
104
|
+
sc,
|
|
105
|
+
{ returnVariable, value, type: typeof value }
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ── No return variable — code ran for side-effects only ───────────────
|
|
110
|
+
const sc = await this.shots.onPass(this.page);
|
|
111
|
+
return pass("Custom code executed successfully.", sc);
|
|
112
|
+
|
|
113
|
+
} catch (error: any) {
|
|
114
|
+
this.log.error(`runCustomCode failed: ${error.message}`);
|
|
115
|
+
const sc = await this.shots.onFail(this.page);
|
|
116
|
+
return fail(
|
|
117
|
+
`Exception occurred during custom code execution`,
|
|
118
|
+
error.message,
|
|
119
|
+
sc
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|