automation_model 1.0.433-dev → 1.0.433-stage
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/lib/api.d.ts +42 -1
- package/lib/api.js +199 -13
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +11 -32
- package/lib/auto_page.js.map +1 -1
- package/lib/axe/axe.mini.js +12 -0
- package/lib/browser_manager.js +8 -3
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +5 -0
- package/lib/command_common.js +111 -0
- package/lib/command_common.js.map +1 -0
- package/lib/environment.d.ts +3 -0
- package/lib/environment.js +5 -2
- package/lib/environment.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +38 -3
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.d.ts +7 -0
- package/lib/locate_element.js +213 -0
- package/lib/locate_element.js.map +1 -0
- package/lib/network.d.ts +2 -0
- package/lib/network.js +123 -0
- package/lib/network.js.map +1 -0
- package/lib/stable_browser.d.ts +26 -16
- package/lib/stable_browser.js +525 -633
- package/lib/stable_browser.js.map +1 -1
- package/lib/test_context.d.ts +2 -0
- package/lib/test_context.js +11 -10
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +3 -1
- package/lib/utils.js +68 -1
- package/lib/utils.js.map +1 -1
- package/package.json +5 -4
package/lib/stable_browser.js
CHANGED
|
@@ -2,19 +2,22 @@
|
|
|
2
2
|
import { expect } from "@playwright/test";
|
|
3
3
|
import dayjs from "dayjs";
|
|
4
4
|
import fs from "fs";
|
|
5
|
+
import { Jimp } from "jimp";
|
|
5
6
|
import path from "path";
|
|
6
7
|
import reg_parser from "regex-parser";
|
|
7
|
-
import sharp from "sharp";
|
|
8
8
|
import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
|
|
9
9
|
import { getDateTimeValue } from "./date_time.js";
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import
|
|
14
|
-
import { decrypt } from "./utils.js";
|
|
13
|
+
import { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
15
14
|
import csv from "csv-parser";
|
|
16
15
|
import { Readable } from "node:stream";
|
|
17
16
|
import readline from "readline";
|
|
17
|
+
import { getContext } from "./init_browser.js";
|
|
18
|
+
import { locate_element } from "./locate_element.js";
|
|
19
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
|
|
20
|
+
import { registerNetworkEvents } from "./network.js";
|
|
18
21
|
const Types = {
|
|
19
22
|
CLICK: "click_element",
|
|
20
23
|
NAVIGATE: "navigate",
|
|
@@ -42,15 +45,24 @@ const Types = {
|
|
|
42
45
|
LOAD_DATA: "load_data",
|
|
43
46
|
SET_INPUT: "set_input",
|
|
44
47
|
};
|
|
48
|
+
export const apps = {};
|
|
45
49
|
class StableBrowser {
|
|
46
|
-
|
|
50
|
+
browser;
|
|
51
|
+
page;
|
|
52
|
+
logger;
|
|
53
|
+
context;
|
|
54
|
+
world;
|
|
55
|
+
project_path = null;
|
|
56
|
+
webLogFile = null;
|
|
57
|
+
networkLogger = null;
|
|
58
|
+
configuration = null;
|
|
59
|
+
appName = "main";
|
|
60
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
47
61
|
this.browser = browser;
|
|
48
62
|
this.page = page;
|
|
49
63
|
this.logger = logger;
|
|
50
64
|
this.context = context;
|
|
51
|
-
this.
|
|
52
|
-
this.webLogFile = null;
|
|
53
|
-
this.configuration = null;
|
|
65
|
+
this.world = world;
|
|
54
66
|
if (!this.logger) {
|
|
55
67
|
this.logger = console;
|
|
56
68
|
}
|
|
@@ -76,16 +88,24 @@ class StableBrowser {
|
|
|
76
88
|
this.logger.error("unable to read ai_config.json");
|
|
77
89
|
}
|
|
78
90
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
79
|
-
this.
|
|
80
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
81
|
-
this.registerRequestListener();
|
|
91
|
+
this.world = world;
|
|
82
92
|
context.pages = [this.page];
|
|
83
93
|
context.pageLoading = { status: false };
|
|
94
|
+
this.registerEventListeners(this.context);
|
|
95
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
96
|
+
}
|
|
97
|
+
registerEventListeners(context) {
|
|
98
|
+
this.registerConsoleLogListener(this.page, context);
|
|
99
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
100
|
+
if (!context.pageLoading) {
|
|
101
|
+
context.pageLoading = { status: false };
|
|
102
|
+
}
|
|
84
103
|
context.playContext.on("page", async function (page) {
|
|
85
104
|
context.pageLoading.status = true;
|
|
86
105
|
this.page = page;
|
|
87
106
|
context.page = page;
|
|
88
107
|
context.pages.push(page);
|
|
108
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
89
109
|
page.on("close", async () => {
|
|
90
110
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
91
111
|
this.context.pages.pop();
|
|
@@ -110,6 +130,36 @@ class StableBrowser {
|
|
|
110
130
|
context.pageLoading.status = false;
|
|
111
131
|
}.bind(this));
|
|
112
132
|
}
|
|
133
|
+
async switchApp(appName) {
|
|
134
|
+
// check if the current app (this.appName) is the same as the new app
|
|
135
|
+
if (this.appName === appName) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let navigate = false;
|
|
139
|
+
if (!apps[appName]) {
|
|
140
|
+
let newContext = await getContext(null, false, this.logger, appName, false, this);
|
|
141
|
+
navigate = true;
|
|
142
|
+
apps[appName] = {
|
|
143
|
+
context: newContext,
|
|
144
|
+
browser: newContext.browser,
|
|
145
|
+
page: newContext.page,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const tempContext = {};
|
|
149
|
+
this._copyContext(this, tempContext);
|
|
150
|
+
this._copyContext(apps[appName], this);
|
|
151
|
+
apps[this.appName] = tempContext;
|
|
152
|
+
this.appName = appName;
|
|
153
|
+
if (navigate) {
|
|
154
|
+
await this.goto(this.context.environment.baseUrl);
|
|
155
|
+
await this.waitForPageLoad();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
_copyContext(from, to) {
|
|
159
|
+
to.browser = from.browser;
|
|
160
|
+
to.page = from.page;
|
|
161
|
+
to.context = from.context;
|
|
162
|
+
}
|
|
113
163
|
getWebLogFile(logFolder) {
|
|
114
164
|
if (!fs.existsSync(logFolder)) {
|
|
115
165
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -121,37 +171,63 @@ class StableBrowser {
|
|
|
121
171
|
const fileName = nextIndex + ".json";
|
|
122
172
|
return path.join(logFolder, fileName);
|
|
123
173
|
}
|
|
124
|
-
registerConsoleLogListener(page, context
|
|
174
|
+
registerConsoleLogListener(page, context) {
|
|
125
175
|
if (!this.context.webLogger) {
|
|
126
176
|
this.context.webLogger = [];
|
|
127
177
|
}
|
|
128
178
|
page.on("console", async (msg) => {
|
|
129
|
-
|
|
179
|
+
const obj = {
|
|
130
180
|
type: msg.type(),
|
|
131
181
|
text: msg.text(),
|
|
132
182
|
location: msg.location(),
|
|
133
183
|
time: new Date().toISOString(),
|
|
134
|
-
}
|
|
135
|
-
|
|
184
|
+
};
|
|
185
|
+
this.context.webLogger.push(obj);
|
|
186
|
+
if (msg.type() === "error") {
|
|
187
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
188
|
+
}
|
|
136
189
|
});
|
|
137
190
|
}
|
|
138
|
-
registerRequestListener() {
|
|
139
|
-
this.
|
|
191
|
+
registerRequestListener(page, context, logFile) {
|
|
192
|
+
if (!this.context.networkLogger) {
|
|
193
|
+
this.context.networkLogger = [];
|
|
194
|
+
}
|
|
195
|
+
page.on("request", async (data) => {
|
|
196
|
+
const startTime = new Date().getTime();
|
|
140
197
|
try {
|
|
141
|
-
const pageUrl = new URL(
|
|
198
|
+
const pageUrl = new URL(page.url());
|
|
142
199
|
const requestUrl = new URL(data.url());
|
|
143
200
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
144
201
|
const method = data.method();
|
|
145
|
-
if (
|
|
202
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
146
203
|
const token = await data.headerValue("Authorization");
|
|
147
204
|
if (token) {
|
|
148
|
-
|
|
205
|
+
context.authtoken = token;
|
|
149
206
|
}
|
|
150
207
|
}
|
|
151
208
|
}
|
|
209
|
+
const response = await data.response();
|
|
210
|
+
const endTime = new Date().getTime();
|
|
211
|
+
const obj = {
|
|
212
|
+
url: data.url(),
|
|
213
|
+
method: data.method(),
|
|
214
|
+
postData: data.postData(),
|
|
215
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
216
|
+
duration: endTime - startTime,
|
|
217
|
+
startTime,
|
|
218
|
+
};
|
|
219
|
+
context.networkLogger.push(obj);
|
|
220
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
152
221
|
}
|
|
153
222
|
catch (error) {
|
|
154
223
|
console.error("Error in request listener", error);
|
|
224
|
+
context.networkLogger.push({
|
|
225
|
+
error: "not able to listen",
|
|
226
|
+
message: error.message,
|
|
227
|
+
stack: error.stack,
|
|
228
|
+
time: new Date().toISOString(),
|
|
229
|
+
});
|
|
230
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
155
231
|
}
|
|
156
232
|
});
|
|
157
233
|
}
|
|
@@ -166,20 +242,6 @@ class StableBrowser {
|
|
|
166
242
|
timeout: 60000,
|
|
167
243
|
});
|
|
168
244
|
}
|
|
169
|
-
_validateSelectors(selectors) {
|
|
170
|
-
if (!selectors) {
|
|
171
|
-
throw new Error("selectors is null");
|
|
172
|
-
}
|
|
173
|
-
if (!selectors.locators) {
|
|
174
|
-
throw new Error("selectors.locators is null");
|
|
175
|
-
}
|
|
176
|
-
if (!Array.isArray(selectors.locators)) {
|
|
177
|
-
throw new Error("selectors.locators expected to be array");
|
|
178
|
-
}
|
|
179
|
-
if (selectors.locators.length === 0) {
|
|
180
|
-
throw new Error("selectors.locators expected to be non empty array");
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
245
|
_fixUsingParams(text, _params) {
|
|
184
246
|
if (!_params || typeof text !== "string") {
|
|
185
247
|
return text;
|
|
@@ -247,7 +309,7 @@ class StableBrowser {
|
|
|
247
309
|
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
248
310
|
}
|
|
249
311
|
}
|
|
250
|
-
if (locator
|
|
312
|
+
if (locator?.engine) {
|
|
251
313
|
if (locator.engine === "css") {
|
|
252
314
|
locatorReturn = scope.locator(locator.selector);
|
|
253
315
|
}
|
|
@@ -268,7 +330,10 @@ class StableBrowser {
|
|
|
268
330
|
return locatorReturn;
|
|
269
331
|
}
|
|
270
332
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
271
|
-
|
|
333
|
+
if (css && css.locator) {
|
|
334
|
+
css = css.locator;
|
|
335
|
+
}
|
|
336
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
|
|
272
337
|
if (result.elementCount === 0) {
|
|
273
338
|
return;
|
|
274
339
|
}
|
|
@@ -283,7 +348,7 @@ class StableBrowser {
|
|
|
283
348
|
}
|
|
284
349
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
285
350
|
//const stringifyText = JSON.stringify(text);
|
|
286
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
351
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
287
352
|
function isParent(parent, child) {
|
|
288
353
|
let currentNode = child.parentNode;
|
|
289
354
|
while (currentNode !== null) {
|
|
@@ -295,6 +360,15 @@ class StableBrowser {
|
|
|
295
360
|
return false;
|
|
296
361
|
}
|
|
297
362
|
document.isParent = isParent;
|
|
363
|
+
function getRegex(str) {
|
|
364
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
365
|
+
if (!match) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
let [_, pattern, flags] = match;
|
|
369
|
+
return new RegExp(pattern, flags);
|
|
370
|
+
}
|
|
371
|
+
document.getRegex = getRegex;
|
|
298
372
|
function collectAllShadowDomElements(element, result = []) {
|
|
299
373
|
// Check and add the element if it has a shadow root
|
|
300
374
|
if (element.shadowRoot) {
|
|
@@ -313,6 +387,10 @@ class StableBrowser {
|
|
|
313
387
|
if (!tag) {
|
|
314
388
|
tag = "*";
|
|
315
389
|
}
|
|
390
|
+
let regexpSearch = document.getRegex(text);
|
|
391
|
+
if (regexpSearch) {
|
|
392
|
+
regex = true;
|
|
393
|
+
}
|
|
316
394
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
317
395
|
let shadowHosts = [];
|
|
318
396
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -328,7 +406,9 @@ class StableBrowser {
|
|
|
328
406
|
let randomToken = null;
|
|
329
407
|
const foundElements = [];
|
|
330
408
|
if (regex) {
|
|
331
|
-
|
|
409
|
+
if (!regexpSearch) {
|
|
410
|
+
regexpSearch = new RegExp(text, "im");
|
|
411
|
+
}
|
|
332
412
|
for (let i = 0; i < elements.length; i++) {
|
|
333
413
|
const element = elements[i];
|
|
334
414
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -342,8 +422,8 @@ class StableBrowser {
|
|
|
342
422
|
for (let i = 0; i < elements.length; i++) {
|
|
343
423
|
const element = elements[i];
|
|
344
424
|
if (partial) {
|
|
345
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
346
|
-
(element.value && element.value.includes(text))) {
|
|
425
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
426
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
347
427
|
foundElements.push(element);
|
|
348
428
|
}
|
|
349
429
|
}
|
|
@@ -388,6 +468,12 @@ class StableBrowser {
|
|
|
388
468
|
}
|
|
389
469
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
390
470
|
let locatorSearch = selectorHierarchy[index];
|
|
471
|
+
try {
|
|
472
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
473
|
+
}
|
|
474
|
+
catch (e) {
|
|
475
|
+
console.error(e);
|
|
476
|
+
}
|
|
391
477
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
392
478
|
let locator = null;
|
|
393
479
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
@@ -476,13 +562,18 @@ class StableBrowser {
|
|
|
476
562
|
if (result.foundElements.length > 0) {
|
|
477
563
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
478
564
|
await dialogCloseLocator.click();
|
|
565
|
+
// wait for the dialog to close
|
|
566
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
479
567
|
return { rerun: true };
|
|
480
568
|
}
|
|
481
569
|
}
|
|
482
570
|
}
|
|
483
571
|
return { rerun: false };
|
|
484
572
|
}
|
|
485
|
-
async _locate(selectors, info, _params, timeout
|
|
573
|
+
async _locate(selectors, info, _params, timeout) {
|
|
574
|
+
if (!timeout) {
|
|
575
|
+
timeout = 30000;
|
|
576
|
+
}
|
|
486
577
|
for (let i = 0; i < 3; i++) {
|
|
487
578
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
488
579
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
@@ -496,32 +587,41 @@ class StableBrowser {
|
|
|
496
587
|
}
|
|
497
588
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
498
589
|
}
|
|
499
|
-
async
|
|
500
|
-
let highPriorityTimeout = 5000;
|
|
501
|
-
let visibleOnlyTimeout = 6000;
|
|
502
|
-
let startTime = performance.now();
|
|
503
|
-
let locatorsCount = 0;
|
|
504
|
-
//let arrayMode = Array.isArray(selectors);
|
|
590
|
+
async _findFrameScope(selectors, timeout = 30000) {
|
|
505
591
|
let scope = this.page;
|
|
592
|
+
if (selectors.frame) {
|
|
593
|
+
return selectors.frame;
|
|
594
|
+
}
|
|
506
595
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
507
|
-
const findFrame = (frame, framescope) => {
|
|
596
|
+
const findFrame = async (frame, framescope) => {
|
|
508
597
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
509
598
|
let frameLocator = frame.selectors[i];
|
|
510
599
|
if (frameLocator.css) {
|
|
511
|
-
|
|
512
|
-
|
|
600
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
601
|
+
if (frameLocator.index) {
|
|
602
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
606
|
+
timeout: 5000,
|
|
607
|
+
});
|
|
608
|
+
framescope = testframescope;
|
|
609
|
+
break;
|
|
610
|
+
}
|
|
611
|
+
catch (error) {
|
|
612
|
+
console.error("frame not found " + frameLocator.css);
|
|
613
|
+
}
|
|
513
614
|
}
|
|
514
615
|
}
|
|
515
616
|
if (frame.children) {
|
|
516
|
-
return findFrame(frame.children, framescope);
|
|
617
|
+
return await findFrame(frame.children, framescope);
|
|
517
618
|
}
|
|
518
619
|
return framescope;
|
|
519
620
|
};
|
|
520
|
-
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
521
621
|
while (true) {
|
|
522
622
|
let frameFound = false;
|
|
523
623
|
if (selectors.nestFrmLoc) {
|
|
524
|
-
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
624
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
525
625
|
frameFound = true;
|
|
526
626
|
break;
|
|
527
627
|
}
|
|
@@ -550,6 +650,25 @@ class StableBrowser {
|
|
|
550
650
|
}
|
|
551
651
|
}
|
|
552
652
|
}
|
|
653
|
+
if (!scope) {
|
|
654
|
+
scope = this.page;
|
|
655
|
+
}
|
|
656
|
+
return scope;
|
|
657
|
+
}
|
|
658
|
+
async _getDocumentBody(selectors, timeout = 30000) {
|
|
659
|
+
let scope = await this._findFrameScope(selectors, timeout);
|
|
660
|
+
return scope.evaluate(() => {
|
|
661
|
+
var bodyContent = document.body.innerHTML;
|
|
662
|
+
return bodyContent;
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
666
|
+
let highPriorityTimeout = 5000;
|
|
667
|
+
let visibleOnlyTimeout = 6000;
|
|
668
|
+
let startTime = performance.now();
|
|
669
|
+
let locatorsCount = 0;
|
|
670
|
+
//let arrayMode = Array.isArray(selectors);
|
|
671
|
+
let scope = await this._findFrameScope(selectors, timeout);
|
|
553
672
|
let selectorsLocators = null;
|
|
554
673
|
selectorsLocators = selectors.locators;
|
|
555
674
|
// group selectors by priority
|
|
@@ -685,86 +804,121 @@ class StableBrowser {
|
|
|
685
804
|
}
|
|
686
805
|
return result;
|
|
687
806
|
}
|
|
688
|
-
async
|
|
689
|
-
this._validateSelectors(selectors);
|
|
807
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
690
808
|
const startTime = Date.now();
|
|
691
|
-
|
|
692
|
-
|
|
809
|
+
let timeout = 30000;
|
|
810
|
+
if (options && options.timeout) {
|
|
811
|
+
timeout = options.timeout;
|
|
693
812
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
813
|
+
while (true) {
|
|
814
|
+
try {
|
|
815
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
816
|
+
if (result?.elementNumber >= 0) {
|
|
817
|
+
const selectors = {
|
|
818
|
+
frame: result?.frame,
|
|
819
|
+
locators: [
|
|
820
|
+
{
|
|
821
|
+
css: result?.css,
|
|
822
|
+
},
|
|
823
|
+
],
|
|
824
|
+
};
|
|
825
|
+
await this.click(selectors, _params, options, world);
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
catch (e) {
|
|
830
|
+
if (performance.now() - startTime > timeout) {
|
|
831
|
+
throw e;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
838
|
+
const startTime = Date.now();
|
|
839
|
+
let timeout = 30000;
|
|
840
|
+
if (options && options.timeout) {
|
|
841
|
+
timeout = options.timeout;
|
|
842
|
+
}
|
|
843
|
+
while (true) {
|
|
844
|
+
try {
|
|
845
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
846
|
+
if (result?.elementNumber >= 0) {
|
|
847
|
+
const selectors = {
|
|
848
|
+
frame: result?.frame,
|
|
849
|
+
locators: [
|
|
850
|
+
{
|
|
851
|
+
css: result?.css,
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
};
|
|
855
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
catch (e) {
|
|
860
|
+
if (performance.now() - startTime > timeout) {
|
|
861
|
+
throw e;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
async click(selectors, _params, options = {}, world = null) {
|
|
868
|
+
const state = {
|
|
869
|
+
selectors,
|
|
870
|
+
_params,
|
|
871
|
+
options,
|
|
872
|
+
world,
|
|
873
|
+
text: "Click element",
|
|
874
|
+
type: Types.CLICK,
|
|
875
|
+
operation: "click",
|
|
876
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
877
|
+
};
|
|
701
878
|
try {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
879
|
+
await _preCommand(state, this);
|
|
880
|
+
if (state.options && state.options.context) {
|
|
881
|
+
state.selectors.locators[0].text = state.options.context;
|
|
882
|
+
}
|
|
705
883
|
try {
|
|
706
|
-
await
|
|
707
|
-
await element.click();
|
|
884
|
+
await state.element.click();
|
|
708
885
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
709
886
|
}
|
|
710
887
|
catch (e) {
|
|
711
888
|
// await this.closeUnexpectedPopups();
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
await element.dispatchEvent("click");
|
|
889
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
890
|
+
await state.element.dispatchEvent("click");
|
|
715
891
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
716
892
|
}
|
|
717
893
|
await this.waitForPageLoad();
|
|
718
|
-
return info;
|
|
894
|
+
return state.info;
|
|
719
895
|
}
|
|
720
896
|
catch (e) {
|
|
721
|
-
|
|
722
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
723
|
-
info.screenshotPath = screenshotPath;
|
|
724
|
-
Object.assign(e, { info: info });
|
|
725
|
-
error = e;
|
|
726
|
-
throw e;
|
|
897
|
+
await _commandError(state, e, this);
|
|
727
898
|
}
|
|
728
899
|
finally {
|
|
729
|
-
|
|
730
|
-
this._reportToWorld(world, {
|
|
731
|
-
element_name: selectors.element_name,
|
|
732
|
-
type: Types.CLICK,
|
|
733
|
-
text: `Click element`,
|
|
734
|
-
screenshotId,
|
|
735
|
-
result: error
|
|
736
|
-
? {
|
|
737
|
-
status: "FAILED",
|
|
738
|
-
startTime,
|
|
739
|
-
endTime,
|
|
740
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
741
|
-
}
|
|
742
|
-
: {
|
|
743
|
-
status: "PASSED",
|
|
744
|
-
startTime,
|
|
745
|
-
endTime,
|
|
746
|
-
},
|
|
747
|
-
info: info,
|
|
748
|
-
});
|
|
900
|
+
_commandFinally(state, this);
|
|
749
901
|
}
|
|
750
902
|
}
|
|
751
903
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
904
|
+
const state = {
|
|
905
|
+
selectors,
|
|
906
|
+
_params,
|
|
907
|
+
options,
|
|
908
|
+
world,
|
|
909
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
910
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
911
|
+
operation: "setCheck",
|
|
912
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
913
|
+
};
|
|
762
914
|
try {
|
|
763
|
-
|
|
764
|
-
|
|
915
|
+
await _preCommand(state, this);
|
|
916
|
+
state.info.checked = checked;
|
|
917
|
+
// let element = await this._locate(selectors, info, _params);
|
|
918
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
765
919
|
try {
|
|
766
|
-
await this._highlightElements(element);
|
|
767
|
-
await element.setChecked(checked);
|
|
920
|
+
// await this._highlightElements(element);
|
|
921
|
+
await state.element.setChecked(checked);
|
|
768
922
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
769
923
|
}
|
|
770
924
|
catch (e) {
|
|
@@ -773,179 +927,108 @@ class StableBrowser {
|
|
|
773
927
|
}
|
|
774
928
|
else {
|
|
775
929
|
//await this.closeUnexpectedPopups();
|
|
776
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
777
|
-
element = await this._locate(selectors, info, _params);
|
|
778
|
-
await element.setChecked(checked, { timeout: 5000, force: true });
|
|
930
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
931
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
932
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
779
933
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
780
934
|
}
|
|
781
935
|
}
|
|
782
936
|
await this.waitForPageLoad();
|
|
783
|
-
return info;
|
|
937
|
+
return state.info;
|
|
784
938
|
}
|
|
785
939
|
catch (e) {
|
|
786
|
-
|
|
787
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
788
|
-
info.screenshotPath = screenshotPath;
|
|
789
|
-
Object.assign(e, { info: info });
|
|
790
|
-
error = e;
|
|
791
|
-
throw e;
|
|
940
|
+
await _commandError(state, e, this);
|
|
792
941
|
}
|
|
793
942
|
finally {
|
|
794
|
-
|
|
795
|
-
this._reportToWorld(world, {
|
|
796
|
-
element_name: selectors.element_name,
|
|
797
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
798
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
799
|
-
screenshotId,
|
|
800
|
-
result: error
|
|
801
|
-
? {
|
|
802
|
-
status: "FAILED",
|
|
803
|
-
startTime,
|
|
804
|
-
endTime,
|
|
805
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
806
|
-
}
|
|
807
|
-
: {
|
|
808
|
-
status: "PASSED",
|
|
809
|
-
startTime,
|
|
810
|
-
endTime,
|
|
811
|
-
},
|
|
812
|
-
info: info,
|
|
813
|
-
});
|
|
943
|
+
_commandFinally(state, this);
|
|
814
944
|
}
|
|
815
945
|
}
|
|
816
946
|
async hover(selectors, _params, options = {}, world = null) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
947
|
+
const state = {
|
|
948
|
+
selectors,
|
|
949
|
+
_params,
|
|
950
|
+
options,
|
|
951
|
+
world,
|
|
952
|
+
type: Types.HOVER,
|
|
953
|
+
text: `Hover element`,
|
|
954
|
+
operation: "hover",
|
|
955
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
956
|
+
};
|
|
826
957
|
try {
|
|
827
|
-
|
|
828
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
958
|
+
await _preCommand(state, this);
|
|
829
959
|
try {
|
|
830
|
-
await
|
|
831
|
-
await element.hover();
|
|
960
|
+
await state.element.hover();
|
|
832
961
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
833
962
|
}
|
|
834
963
|
catch (e) {
|
|
835
964
|
//await this.closeUnexpectedPopups();
|
|
836
|
-
info.log += "hover failed, will try again" + "\n";
|
|
837
|
-
element = await this._locate(selectors, info, _params);
|
|
838
|
-
await element.hover({ timeout: 10000 });
|
|
965
|
+
state.info.log += "hover failed, will try again" + "\n";
|
|
966
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
967
|
+
await state.element.hover({ timeout: 10000 });
|
|
839
968
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
840
969
|
}
|
|
841
970
|
await this.waitForPageLoad();
|
|
842
|
-
return info;
|
|
971
|
+
return state.info;
|
|
843
972
|
}
|
|
844
973
|
catch (e) {
|
|
845
|
-
|
|
846
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
847
|
-
info.screenshotPath = screenshotPath;
|
|
848
|
-
Object.assign(e, { info: info });
|
|
849
|
-
error = e;
|
|
850
|
-
throw e;
|
|
974
|
+
await _commandError(state, e, this);
|
|
851
975
|
}
|
|
852
976
|
finally {
|
|
853
|
-
|
|
854
|
-
this._reportToWorld(world, {
|
|
855
|
-
element_name: selectors.element_name,
|
|
856
|
-
type: Types.HOVER,
|
|
857
|
-
text: `Hover element`,
|
|
858
|
-
screenshotId,
|
|
859
|
-
result: error
|
|
860
|
-
? {
|
|
861
|
-
status: "FAILED",
|
|
862
|
-
startTime,
|
|
863
|
-
endTime,
|
|
864
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
865
|
-
}
|
|
866
|
-
: {
|
|
867
|
-
status: "PASSED",
|
|
868
|
-
startTime,
|
|
869
|
-
endTime,
|
|
870
|
-
},
|
|
871
|
-
info: info,
|
|
872
|
-
});
|
|
977
|
+
_commandFinally(state, this);
|
|
873
978
|
}
|
|
874
979
|
}
|
|
875
980
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
876
|
-
this._validateSelectors(selectors);
|
|
877
981
|
if (!values) {
|
|
878
982
|
throw new Error("values is null");
|
|
879
983
|
}
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
984
|
+
const state = {
|
|
985
|
+
selectors,
|
|
986
|
+
_params,
|
|
987
|
+
options,
|
|
988
|
+
world,
|
|
989
|
+
value: values.toString(),
|
|
990
|
+
type: Types.SELECT,
|
|
991
|
+
text: `Select option: ${values}`,
|
|
992
|
+
operation: "selectOption",
|
|
993
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
994
|
+
};
|
|
888
995
|
try {
|
|
889
|
-
|
|
890
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
996
|
+
await _preCommand(state, this);
|
|
891
997
|
try {
|
|
892
|
-
await
|
|
893
|
-
await element.selectOption(values);
|
|
998
|
+
await state.element.selectOption(values);
|
|
894
999
|
}
|
|
895
1000
|
catch (e) {
|
|
896
1001
|
//await this.closeUnexpectedPopups();
|
|
897
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
898
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1002
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1003
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
899
1004
|
}
|
|
900
1005
|
await this.waitForPageLoad();
|
|
901
|
-
return info;
|
|
1006
|
+
return state.info;
|
|
902
1007
|
}
|
|
903
1008
|
catch (e) {
|
|
904
|
-
|
|
905
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
906
|
-
info.screenshotPath = screenshotPath;
|
|
907
|
-
Object.assign(e, { info: info });
|
|
908
|
-
this.logger.info("click failed, will try next selector");
|
|
909
|
-
error = e;
|
|
910
|
-
throw e;
|
|
1009
|
+
await _commandError(state, e, this);
|
|
911
1010
|
}
|
|
912
1011
|
finally {
|
|
913
|
-
|
|
914
|
-
this._reportToWorld(world, {
|
|
915
|
-
element_name: selectors.element_name,
|
|
916
|
-
type: Types.SELECT,
|
|
917
|
-
text: `Select option: ${values}`,
|
|
918
|
-
value: values.toString(),
|
|
919
|
-
screenshotId,
|
|
920
|
-
result: error
|
|
921
|
-
? {
|
|
922
|
-
status: "FAILED",
|
|
923
|
-
startTime,
|
|
924
|
-
endTime,
|
|
925
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
926
|
-
}
|
|
927
|
-
: {
|
|
928
|
-
status: "PASSED",
|
|
929
|
-
startTime,
|
|
930
|
-
endTime,
|
|
931
|
-
},
|
|
932
|
-
info: info,
|
|
933
|
-
});
|
|
1012
|
+
_commandFinally(state, this);
|
|
934
1013
|
}
|
|
935
1014
|
}
|
|
936
1015
|
async type(_value, _params = null, options = {}, world = null) {
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1016
|
+
const state = {
|
|
1017
|
+
value: _value,
|
|
1018
|
+
_params,
|
|
1019
|
+
options,
|
|
1020
|
+
world,
|
|
1021
|
+
locate: false,
|
|
1022
|
+
scroll: false,
|
|
1023
|
+
highlight: false,
|
|
1024
|
+
type: Types.TYPE_PRESS,
|
|
1025
|
+
text: `Type value: ${_value}`,
|
|
1026
|
+
operation: "type",
|
|
1027
|
+
log: "",
|
|
1028
|
+
};
|
|
946
1029
|
try {
|
|
947
|
-
|
|
948
|
-
const valueSegment =
|
|
1030
|
+
await _preCommand(state, this);
|
|
1031
|
+
const valueSegment = state.value.split("&&");
|
|
949
1032
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
950
1033
|
if (i > 0) {
|
|
951
1034
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -965,108 +1048,53 @@ class StableBrowser {
|
|
|
965
1048
|
await this.page.keyboard.type(value);
|
|
966
1049
|
}
|
|
967
1050
|
}
|
|
968
|
-
return info;
|
|
1051
|
+
return state.info;
|
|
969
1052
|
}
|
|
970
1053
|
catch (e) {
|
|
971
|
-
|
|
972
|
-
this.logger.error("type failed " + info.log);
|
|
973
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
974
|
-
info.screenshotPath = screenshotPath;
|
|
975
|
-
Object.assign(e, { info: info });
|
|
976
|
-
error = e;
|
|
977
|
-
throw e;
|
|
1054
|
+
await _commandError(state, e, this);
|
|
978
1055
|
}
|
|
979
1056
|
finally {
|
|
980
|
-
|
|
981
|
-
this._reportToWorld(world, {
|
|
982
|
-
type: Types.TYPE_PRESS,
|
|
983
|
-
screenshotId,
|
|
984
|
-
value: _value,
|
|
985
|
-
text: `type value: ${_value}`,
|
|
986
|
-
result: error
|
|
987
|
-
? {
|
|
988
|
-
status: "FAILED",
|
|
989
|
-
startTime,
|
|
990
|
-
endTime,
|
|
991
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
992
|
-
}
|
|
993
|
-
: {
|
|
994
|
-
status: "PASSED",
|
|
995
|
-
startTime,
|
|
996
|
-
endTime,
|
|
997
|
-
},
|
|
998
|
-
info: info,
|
|
999
|
-
});
|
|
1057
|
+
_commandFinally(state, this);
|
|
1000
1058
|
}
|
|
1001
1059
|
}
|
|
1002
1060
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
let screenshotPath = null;
|
|
1061
|
+
const state = {
|
|
1062
|
+
selectors,
|
|
1063
|
+
_params,
|
|
1064
|
+
value,
|
|
1065
|
+
options,
|
|
1066
|
+
world,
|
|
1067
|
+
type: Types.SET_INPUT,
|
|
1068
|
+
text: `Set input value`,
|
|
1069
|
+
operation: "setInputValue",
|
|
1070
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1071
|
+
};
|
|
1015
1072
|
try {
|
|
1016
|
-
|
|
1017
|
-
let
|
|
1018
|
-
await this.scrollIfNeeded(element, info);
|
|
1019
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1020
|
-
await this._highlightElements(element);
|
|
1073
|
+
await _preCommand(state, this);
|
|
1074
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1021
1075
|
try {
|
|
1022
|
-
await element.evaluateHandle((el, value) => {
|
|
1076
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1023
1077
|
el.value = value;
|
|
1024
1078
|
}, value);
|
|
1025
1079
|
}
|
|
1026
1080
|
catch (error) {
|
|
1027
1081
|
this.logger.error("setInputValue failed, will try again");
|
|
1028
|
-
|
|
1029
|
-
info.
|
|
1030
|
-
|
|
1031
|
-
await element.evaluateHandle((el, value) => {
|
|
1082
|
+
await _screenshot(state, this);
|
|
1083
|
+
Object.assign(error, { info: state.info });
|
|
1084
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1032
1085
|
el.value = value;
|
|
1033
1086
|
});
|
|
1034
1087
|
}
|
|
1035
1088
|
}
|
|
1036
1089
|
catch (e) {
|
|
1037
|
-
|
|
1038
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1039
|
-
info.screenshotPath = screenshotPath;
|
|
1040
|
-
Object.assign(e, { info: info });
|
|
1041
|
-
error = e;
|
|
1042
|
-
throw e;
|
|
1090
|
+
await _commandError(state, e, this);
|
|
1043
1091
|
}
|
|
1044
1092
|
finally {
|
|
1045
|
-
|
|
1046
|
-
this._reportToWorld(world, {
|
|
1047
|
-
element_name: selectors.element_name,
|
|
1048
|
-
type: Types.SET_INPUT,
|
|
1049
|
-
text: `Set input value`,
|
|
1050
|
-
value: value,
|
|
1051
|
-
screenshotId,
|
|
1052
|
-
result: error
|
|
1053
|
-
? {
|
|
1054
|
-
status: "FAILED",
|
|
1055
|
-
startTime,
|
|
1056
|
-
endTime,
|
|
1057
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1058
|
-
}
|
|
1059
|
-
: {
|
|
1060
|
-
status: "PASSED",
|
|
1061
|
-
startTime,
|
|
1062
|
-
endTime,
|
|
1063
|
-
},
|
|
1064
|
-
info: info,
|
|
1065
|
-
});
|
|
1093
|
+
_commandFinally(state, this);
|
|
1066
1094
|
}
|
|
1067
1095
|
}
|
|
1068
1096
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1069
|
-
|
|
1097
|
+
_validateSelectors(selectors);
|
|
1070
1098
|
const startTime = Date.now();
|
|
1071
1099
|
let error = null;
|
|
1072
1100
|
let screenshotId = null;
|
|
@@ -1159,32 +1187,32 @@ class StableBrowser {
|
|
|
1159
1187
|
}
|
|
1160
1188
|
}
|
|
1161
1189
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1162
|
-
|
|
1163
|
-
const startTime = Date.now();
|
|
1164
|
-
let error = null;
|
|
1165
|
-
let screenshotId = null;
|
|
1166
|
-
let screenshotPath = null;
|
|
1167
|
-
const info = {};
|
|
1168
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1169
|
-
info.operation = "clickType";
|
|
1170
|
-
info.selectors = selectors;
|
|
1190
|
+
_value = unEscapeString(_value);
|
|
1171
1191
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1192
|
+
const state = {
|
|
1193
|
+
selectors,
|
|
1194
|
+
_params,
|
|
1195
|
+
value: newValue,
|
|
1196
|
+
originalValue: _value,
|
|
1197
|
+
options,
|
|
1198
|
+
world,
|
|
1199
|
+
type: Types.FILL,
|
|
1200
|
+
text: `Click type input with value: ${_value}`,
|
|
1201
|
+
operation: "clickType",
|
|
1202
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1203
|
+
};
|
|
1172
1204
|
if (newValue !== _value) {
|
|
1173
1205
|
//this.logger.info(_value + "=" + newValue);
|
|
1174
1206
|
_value = newValue;
|
|
1175
1207
|
}
|
|
1176
|
-
info.value = _value;
|
|
1177
1208
|
try {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
await this.scrollIfNeeded(element, info);
|
|
1181
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1182
|
-
await this._highlightElements(element);
|
|
1209
|
+
await _preCommand(state, this);
|
|
1210
|
+
state.info.value = _value;
|
|
1183
1211
|
if (options === null || options === undefined || !options.press) {
|
|
1184
1212
|
try {
|
|
1185
|
-
let currentValue = await element.inputValue();
|
|
1213
|
+
let currentValue = await state.element.inputValue();
|
|
1186
1214
|
if (currentValue) {
|
|
1187
|
-
await element.fill("");
|
|
1215
|
+
await state.element.fill("");
|
|
1188
1216
|
}
|
|
1189
1217
|
}
|
|
1190
1218
|
catch (e) {
|
|
@@ -1193,22 +1221,22 @@ class StableBrowser {
|
|
|
1193
1221
|
}
|
|
1194
1222
|
if (options === null || options === undefined || options.press) {
|
|
1195
1223
|
try {
|
|
1196
|
-
await element.click({ timeout: 5000 });
|
|
1224
|
+
await state.element.click({ timeout: 5000 });
|
|
1197
1225
|
}
|
|
1198
1226
|
catch (e) {
|
|
1199
|
-
await element.dispatchEvent("click");
|
|
1227
|
+
await state.element.dispatchEvent("click");
|
|
1200
1228
|
}
|
|
1201
1229
|
}
|
|
1202
1230
|
else {
|
|
1203
1231
|
try {
|
|
1204
|
-
await element.focus();
|
|
1232
|
+
await state.element.focus();
|
|
1205
1233
|
}
|
|
1206
1234
|
catch (e) {
|
|
1207
|
-
await element.dispatchEvent("focus");
|
|
1235
|
+
await state.element.dispatchEvent("focus");
|
|
1208
1236
|
}
|
|
1209
1237
|
}
|
|
1210
1238
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1211
|
-
const valueSegment =
|
|
1239
|
+
const valueSegment = state.value.split("&&");
|
|
1212
1240
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1213
1241
|
if (i > 0) {
|
|
1214
1242
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1228,13 +1256,14 @@ class StableBrowser {
|
|
|
1228
1256
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1229
1257
|
}
|
|
1230
1258
|
}
|
|
1259
|
+
await _screenshot(state, this);
|
|
1231
1260
|
if (enter === true) {
|
|
1232
1261
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1233
1262
|
await this.page.keyboard.press("Enter");
|
|
1234
1263
|
await this.waitForPageLoad();
|
|
1235
1264
|
}
|
|
1236
1265
|
else if (enter === false) {
|
|
1237
|
-
await element.dispatchEvent("change");
|
|
1266
|
+
await state.element.dispatchEvent("change");
|
|
1238
1267
|
//await this.page.keyboard.press("Tab");
|
|
1239
1268
|
}
|
|
1240
1269
|
else {
|
|
@@ -1243,103 +1272,50 @@ class StableBrowser {
|
|
|
1243
1272
|
await this.waitForPageLoad();
|
|
1244
1273
|
}
|
|
1245
1274
|
}
|
|
1246
|
-
return info;
|
|
1275
|
+
return state.info;
|
|
1247
1276
|
}
|
|
1248
1277
|
catch (e) {
|
|
1249
|
-
|
|
1250
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1251
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1252
|
-
info.screenshotPath = screenshotPath;
|
|
1253
|
-
Object.assign(e, { info: info });
|
|
1254
|
-
error = e;
|
|
1255
|
-
throw e;
|
|
1278
|
+
await _commandError(state, e, this);
|
|
1256
1279
|
}
|
|
1257
1280
|
finally {
|
|
1258
|
-
|
|
1259
|
-
this._reportToWorld(world, {
|
|
1260
|
-
element_name: selectors.element_name,
|
|
1261
|
-
type: Types.FILL,
|
|
1262
|
-
screenshotId,
|
|
1263
|
-
value: _value,
|
|
1264
|
-
text: `clickType input with value: ${_value}`,
|
|
1265
|
-
result: error
|
|
1266
|
-
? {
|
|
1267
|
-
status: "FAILED",
|
|
1268
|
-
startTime,
|
|
1269
|
-
endTime,
|
|
1270
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1271
|
-
}
|
|
1272
|
-
: {
|
|
1273
|
-
status: "PASSED",
|
|
1274
|
-
startTime,
|
|
1275
|
-
endTime,
|
|
1276
|
-
},
|
|
1277
|
-
info: info,
|
|
1278
|
-
});
|
|
1281
|
+
_commandFinally(state, this);
|
|
1279
1282
|
}
|
|
1280
1283
|
}
|
|
1281
1284
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1285
|
+
const state = {
|
|
1286
|
+
selectors,
|
|
1287
|
+
_params,
|
|
1288
|
+
value: unEscapeString(value),
|
|
1289
|
+
options,
|
|
1290
|
+
world,
|
|
1291
|
+
type: Types.FILL,
|
|
1292
|
+
text: `Fill input with value: ${value}`,
|
|
1293
|
+
operation: "fill",
|
|
1294
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1295
|
+
};
|
|
1292
1296
|
try {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
await
|
|
1296
|
-
await element.fill(value);
|
|
1297
|
-
await element.dispatchEvent("change");
|
|
1297
|
+
await _preCommand(state, this);
|
|
1298
|
+
await state.element.fill(value);
|
|
1299
|
+
await state.element.dispatchEvent("change");
|
|
1298
1300
|
if (enter) {
|
|
1299
1301
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1300
1302
|
await this.page.keyboard.press("Enter");
|
|
1301
1303
|
}
|
|
1302
1304
|
await this.waitForPageLoad();
|
|
1303
|
-
return info;
|
|
1305
|
+
return state.info;
|
|
1304
1306
|
}
|
|
1305
1307
|
catch (e) {
|
|
1306
|
-
|
|
1307
|
-
this.logger.error("fill failed " + info.log);
|
|
1308
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1309
|
-
info.screenshotPath = screenshotPath;
|
|
1310
|
-
Object.assign(e, { info: info });
|
|
1311
|
-
error = e;
|
|
1312
|
-
throw e;
|
|
1308
|
+
await _commandError(state, e, this);
|
|
1313
1309
|
}
|
|
1314
1310
|
finally {
|
|
1315
|
-
|
|
1316
|
-
this._reportToWorld(world, {
|
|
1317
|
-
element_name: selectors.element_name,
|
|
1318
|
-
type: Types.FILL,
|
|
1319
|
-
screenshotId,
|
|
1320
|
-
value,
|
|
1321
|
-
text: `Fill input with value: ${value}`,
|
|
1322
|
-
result: error
|
|
1323
|
-
? {
|
|
1324
|
-
status: "FAILED",
|
|
1325
|
-
startTime,
|
|
1326
|
-
endTime,
|
|
1327
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1328
|
-
}
|
|
1329
|
-
: {
|
|
1330
|
-
status: "PASSED",
|
|
1331
|
-
startTime,
|
|
1332
|
-
endTime,
|
|
1333
|
-
},
|
|
1334
|
-
info: info,
|
|
1335
|
-
});
|
|
1311
|
+
_commandFinally(state, this);
|
|
1336
1312
|
}
|
|
1337
1313
|
}
|
|
1338
1314
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1339
1315
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1340
1316
|
}
|
|
1341
1317
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1342
|
-
|
|
1318
|
+
_validateSelectors(selectors);
|
|
1343
1319
|
let screenshotId = null;
|
|
1344
1320
|
let screenshotPath = null;
|
|
1345
1321
|
if (!info.log) {
|
|
@@ -1383,165 +1359,124 @@ class StableBrowser {
|
|
|
1383
1359
|
}
|
|
1384
1360
|
}
|
|
1385
1361
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1386
|
-
var _a;
|
|
1387
|
-
this._validateSelectors(selectors);
|
|
1388
1362
|
if (!pattern) {
|
|
1389
1363
|
throw new Error("pattern is null");
|
|
1390
1364
|
}
|
|
1391
1365
|
if (!text) {
|
|
1392
1366
|
throw new Error("text is null");
|
|
1393
1367
|
}
|
|
1368
|
+
const state = {
|
|
1369
|
+
selectors,
|
|
1370
|
+
_params,
|
|
1371
|
+
pattern,
|
|
1372
|
+
value: pattern,
|
|
1373
|
+
options,
|
|
1374
|
+
world,
|
|
1375
|
+
locate: false,
|
|
1376
|
+
scroll: false,
|
|
1377
|
+
screenshot: false,
|
|
1378
|
+
highlight: false,
|
|
1379
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1380
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1381
|
+
operation: "containsPattern",
|
|
1382
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1383
|
+
};
|
|
1394
1384
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1395
1385
|
if (newValue !== text) {
|
|
1396
1386
|
this.logger.info(text + "=" + newValue);
|
|
1397
1387
|
text = newValue;
|
|
1398
1388
|
}
|
|
1399
|
-
const startTime = Date.now();
|
|
1400
|
-
let error = null;
|
|
1401
|
-
let screenshotId = null;
|
|
1402
|
-
let screenshotPath = null;
|
|
1403
|
-
const info = {};
|
|
1404
|
-
info.log =
|
|
1405
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1406
|
-
info.operation = "containsPattern";
|
|
1407
|
-
info.selectors = selectors;
|
|
1408
|
-
info.value = text;
|
|
1409
|
-
info.pattern = pattern;
|
|
1410
1389
|
let foundObj = null;
|
|
1411
1390
|
try {
|
|
1412
|
-
|
|
1391
|
+
await _preCommand(state, this);
|
|
1392
|
+
state.info.pattern = pattern;
|
|
1393
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1413
1394
|
if (foundObj && foundObj.element) {
|
|
1414
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1395
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1415
1396
|
}
|
|
1416
|
-
|
|
1397
|
+
await _screenshot(state, this);
|
|
1417
1398
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1418
1399
|
pattern = pattern.replace("{text}", escapedText);
|
|
1419
1400
|
let regex = new RegExp(pattern, "im");
|
|
1420
|
-
if (!regex.test(foundObj
|
|
1421
|
-
info.foundText = foundObj
|
|
1401
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1402
|
+
state.info.foundText = foundObj?.text;
|
|
1422
1403
|
throw new Error("element doesn't contain text " + text);
|
|
1423
1404
|
}
|
|
1424
|
-
return info;
|
|
1405
|
+
return state.info;
|
|
1425
1406
|
}
|
|
1426
1407
|
catch (e) {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1430
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1431
|
-
info.screenshotPath = screenshotPath;
|
|
1432
|
-
Object.assign(e, { info: info });
|
|
1433
|
-
error = e;
|
|
1434
|
-
throw e;
|
|
1408
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1409
|
+
await _commandError(state, e, this);
|
|
1435
1410
|
}
|
|
1436
1411
|
finally {
|
|
1437
|
-
|
|
1438
|
-
this._reportToWorld(world, {
|
|
1439
|
-
element_name: selectors.element_name,
|
|
1440
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1441
|
-
value: pattern,
|
|
1442
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1443
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1444
|
-
result: error
|
|
1445
|
-
? {
|
|
1446
|
-
status: "FAILED",
|
|
1447
|
-
startTime,
|
|
1448
|
-
endTime,
|
|
1449
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1450
|
-
}
|
|
1451
|
-
: {
|
|
1452
|
-
status: "PASSED",
|
|
1453
|
-
startTime,
|
|
1454
|
-
endTime,
|
|
1455
|
-
},
|
|
1456
|
-
info: info,
|
|
1457
|
-
});
|
|
1412
|
+
_commandFinally(state, this);
|
|
1458
1413
|
}
|
|
1459
1414
|
}
|
|
1460
1415
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1461
|
-
|
|
1462
|
-
|
|
1416
|
+
const state = {
|
|
1417
|
+
selectors,
|
|
1418
|
+
_params,
|
|
1419
|
+
value: text,
|
|
1420
|
+
options,
|
|
1421
|
+
world,
|
|
1422
|
+
locate: false,
|
|
1423
|
+
scroll: false,
|
|
1424
|
+
screenshot: false,
|
|
1425
|
+
highlight: false,
|
|
1426
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1427
|
+
text: `Verify element contains text: ${text}`,
|
|
1428
|
+
operation: "containsText",
|
|
1429
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1430
|
+
};
|
|
1463
1431
|
if (!text) {
|
|
1464
1432
|
throw new Error("text is null");
|
|
1465
1433
|
}
|
|
1466
|
-
|
|
1467
|
-
let error = null;
|
|
1468
|
-
let screenshotId = null;
|
|
1469
|
-
let screenshotPath = null;
|
|
1470
|
-
const info = {};
|
|
1471
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1472
|
-
info.operation = "containsText";
|
|
1473
|
-
info.selectors = selectors;
|
|
1434
|
+
text = unEscapeString(text);
|
|
1474
1435
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1475
1436
|
if (newValue !== text) {
|
|
1476
1437
|
this.logger.info(text + "=" + newValue);
|
|
1477
1438
|
text = newValue;
|
|
1478
1439
|
}
|
|
1479
|
-
info.value = text;
|
|
1480
1440
|
let foundObj = null;
|
|
1481
1441
|
try {
|
|
1482
|
-
|
|
1442
|
+
await _preCommand(state, this);
|
|
1443
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1483
1444
|
if (foundObj && foundObj.element) {
|
|
1484
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1445
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1485
1446
|
}
|
|
1486
|
-
|
|
1447
|
+
await _screenshot(state, this);
|
|
1487
1448
|
const dateAlternatives = findDateAlternatives(text);
|
|
1488
1449
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1489
1450
|
if (dateAlternatives.date) {
|
|
1490
1451
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1491
|
-
if (
|
|
1492
|
-
|
|
1493
|
-
return info;
|
|
1452
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1453
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1454
|
+
return state.info;
|
|
1494
1455
|
}
|
|
1495
1456
|
}
|
|
1496
1457
|
throw new Error("element doesn't contain text " + text);
|
|
1497
1458
|
}
|
|
1498
1459
|
else if (numberAlternatives.number) {
|
|
1499
1460
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1500
|
-
if (
|
|
1501
|
-
|
|
1502
|
-
return info;
|
|
1461
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1462
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1463
|
+
return state.info;
|
|
1503
1464
|
}
|
|
1504
1465
|
}
|
|
1505
1466
|
throw new Error("element doesn't contain text " + text);
|
|
1506
1467
|
}
|
|
1507
|
-
else if (!
|
|
1508
|
-
info.foundText = foundObj
|
|
1509
|
-
info.value = foundObj
|
|
1468
|
+
else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
|
|
1469
|
+
state.info.foundText = foundObj?.text;
|
|
1470
|
+
state.info.value = foundObj?.value;
|
|
1510
1471
|
throw new Error("element doesn't contain text " + text);
|
|
1511
1472
|
}
|
|
1512
|
-
return info;
|
|
1473
|
+
return state.info;
|
|
1513
1474
|
}
|
|
1514
1475
|
catch (e) {
|
|
1515
|
-
|
|
1516
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1517
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1518
|
-
info.screenshotPath = screenshotPath;
|
|
1519
|
-
Object.assign(e, { info: info });
|
|
1520
|
-
error = e;
|
|
1521
|
-
throw e;
|
|
1476
|
+
await _commandError(state, e, this);
|
|
1522
1477
|
}
|
|
1523
1478
|
finally {
|
|
1524
|
-
|
|
1525
|
-
this._reportToWorld(world, {
|
|
1526
|
-
element_name: selectors.element_name,
|
|
1527
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1528
|
-
text: `Verify element contains text: ${text}`,
|
|
1529
|
-
value: text,
|
|
1530
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1531
|
-
result: error
|
|
1532
|
-
? {
|
|
1533
|
-
status: "FAILED",
|
|
1534
|
-
startTime,
|
|
1535
|
-
endTime,
|
|
1536
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1537
|
-
}
|
|
1538
|
-
: {
|
|
1539
|
-
status: "PASSED",
|
|
1540
|
-
startTime,
|
|
1541
|
-
endTime,
|
|
1542
|
-
},
|
|
1543
|
-
info: info,
|
|
1544
|
-
});
|
|
1479
|
+
_commandFinally(state, this);
|
|
1545
1480
|
}
|
|
1546
1481
|
}
|
|
1547
1482
|
_getDataFile(world = null) {
|
|
@@ -1562,7 +1497,10 @@ class StableBrowser {
|
|
|
1562
1497
|
}
|
|
1563
1498
|
async waitForUserInput(message, world = null) {
|
|
1564
1499
|
if (!message) {
|
|
1565
|
-
message = "Press any key to continue";
|
|
1500
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1501
|
+
}
|
|
1502
|
+
else {
|
|
1503
|
+
message = "# Wait for user input. " + message;
|
|
1566
1504
|
}
|
|
1567
1505
|
message += "\n";
|
|
1568
1506
|
const value = await new Promise((resolve) => {
|
|
@@ -1767,7 +1705,6 @@ class StableBrowser {
|
|
|
1767
1705
|
}
|
|
1768
1706
|
async takeScreenshot(screenshotPath) {
|
|
1769
1707
|
const playContext = this.context.playContext;
|
|
1770
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1771
1708
|
// Using CDP to capture the screenshot
|
|
1772
1709
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1773
1710
|
document.body.scrollWidth,
|
|
@@ -1777,97 +1714,67 @@ class StableBrowser {
|
|
|
1777
1714
|
document.body.clientWidth,
|
|
1778
1715
|
document.documentElement.clientWidth,
|
|
1779
1716
|
])));
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
await client.detach();
|
|
1717
|
+
let screenshotBuffer = null;
|
|
1718
|
+
if (this.context.browserName === "chromium") {
|
|
1719
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1720
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1721
|
+
format: "png",
|
|
1722
|
+
// clip: {
|
|
1723
|
+
// x: 0,
|
|
1724
|
+
// y: 0,
|
|
1725
|
+
// width: viewportWidth,
|
|
1726
|
+
// height: viewportHeight,
|
|
1727
|
+
// scale: 1,
|
|
1728
|
+
// },
|
|
1729
|
+
});
|
|
1730
|
+
await client.detach();
|
|
1731
|
+
if (!screenshotPath) {
|
|
1732
|
+
return data;
|
|
1733
|
+
}
|
|
1734
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1735
|
+
}
|
|
1736
|
+
else {
|
|
1737
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1738
|
+
}
|
|
1739
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1740
|
+
// Get the image dimensions
|
|
1741
|
+
const { width, height } = image.bitmap;
|
|
1742
|
+
const resizeRatio = viewportWidth / width;
|
|
1743
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1744
|
+
if (width > viewportWidth) {
|
|
1745
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1746
|
+
await image.write(screenshotPath);
|
|
1747
|
+
}
|
|
1748
|
+
else {
|
|
1749
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1750
|
+
}
|
|
1815
1751
|
}
|
|
1816
1752
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1753
|
+
const state = {
|
|
1754
|
+
selectors,
|
|
1755
|
+
_params,
|
|
1756
|
+
options,
|
|
1757
|
+
world,
|
|
1758
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1759
|
+
text: `Verify element exists in page`,
|
|
1760
|
+
operation: "verifyElementExistInPage",
|
|
1761
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1762
|
+
};
|
|
1822
1763
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1823
|
-
const info = {};
|
|
1824
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1825
|
-
info.operation = "verify";
|
|
1826
|
-
info.selectors = selectors;
|
|
1827
1764
|
try {
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
}
|
|
1832
|
-
await this._highlightElements(element);
|
|
1833
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1834
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1835
|
-
return info;
|
|
1765
|
+
await _preCommand(state, this);
|
|
1766
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1767
|
+
return state.info;
|
|
1836
1768
|
}
|
|
1837
1769
|
catch (e) {
|
|
1838
|
-
|
|
1839
|
-
this.logger.error("verify failed " + info.log);
|
|
1840
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1841
|
-
info.screenshotPath = screenshotPath;
|
|
1842
|
-
Object.assign(e, { info: info });
|
|
1843
|
-
error = e;
|
|
1844
|
-
throw e;
|
|
1770
|
+
await _commandError(state, e, this);
|
|
1845
1771
|
}
|
|
1846
1772
|
finally {
|
|
1847
|
-
|
|
1848
|
-
this._reportToWorld(world, {
|
|
1849
|
-
element_name: selectors.element_name,
|
|
1850
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1851
|
-
text: "Verify element exists in page",
|
|
1852
|
-
screenshotId,
|
|
1853
|
-
result: error
|
|
1854
|
-
? {
|
|
1855
|
-
status: "FAILED",
|
|
1856
|
-
startTime,
|
|
1857
|
-
endTime,
|
|
1858
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1859
|
-
}
|
|
1860
|
-
: {
|
|
1861
|
-
status: "PASSED",
|
|
1862
|
-
startTime,
|
|
1863
|
-
endTime,
|
|
1864
|
-
},
|
|
1865
|
-
info: info,
|
|
1866
|
-
});
|
|
1773
|
+
_commandFinally(state, this);
|
|
1867
1774
|
}
|
|
1868
1775
|
}
|
|
1869
1776
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1870
|
-
|
|
1777
|
+
_validateSelectors(selectors);
|
|
1871
1778
|
const startTime = Date.now();
|
|
1872
1779
|
let error = null;
|
|
1873
1780
|
let screenshotId = null;
|
|
@@ -1926,7 +1833,7 @@ class StableBrowser {
|
|
|
1926
1833
|
status: "FAILED",
|
|
1927
1834
|
startTime,
|
|
1928
1835
|
endTime,
|
|
1929
|
-
message: error
|
|
1836
|
+
message: error?.message,
|
|
1930
1837
|
}
|
|
1931
1838
|
: {
|
|
1932
1839
|
status: "PASSED",
|
|
@@ -2135,7 +2042,7 @@ class StableBrowser {
|
|
|
2135
2042
|
status: "FAILED",
|
|
2136
2043
|
startTime,
|
|
2137
2044
|
endTime,
|
|
2138
|
-
message: error
|
|
2045
|
+
message: error?.message,
|
|
2139
2046
|
}
|
|
2140
2047
|
: {
|
|
2141
2048
|
status: "PASSED",
|
|
@@ -2171,20 +2078,20 @@ class StableBrowser {
|
|
|
2171
2078
|
for (let i = 0; i < frames.length; i++) {
|
|
2172
2079
|
if (dateAlternatives.date) {
|
|
2173
2080
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2174
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
|
|
2081
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
|
|
2175
2082
|
result.frame = frames[i];
|
|
2176
2083
|
results.push(result);
|
|
2177
2084
|
}
|
|
2178
2085
|
}
|
|
2179
2086
|
else if (numberAlternatives.number) {
|
|
2180
2087
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2181
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
|
|
2088
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
|
|
2182
2089
|
result.frame = frames[i];
|
|
2183
2090
|
results.push(result);
|
|
2184
2091
|
}
|
|
2185
2092
|
}
|
|
2186
2093
|
else {
|
|
2187
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, {});
|
|
2094
|
+
const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
|
|
2188
2095
|
result.frame = frames[i];
|
|
2189
2096
|
results.push(result);
|
|
2190
2097
|
}
|
|
@@ -2233,7 +2140,7 @@ class StableBrowser {
|
|
|
2233
2140
|
status: "FAILED",
|
|
2234
2141
|
startTime,
|
|
2235
2142
|
endTime,
|
|
2236
|
-
message: error
|
|
2143
|
+
message: error?.message,
|
|
2237
2144
|
}
|
|
2238
2145
|
: {
|
|
2239
2146
|
status: "PASSED",
|
|
@@ -2317,7 +2224,7 @@ class StableBrowser {
|
|
|
2317
2224
|
status: "FAILED",
|
|
2318
2225
|
startTime,
|
|
2319
2226
|
endTime,
|
|
2320
|
-
message: error
|
|
2227
|
+
message: error?.message,
|
|
2321
2228
|
}
|
|
2322
2229
|
: {
|
|
2323
2230
|
status: "PASSED",
|
|
@@ -2349,7 +2256,7 @@ class StableBrowser {
|
|
|
2349
2256
|
this.logger.info("Table data verified");
|
|
2350
2257
|
}
|
|
2351
2258
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2352
|
-
|
|
2259
|
+
_validateSelectors(selectors);
|
|
2353
2260
|
const startTime = Date.now();
|
|
2354
2261
|
let error = null;
|
|
2355
2262
|
let screenshotId = null;
|
|
@@ -2385,7 +2292,7 @@ class StableBrowser {
|
|
|
2385
2292
|
status: "FAILED",
|
|
2386
2293
|
startTime,
|
|
2387
2294
|
endTime,
|
|
2388
|
-
message: error
|
|
2295
|
+
message: error?.message,
|
|
2389
2296
|
}
|
|
2390
2297
|
: {
|
|
2391
2298
|
status: "PASSED",
|
|
@@ -2397,7 +2304,7 @@ class StableBrowser {
|
|
|
2397
2304
|
}
|
|
2398
2305
|
}
|
|
2399
2306
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2400
|
-
|
|
2307
|
+
_validateSelectors(selectors);
|
|
2401
2308
|
if (!query) {
|
|
2402
2309
|
throw new Error("query is null");
|
|
2403
2310
|
}
|
|
@@ -2550,7 +2457,7 @@ class StableBrowser {
|
|
|
2550
2457
|
status: "FAILED",
|
|
2551
2458
|
startTime,
|
|
2552
2459
|
endTime,
|
|
2553
|
-
message: error
|
|
2460
|
+
message: error?.message,
|
|
2554
2461
|
}
|
|
2555
2462
|
: {
|
|
2556
2463
|
status: "PASSED",
|
|
@@ -2562,27 +2469,7 @@ class StableBrowser {
|
|
|
2562
2469
|
}
|
|
2563
2470
|
}
|
|
2564
2471
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2565
|
-
|
|
2566
|
-
return value;
|
|
2567
|
-
}
|
|
2568
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2569
|
-
let regex = /{{(.*?)}}/g;
|
|
2570
|
-
let matches = value.match(regex);
|
|
2571
|
-
if (matches) {
|
|
2572
|
-
const testData = this.getTestData(world);
|
|
2573
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2574
|
-
let match = matches[i];
|
|
2575
|
-
let key = match.substring(2, match.length - 2);
|
|
2576
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2577
|
-
if (newValue !== null) {
|
|
2578
|
-
value = value.replace(match, newValue);
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2583
|
-
return await decrypt(value, null, totpWait);
|
|
2584
|
-
}
|
|
2585
|
-
return value;
|
|
2472
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2586
2473
|
}
|
|
2587
2474
|
_getLoadTimeout(options) {
|
|
2588
2475
|
let timeout = 15000;
|
|
@@ -2642,7 +2529,7 @@ class StableBrowser {
|
|
|
2642
2529
|
status: "FAILED",
|
|
2643
2530
|
startTime,
|
|
2644
2531
|
endTime,
|
|
2645
|
-
message: error
|
|
2532
|
+
message: error?.message,
|
|
2646
2533
|
}
|
|
2647
2534
|
: {
|
|
2648
2535
|
status: "PASSED",
|
|
@@ -2677,7 +2564,7 @@ class StableBrowser {
|
|
|
2677
2564
|
status: "FAILED",
|
|
2678
2565
|
startTime,
|
|
2679
2566
|
endTime,
|
|
2680
|
-
message: error
|
|
2567
|
+
message: error?.message,
|
|
2681
2568
|
}
|
|
2682
2569
|
: {
|
|
2683
2570
|
status: "PASSED",
|
|
@@ -2719,7 +2606,7 @@ class StableBrowser {
|
|
|
2719
2606
|
status: "FAILED",
|
|
2720
2607
|
startTime,
|
|
2721
2608
|
endTime,
|
|
2722
|
-
message: error
|
|
2609
|
+
message: error?.message,
|
|
2723
2610
|
}
|
|
2724
2611
|
: {
|
|
2725
2612
|
status: "PASSED",
|
|
@@ -2755,7 +2642,7 @@ class StableBrowser {
|
|
|
2755
2642
|
status: "FAILED",
|
|
2756
2643
|
startTime,
|
|
2757
2644
|
endTime,
|
|
2758
|
-
message: error
|
|
2645
|
+
message: error?.message,
|
|
2759
2646
|
}
|
|
2760
2647
|
: {
|
|
2761
2648
|
status: "PASSED",
|
|
@@ -2940,5 +2827,10 @@ const KEYBOARD_EVENTS = [
|
|
|
2940
2827
|
"TVAntennaCable",
|
|
2941
2828
|
"TVAudioDescription",
|
|
2942
2829
|
];
|
|
2830
|
+
function unEscapeString(str) {
|
|
2831
|
+
const placeholder = "__NEWLINE__";
|
|
2832
|
+
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2833
|
+
return str;
|
|
2834
|
+
}
|
|
2943
2835
|
export { StableBrowser };
|
|
2944
2836
|
//# sourceMappingURL=stable_browser.js.map
|