automation_model 1.0.401-dev → 1.0.401-main
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 +202 -15
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +1 -1
- package/lib/auto_page.js +26 -17
- package/lib/auto_page.js.map +1 -1
- package/lib/axe/axe.mini.js +12 -0
- package/lib/browser_manager.js +22 -11
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +5 -0
- package/lib/command_common.js +131 -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/error-messages.d.ts +6 -0
- package/lib/error-messages.js +182 -0
- package/lib/error-messages.js.map +1 -0
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +51 -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 +3 -0
- package/lib/network.js +144 -0
- package/lib/network.js.map +1 -0
- package/lib/stable_browser.d.ts +36 -21
- package/lib/stable_browser.js +813 -877
- package/lib/stable_browser.js.map +1 -1
- package/lib/test_context.d.ts +3 -0
- package/lib/test_context.js +11 -9
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +3 -1
- package/lib/utils.js +70 -3
- package/lib/utils.js.map +1 -1
- package/package.json +10 -7
package/lib/stable_browser.js
CHANGED
|
@@ -2,17 +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";
|
|
15
|
+
import { Readable } from "node:stream";
|
|
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 { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
16
21
|
const Types = {
|
|
17
22
|
CLICK: "click_element",
|
|
18
23
|
NAVIGATE: "navigate",
|
|
@@ -37,16 +42,27 @@ const Types = {
|
|
|
37
42
|
SET_DATE_TIME: "set_date_time",
|
|
38
43
|
SET_VIEWPORT: "set_viewport",
|
|
39
44
|
VERIFY_VISUAL: "verify_visual",
|
|
45
|
+
LOAD_DATA: "load_data",
|
|
46
|
+
SET_INPUT: "set_input",
|
|
40
47
|
};
|
|
48
|
+
export const apps = {};
|
|
41
49
|
class StableBrowser {
|
|
42
|
-
|
|
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) {
|
|
43
61
|
this.browser = browser;
|
|
44
62
|
this.page = page;
|
|
45
63
|
this.logger = logger;
|
|
46
64
|
this.context = context;
|
|
47
|
-
this.
|
|
48
|
-
this.webLogFile = null;
|
|
49
|
-
this.configuration = null;
|
|
65
|
+
this.world = world;
|
|
50
66
|
if (!this.logger) {
|
|
51
67
|
this.logger = console;
|
|
52
68
|
}
|
|
@@ -72,19 +88,45 @@ class StableBrowser {
|
|
|
72
88
|
this.logger.error("unable to read ai_config.json");
|
|
73
89
|
}
|
|
74
90
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
75
|
-
this.
|
|
76
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
77
|
-
this.registerRequestListener();
|
|
91
|
+
this.world = world;
|
|
78
92
|
context.pages = [this.page];
|
|
79
93
|
context.pageLoading = { status: false };
|
|
94
|
+
this.registerEventListeners(this.context);
|
|
95
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
96
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
97
|
+
}
|
|
98
|
+
registerEventListeners(context) {
|
|
99
|
+
this.registerConsoleLogListener(this.page, context);
|
|
100
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
101
|
+
if (!context.pageLoading) {
|
|
102
|
+
context.pageLoading = { status: false };
|
|
103
|
+
}
|
|
80
104
|
context.playContext.on("page", async function (page) {
|
|
105
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
106
|
+
console.log("close unexpected popups");
|
|
107
|
+
await page.close();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
81
110
|
context.pageLoading.status = true;
|
|
82
111
|
this.page = page;
|
|
83
112
|
context.page = page;
|
|
84
113
|
context.pages.push(page);
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
87
|
-
|
|
114
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
115
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
116
|
+
page.on("close", async () => {
|
|
117
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
118
|
+
this.context.pages.pop();
|
|
119
|
+
this.page = this.context.pages[this.context.pages.length - 1];
|
|
120
|
+
this.context.page = this.page;
|
|
121
|
+
try {
|
|
122
|
+
let title = await this.page.title();
|
|
123
|
+
console.log("Switched to page " + title);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
console.error("Error on page close", error);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
88
130
|
try {
|
|
89
131
|
await this.waitForPageLoad();
|
|
90
132
|
console.log("Switch page: " + (await page.title()));
|
|
@@ -95,6 +137,36 @@ class StableBrowser {
|
|
|
95
137
|
context.pageLoading.status = false;
|
|
96
138
|
}.bind(this));
|
|
97
139
|
}
|
|
140
|
+
async switchApp(appName) {
|
|
141
|
+
// check if the current app (this.appName) is the same as the new app
|
|
142
|
+
if (this.appName === appName) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
let navigate = false;
|
|
146
|
+
if (!apps[appName]) {
|
|
147
|
+
let newContext = await getContext(null, false, this.logger, appName, false, this);
|
|
148
|
+
navigate = true;
|
|
149
|
+
apps[appName] = {
|
|
150
|
+
context: newContext,
|
|
151
|
+
browser: newContext.browser,
|
|
152
|
+
page: newContext.page,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const tempContext = {};
|
|
156
|
+
this._copyContext(this, tempContext);
|
|
157
|
+
this._copyContext(apps[appName], this);
|
|
158
|
+
apps[this.appName] = tempContext;
|
|
159
|
+
this.appName = appName;
|
|
160
|
+
if (navigate) {
|
|
161
|
+
await this.goto(this.context.environment.baseUrl);
|
|
162
|
+
await this.waitForPageLoad();
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
_copyContext(from, to) {
|
|
166
|
+
to.browser = from.browser;
|
|
167
|
+
to.page = from.page;
|
|
168
|
+
to.context = from.context;
|
|
169
|
+
}
|
|
98
170
|
getWebLogFile(logFolder) {
|
|
99
171
|
if (!fs.existsSync(logFolder)) {
|
|
100
172
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -106,37 +178,63 @@ class StableBrowser {
|
|
|
106
178
|
const fileName = nextIndex + ".json";
|
|
107
179
|
return path.join(logFolder, fileName);
|
|
108
180
|
}
|
|
109
|
-
registerConsoleLogListener(page, context
|
|
181
|
+
registerConsoleLogListener(page, context) {
|
|
110
182
|
if (!this.context.webLogger) {
|
|
111
183
|
this.context.webLogger = [];
|
|
112
184
|
}
|
|
113
185
|
page.on("console", async (msg) => {
|
|
114
|
-
|
|
186
|
+
const obj = {
|
|
115
187
|
type: msg.type(),
|
|
116
188
|
text: msg.text(),
|
|
117
189
|
location: msg.location(),
|
|
118
190
|
time: new Date().toISOString(),
|
|
119
|
-
}
|
|
120
|
-
|
|
191
|
+
};
|
|
192
|
+
this.context.webLogger.push(obj);
|
|
193
|
+
if (msg.type() === "error") {
|
|
194
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
195
|
+
}
|
|
121
196
|
});
|
|
122
197
|
}
|
|
123
|
-
registerRequestListener() {
|
|
124
|
-
this.
|
|
198
|
+
registerRequestListener(page, context, logFile) {
|
|
199
|
+
if (!this.context.networkLogger) {
|
|
200
|
+
this.context.networkLogger = [];
|
|
201
|
+
}
|
|
202
|
+
page.on("request", async (data) => {
|
|
203
|
+
const startTime = new Date().getTime();
|
|
125
204
|
try {
|
|
126
|
-
const pageUrl = new URL(
|
|
205
|
+
const pageUrl = new URL(page.url());
|
|
127
206
|
const requestUrl = new URL(data.url());
|
|
128
207
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
129
208
|
const method = data.method();
|
|
130
|
-
if (
|
|
209
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
131
210
|
const token = await data.headerValue("Authorization");
|
|
132
211
|
if (token) {
|
|
133
|
-
|
|
212
|
+
context.authtoken = token;
|
|
134
213
|
}
|
|
135
214
|
}
|
|
136
215
|
}
|
|
216
|
+
const response = await data.response();
|
|
217
|
+
const endTime = new Date().getTime();
|
|
218
|
+
const obj = {
|
|
219
|
+
url: data.url(),
|
|
220
|
+
method: data.method(),
|
|
221
|
+
postData: data.postData(),
|
|
222
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
223
|
+
duration: endTime - startTime,
|
|
224
|
+
startTime,
|
|
225
|
+
};
|
|
226
|
+
context.networkLogger.push(obj);
|
|
227
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
137
228
|
}
|
|
138
229
|
catch (error) {
|
|
139
230
|
console.error("Error in request listener", error);
|
|
231
|
+
context.networkLogger.push({
|
|
232
|
+
error: "not able to listen",
|
|
233
|
+
message: error.message,
|
|
234
|
+
stack: error.stack,
|
|
235
|
+
time: new Date().toISOString(),
|
|
236
|
+
});
|
|
237
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
140
238
|
}
|
|
141
239
|
});
|
|
142
240
|
}
|
|
@@ -151,20 +249,6 @@ class StableBrowser {
|
|
|
151
249
|
timeout: 60000,
|
|
152
250
|
});
|
|
153
251
|
}
|
|
154
|
-
_validateSelectors(selectors) {
|
|
155
|
-
if (!selectors) {
|
|
156
|
-
throw new Error("selectors is null");
|
|
157
|
-
}
|
|
158
|
-
if (!selectors.locators) {
|
|
159
|
-
throw new Error("selectors.locators is null");
|
|
160
|
-
}
|
|
161
|
-
if (!Array.isArray(selectors.locators)) {
|
|
162
|
-
throw new Error("selectors.locators expected to be array");
|
|
163
|
-
}
|
|
164
|
-
if (selectors.locators.length === 0) {
|
|
165
|
-
throw new Error("selectors.locators expected to be non empty array");
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
252
|
_fixUsingParams(text, _params) {
|
|
169
253
|
if (!_params || typeof text !== "string") {
|
|
170
254
|
return text;
|
|
@@ -179,35 +263,84 @@ class StableBrowser {
|
|
|
179
263
|
}
|
|
180
264
|
return text;
|
|
181
265
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
266
|
+
_fixLocatorUsingParams(locator, _params) {
|
|
267
|
+
// check if not null
|
|
268
|
+
if (!locator) {
|
|
269
|
+
return locator;
|
|
270
|
+
}
|
|
271
|
+
// clone the locator
|
|
272
|
+
locator = JSON.parse(JSON.stringify(locator));
|
|
273
|
+
this.scanAndManipulate(locator, _params);
|
|
274
|
+
return locator;
|
|
275
|
+
}
|
|
276
|
+
_isObject(value) {
|
|
277
|
+
return value && typeof value === "object" && value.constructor === Object;
|
|
278
|
+
}
|
|
279
|
+
scanAndManipulate(currentObj, _params) {
|
|
280
|
+
for (const key in currentObj) {
|
|
281
|
+
if (typeof currentObj[key] === "string") {
|
|
282
|
+
// Perform string manipulation
|
|
283
|
+
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
284
|
+
}
|
|
285
|
+
else if (this._isObject(currentObj[key])) {
|
|
286
|
+
// Recursively scan nested objects
|
|
287
|
+
this.scanAndManipulate(currentObj[key], _params);
|
|
288
|
+
}
|
|
185
289
|
}
|
|
290
|
+
}
|
|
291
|
+
_getLocator(locator, scope, _params) {
|
|
292
|
+
locator = this._fixLocatorUsingParams(locator, _params);
|
|
293
|
+
let locatorReturn;
|
|
186
294
|
if (locator.role) {
|
|
187
295
|
if (locator.role[1].nameReg) {
|
|
188
296
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
189
297
|
delete locator.role[1].nameReg;
|
|
190
298
|
}
|
|
191
|
-
if (locator.role[1].name) {
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
299
|
+
// if (locator.role[1].name) {
|
|
300
|
+
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
301
|
+
// }
|
|
302
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
195
303
|
}
|
|
196
304
|
if (locator.css) {
|
|
197
|
-
|
|
305
|
+
locatorReturn = scope.locator(locator.css);
|
|
198
306
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
307
|
+
// handle role/name locators
|
|
308
|
+
// locator.selector will be something like: textbox[name="Username"i]
|
|
309
|
+
if (locator.engine === "internal:role") {
|
|
310
|
+
// extract the role, name and the i/s flags using regex
|
|
311
|
+
const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
|
|
312
|
+
if (match) {
|
|
313
|
+
const role = match[1];
|
|
314
|
+
const name = match[3];
|
|
315
|
+
const flags = match[4];
|
|
316
|
+
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
203
317
|
}
|
|
204
|
-
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
205
|
-
return locator;
|
|
206
318
|
}
|
|
207
|
-
|
|
319
|
+
if (locator?.engine) {
|
|
320
|
+
if (locator.engine === "css") {
|
|
321
|
+
locatorReturn = scope.locator(locator.selector);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
let selector = locator.selector;
|
|
325
|
+
if (locator.engine === "internal:attr") {
|
|
326
|
+
if (!selector.startsWith("[")) {
|
|
327
|
+
selector = `[${selector}]`;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (!locatorReturn) {
|
|
334
|
+
console.error(locator);
|
|
335
|
+
throw new Error("Locator undefined");
|
|
336
|
+
}
|
|
337
|
+
return locatorReturn;
|
|
208
338
|
}
|
|
209
339
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
210
|
-
|
|
340
|
+
if (css && css.locator) {
|
|
341
|
+
css = css.locator;
|
|
342
|
+
}
|
|
343
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
211
344
|
if (result.elementCount === 0) {
|
|
212
345
|
return;
|
|
213
346
|
}
|
|
@@ -222,7 +355,7 @@ class StableBrowser {
|
|
|
222
355
|
}
|
|
223
356
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
224
357
|
//const stringifyText = JSON.stringify(text);
|
|
225
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
358
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
226
359
|
function isParent(parent, child) {
|
|
227
360
|
let currentNode = child.parentNode;
|
|
228
361
|
while (currentNode !== null) {
|
|
@@ -234,6 +367,15 @@ class StableBrowser {
|
|
|
234
367
|
return false;
|
|
235
368
|
}
|
|
236
369
|
document.isParent = isParent;
|
|
370
|
+
function getRegex(str) {
|
|
371
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
372
|
+
if (!match) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
let [_, pattern, flags] = match;
|
|
376
|
+
return new RegExp(pattern, flags);
|
|
377
|
+
}
|
|
378
|
+
document.getRegex = getRegex;
|
|
237
379
|
function collectAllShadowDomElements(element, result = []) {
|
|
238
380
|
// Check and add the element if it has a shadow root
|
|
239
381
|
if (element.shadowRoot) {
|
|
@@ -250,7 +392,11 @@ class StableBrowser {
|
|
|
250
392
|
}
|
|
251
393
|
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
252
394
|
if (!tag) {
|
|
253
|
-
tag = "
|
|
395
|
+
tag = "*:not(script, style, head)";
|
|
396
|
+
}
|
|
397
|
+
let regexpSearch = document.getRegex(text);
|
|
398
|
+
if (regexpSearch) {
|
|
399
|
+
regex = true;
|
|
254
400
|
}
|
|
255
401
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
256
402
|
let shadowHosts = [];
|
|
@@ -267,7 +413,9 @@ class StableBrowser {
|
|
|
267
413
|
let randomToken = null;
|
|
268
414
|
const foundElements = [];
|
|
269
415
|
if (regex) {
|
|
270
|
-
|
|
416
|
+
if (!regexpSearch) {
|
|
417
|
+
regexpSearch = new RegExp(text, "im");
|
|
418
|
+
}
|
|
271
419
|
for (let i = 0; i < elements.length; i++) {
|
|
272
420
|
const element = elements[i];
|
|
273
421
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -281,8 +429,8 @@ class StableBrowser {
|
|
|
281
429
|
for (let i = 0; i < elements.length; i++) {
|
|
282
430
|
const element = elements[i];
|
|
283
431
|
if (partial) {
|
|
284
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
285
|
-
(element.value && element.value.includes(text))) {
|
|
432
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
433
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
286
434
|
foundElements.push(element);
|
|
287
435
|
}
|
|
288
436
|
}
|
|
@@ -327,18 +475,29 @@ class StableBrowser {
|
|
|
327
475
|
}
|
|
328
476
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
329
477
|
let locatorSearch = selectorHierarchy[index];
|
|
478
|
+
try {
|
|
479
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
console.error(e);
|
|
483
|
+
}
|
|
330
484
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
331
485
|
let locator = null;
|
|
332
486
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
333
487
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
334
488
|
if (!locatorString) {
|
|
489
|
+
info.failCause.textNotFound = true;
|
|
490
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
335
491
|
return;
|
|
336
492
|
}
|
|
337
493
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
338
494
|
}
|
|
339
495
|
else if (locatorSearch.text) {
|
|
340
|
-
let
|
|
496
|
+
let text = this._fixUsingParams(locatorSearch.text, _params);
|
|
497
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
341
498
|
if (result.elementCount === 0) {
|
|
499
|
+
info.failCause.textNotFound = true;
|
|
500
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
342
501
|
return;
|
|
343
502
|
}
|
|
344
503
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -355,6 +514,9 @@ class StableBrowser {
|
|
|
355
514
|
// cssHref = true;
|
|
356
515
|
// }
|
|
357
516
|
let count = await locator.count();
|
|
517
|
+
if (count > 0 && !info.failCause.count) {
|
|
518
|
+
info.failCause.count = count;
|
|
519
|
+
}
|
|
358
520
|
//info.log += "total elements found " + count + "\n";
|
|
359
521
|
//let visibleCount = 0;
|
|
360
522
|
let visibleLocator = null;
|
|
@@ -372,6 +534,8 @@ class StableBrowser {
|
|
|
372
534
|
foundLocators.push(locator.nth(j));
|
|
373
535
|
}
|
|
374
536
|
else {
|
|
537
|
+
info.failCause.visible = visible;
|
|
538
|
+
info.failCause.enabled = enabled;
|
|
375
539
|
if (!info.printMessages) {
|
|
376
540
|
info.printMessages = {};
|
|
377
541
|
}
|
|
@@ -383,6 +547,11 @@ class StableBrowser {
|
|
|
383
547
|
}
|
|
384
548
|
}
|
|
385
549
|
async closeUnexpectedPopups(info, _params) {
|
|
550
|
+
if (!info) {
|
|
551
|
+
info = {};
|
|
552
|
+
info.failCause = {};
|
|
553
|
+
info.log = "";
|
|
554
|
+
}
|
|
386
555
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
387
556
|
if (!info) {
|
|
388
557
|
info = {};
|
|
@@ -415,15 +584,20 @@ class StableBrowser {
|
|
|
415
584
|
if (result.foundElements.length > 0) {
|
|
416
585
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
417
586
|
await dialogCloseLocator.click();
|
|
587
|
+
// wait for the dialog to close
|
|
588
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
418
589
|
return { rerun: true };
|
|
419
590
|
}
|
|
420
591
|
}
|
|
421
592
|
}
|
|
422
593
|
return { rerun: false };
|
|
423
594
|
}
|
|
424
|
-
async _locate(selectors, info, _params, timeout
|
|
595
|
+
async _locate(selectors, info, _params, timeout) {
|
|
596
|
+
if (!timeout) {
|
|
597
|
+
timeout = 30000;
|
|
598
|
+
}
|
|
425
599
|
for (let i = 0; i < 3; i++) {
|
|
426
|
-
info.log += "attempt " + i + ":
|
|
600
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
427
601
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
428
602
|
let selector = selectors.locators[j];
|
|
429
603
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -435,17 +609,49 @@ class StableBrowser {
|
|
|
435
609
|
}
|
|
436
610
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
437
611
|
}
|
|
438
|
-
async
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
612
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
613
|
+
if (!info) {
|
|
614
|
+
info = {};
|
|
615
|
+
info.failCause = {};
|
|
616
|
+
info.log = "";
|
|
617
|
+
}
|
|
444
618
|
let scope = this.page;
|
|
619
|
+
if (selectors.frame) {
|
|
620
|
+
return selectors.frame;
|
|
621
|
+
}
|
|
445
622
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
446
|
-
|
|
623
|
+
const findFrame = async (frame, framescope) => {
|
|
624
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
625
|
+
let frameLocator = frame.selectors[i];
|
|
626
|
+
if (frameLocator.css) {
|
|
627
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
628
|
+
if (frameLocator.index) {
|
|
629
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
633
|
+
timeout: 5000,
|
|
634
|
+
});
|
|
635
|
+
framescope = testframescope;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
console.error("frame not found " + frameLocator.css);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (frame.children) {
|
|
644
|
+
return await findFrame(frame.children, framescope);
|
|
645
|
+
}
|
|
646
|
+
return framescope;
|
|
647
|
+
};
|
|
447
648
|
while (true) {
|
|
448
649
|
let frameFound = false;
|
|
650
|
+
if (selectors.nestFrmLoc) {
|
|
651
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
652
|
+
frameFound = true;
|
|
653
|
+
break;
|
|
654
|
+
}
|
|
449
655
|
if (selectors.frameLocators) {
|
|
450
656
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
451
657
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -462,6 +668,8 @@ class StableBrowser {
|
|
|
462
668
|
if (!scope) {
|
|
463
669
|
info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
464
670
|
if (performance.now() - startTime > timeout) {
|
|
671
|
+
info.failCause.iframeNotFound = true;
|
|
672
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
465
673
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
466
674
|
}
|
|
467
675
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -471,6 +679,30 @@ class StableBrowser {
|
|
|
471
679
|
}
|
|
472
680
|
}
|
|
473
681
|
}
|
|
682
|
+
if (!scope) {
|
|
683
|
+
scope = this.page;
|
|
684
|
+
}
|
|
685
|
+
return scope;
|
|
686
|
+
}
|
|
687
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
688
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
689
|
+
return scope.evaluate(() => {
|
|
690
|
+
var bodyContent = document.body.innerHTML;
|
|
691
|
+
return bodyContent;
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
695
|
+
if (!info) {
|
|
696
|
+
info = {};
|
|
697
|
+
info.failCause = {};
|
|
698
|
+
info.log = "";
|
|
699
|
+
}
|
|
700
|
+
let highPriorityTimeout = 5000;
|
|
701
|
+
let visibleOnlyTimeout = 6000;
|
|
702
|
+
let startTime = performance.now();
|
|
703
|
+
let locatorsCount = 0;
|
|
704
|
+
//let arrayMode = Array.isArray(selectors);
|
|
705
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
474
706
|
let selectorsLocators = null;
|
|
475
707
|
selectorsLocators = selectors.locators;
|
|
476
708
|
// group selectors by priority
|
|
@@ -572,6 +804,8 @@ class StableBrowser {
|
|
|
572
804
|
}
|
|
573
805
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
574
806
|
info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
807
|
+
info.failCause.locatorNotFound = true;
|
|
808
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
575
809
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
576
810
|
}
|
|
577
811
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -603,86 +837,129 @@ class StableBrowser {
|
|
|
603
837
|
});
|
|
604
838
|
result.locatorIndex = i;
|
|
605
839
|
}
|
|
840
|
+
if (foundLocators.length > 1) {
|
|
841
|
+
info.failCause.foundMultiple = true;
|
|
842
|
+
}
|
|
606
843
|
}
|
|
607
844
|
return result;
|
|
608
845
|
}
|
|
609
|
-
async
|
|
610
|
-
this._validateSelectors(selectors);
|
|
846
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
611
847
|
const startTime = Date.now();
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
848
|
+
let timeout = 30000;
|
|
849
|
+
if (options && options.timeout) {
|
|
850
|
+
timeout = options.timeout;
|
|
851
|
+
}
|
|
852
|
+
while (true) {
|
|
853
|
+
try {
|
|
854
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
855
|
+
if (result?.elementNumber >= 0) {
|
|
856
|
+
const selectors = {
|
|
857
|
+
frame: result?.frame,
|
|
858
|
+
locators: [
|
|
859
|
+
{
|
|
860
|
+
css: result?.css,
|
|
861
|
+
},
|
|
862
|
+
],
|
|
863
|
+
};
|
|
864
|
+
await this.click(selectors, _params, options, world);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch (e) {
|
|
869
|
+
if (performance.now() - startTime > timeout) {
|
|
870
|
+
// throw e;
|
|
871
|
+
await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
878
|
+
const startTime = Date.now();
|
|
879
|
+
let timeout = 30000;
|
|
880
|
+
if (options && options.timeout) {
|
|
881
|
+
timeout = options.timeout;
|
|
882
|
+
}
|
|
883
|
+
while (true) {
|
|
884
|
+
try {
|
|
885
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
886
|
+
if (result?.elementNumber >= 0) {
|
|
887
|
+
const selectors = {
|
|
888
|
+
frame: result?.frame,
|
|
889
|
+
locators: [
|
|
890
|
+
{
|
|
891
|
+
css: result?.css,
|
|
892
|
+
},
|
|
893
|
+
],
|
|
894
|
+
};
|
|
895
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
catch (e) {
|
|
900
|
+
if (performance.now() - startTime > timeout) {
|
|
901
|
+
// throw e;
|
|
902
|
+
await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
async click(selectors, _params, options = {}, world = null) {
|
|
909
|
+
const state = {
|
|
910
|
+
selectors,
|
|
911
|
+
_params,
|
|
912
|
+
options,
|
|
913
|
+
world,
|
|
914
|
+
text: "Click element",
|
|
915
|
+
type: Types.CLICK,
|
|
916
|
+
operation: "click",
|
|
917
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
918
|
+
};
|
|
619
919
|
try {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
920
|
+
await _preCommand(state, this);
|
|
921
|
+
if (state.options && state.options.context) {
|
|
922
|
+
state.selectors.locators[0].text = state.options.context;
|
|
923
|
+
}
|
|
623
924
|
try {
|
|
624
|
-
await
|
|
625
|
-
await element.click({ timeout: 5000 });
|
|
925
|
+
await state.element.click();
|
|
626
926
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
627
927
|
}
|
|
628
928
|
catch (e) {
|
|
629
929
|
// await this.closeUnexpectedPopups();
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
await element.click({ timeout: 10000, force: true });
|
|
930
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
931
|
+
await state.element.dispatchEvent("click");
|
|
633
932
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
634
933
|
}
|
|
635
934
|
await this.waitForPageLoad();
|
|
636
|
-
return info;
|
|
935
|
+
return state.info;
|
|
637
936
|
}
|
|
638
937
|
catch (e) {
|
|
639
|
-
|
|
640
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
641
|
-
info.screenshotPath = screenshotPath;
|
|
642
|
-
Object.assign(e, { info: info });
|
|
643
|
-
error = e;
|
|
644
|
-
throw e;
|
|
938
|
+
await _commandError(state, e, this);
|
|
645
939
|
}
|
|
646
940
|
finally {
|
|
647
|
-
|
|
648
|
-
this._reportToWorld(world, {
|
|
649
|
-
element_name: selectors.element_name,
|
|
650
|
-
type: Types.CLICK,
|
|
651
|
-
text: `Click element`,
|
|
652
|
-
screenshotId,
|
|
653
|
-
result: error
|
|
654
|
-
? {
|
|
655
|
-
status: "FAILED",
|
|
656
|
-
startTime,
|
|
657
|
-
endTime,
|
|
658
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
659
|
-
}
|
|
660
|
-
: {
|
|
661
|
-
status: "PASSED",
|
|
662
|
-
startTime,
|
|
663
|
-
endTime,
|
|
664
|
-
},
|
|
665
|
-
info: info,
|
|
666
|
-
});
|
|
941
|
+
_commandFinally(state, this);
|
|
667
942
|
}
|
|
668
943
|
}
|
|
669
944
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
945
|
+
const state = {
|
|
946
|
+
selectors,
|
|
947
|
+
_params,
|
|
948
|
+
options,
|
|
949
|
+
world,
|
|
950
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
951
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
952
|
+
operation: "setCheck",
|
|
953
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
954
|
+
};
|
|
680
955
|
try {
|
|
681
|
-
|
|
682
|
-
|
|
956
|
+
await _preCommand(state, this);
|
|
957
|
+
state.info.checked = checked;
|
|
958
|
+
// let element = await this._locate(selectors, info, _params);
|
|
959
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
683
960
|
try {
|
|
684
|
-
await this._highlightElements(element);
|
|
685
|
-
await element.setChecked(checked
|
|
961
|
+
// await this._highlightElements(element);
|
|
962
|
+
await state.element.setChecked(checked);
|
|
686
963
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
687
964
|
}
|
|
688
965
|
catch (e) {
|
|
@@ -691,179 +968,108 @@ class StableBrowser {
|
|
|
691
968
|
}
|
|
692
969
|
else {
|
|
693
970
|
//await this.closeUnexpectedPopups();
|
|
694
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
695
|
-
element = await this._locate(selectors, info, _params);
|
|
696
|
-
await element.setChecked(checked, { timeout: 5000, force: true });
|
|
971
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
972
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
973
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
697
974
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
698
975
|
}
|
|
699
976
|
}
|
|
700
977
|
await this.waitForPageLoad();
|
|
701
|
-
return info;
|
|
978
|
+
return state.info;
|
|
702
979
|
}
|
|
703
980
|
catch (e) {
|
|
704
|
-
|
|
705
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
706
|
-
info.screenshotPath = screenshotPath;
|
|
707
|
-
Object.assign(e, { info: info });
|
|
708
|
-
error = e;
|
|
709
|
-
throw e;
|
|
981
|
+
await _commandError(state, e, this);
|
|
710
982
|
}
|
|
711
983
|
finally {
|
|
712
|
-
|
|
713
|
-
this._reportToWorld(world, {
|
|
714
|
-
element_name: selectors.element_name,
|
|
715
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
716
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
717
|
-
screenshotId,
|
|
718
|
-
result: error
|
|
719
|
-
? {
|
|
720
|
-
status: "FAILED",
|
|
721
|
-
startTime,
|
|
722
|
-
endTime,
|
|
723
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
724
|
-
}
|
|
725
|
-
: {
|
|
726
|
-
status: "PASSED",
|
|
727
|
-
startTime,
|
|
728
|
-
endTime,
|
|
729
|
-
},
|
|
730
|
-
info: info,
|
|
731
|
-
});
|
|
984
|
+
_commandFinally(state, this);
|
|
732
985
|
}
|
|
733
986
|
}
|
|
734
987
|
async hover(selectors, _params, options = {}, world = null) {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
988
|
+
const state = {
|
|
989
|
+
selectors,
|
|
990
|
+
_params,
|
|
991
|
+
options,
|
|
992
|
+
world,
|
|
993
|
+
type: Types.HOVER,
|
|
994
|
+
text: `Hover element`,
|
|
995
|
+
operation: "hover",
|
|
996
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
997
|
+
};
|
|
744
998
|
try {
|
|
745
|
-
|
|
746
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
999
|
+
await _preCommand(state, this);
|
|
747
1000
|
try {
|
|
748
|
-
await
|
|
749
|
-
await element.hover({ timeout: 10000 });
|
|
1001
|
+
await state.element.hover();
|
|
750
1002
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
751
1003
|
}
|
|
752
1004
|
catch (e) {
|
|
753
1005
|
//await this.closeUnexpectedPopups();
|
|
754
|
-
info.log += "hover failed, will try again" + "\n";
|
|
755
|
-
element = await this._locate(selectors, info, _params);
|
|
756
|
-
await element.hover({ timeout: 10000 });
|
|
1006
|
+
state.info.log += "hover failed, will try again" + "\n";
|
|
1007
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
1008
|
+
await state.element.hover({ timeout: 10000 });
|
|
757
1009
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
758
1010
|
}
|
|
759
1011
|
await this.waitForPageLoad();
|
|
760
|
-
return info;
|
|
1012
|
+
return state.info;
|
|
761
1013
|
}
|
|
762
1014
|
catch (e) {
|
|
763
|
-
|
|
764
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
765
|
-
info.screenshotPath = screenshotPath;
|
|
766
|
-
Object.assign(e, { info: info });
|
|
767
|
-
error = e;
|
|
768
|
-
throw e;
|
|
1015
|
+
await _commandError(state, e, this);
|
|
769
1016
|
}
|
|
770
1017
|
finally {
|
|
771
|
-
|
|
772
|
-
this._reportToWorld(world, {
|
|
773
|
-
element_name: selectors.element_name,
|
|
774
|
-
type: Types.HOVER,
|
|
775
|
-
text: `Hover element`,
|
|
776
|
-
screenshotId,
|
|
777
|
-
result: error
|
|
778
|
-
? {
|
|
779
|
-
status: "FAILED",
|
|
780
|
-
startTime,
|
|
781
|
-
endTime,
|
|
782
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
783
|
-
}
|
|
784
|
-
: {
|
|
785
|
-
status: "PASSED",
|
|
786
|
-
startTime,
|
|
787
|
-
endTime,
|
|
788
|
-
},
|
|
789
|
-
info: info,
|
|
790
|
-
});
|
|
1018
|
+
_commandFinally(state, this);
|
|
791
1019
|
}
|
|
792
1020
|
}
|
|
793
1021
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
794
|
-
this._validateSelectors(selectors);
|
|
795
1022
|
if (!values) {
|
|
796
1023
|
throw new Error("values is null");
|
|
797
1024
|
}
|
|
798
|
-
const
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1025
|
+
const state = {
|
|
1026
|
+
selectors,
|
|
1027
|
+
_params,
|
|
1028
|
+
options,
|
|
1029
|
+
world,
|
|
1030
|
+
value: values.toString(),
|
|
1031
|
+
type: Types.SELECT,
|
|
1032
|
+
text: `Select option: ${values}`,
|
|
1033
|
+
operation: "selectOption",
|
|
1034
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1035
|
+
};
|
|
806
1036
|
try {
|
|
807
|
-
|
|
808
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1037
|
+
await _preCommand(state, this);
|
|
809
1038
|
try {
|
|
810
|
-
await
|
|
811
|
-
await element.selectOption(values, { timeout: 5000 });
|
|
1039
|
+
await state.element.selectOption(values);
|
|
812
1040
|
}
|
|
813
1041
|
catch (e) {
|
|
814
1042
|
//await this.closeUnexpectedPopups();
|
|
815
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
816
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1043
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1044
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
817
1045
|
}
|
|
818
1046
|
await this.waitForPageLoad();
|
|
819
|
-
return info;
|
|
1047
|
+
return state.info;
|
|
820
1048
|
}
|
|
821
1049
|
catch (e) {
|
|
822
|
-
|
|
823
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
824
|
-
info.screenshotPath = screenshotPath;
|
|
825
|
-
Object.assign(e, { info: info });
|
|
826
|
-
this.logger.info("click failed, will try next selector");
|
|
827
|
-
error = e;
|
|
828
|
-
throw e;
|
|
1050
|
+
await _commandError(state, e, this);
|
|
829
1051
|
}
|
|
830
1052
|
finally {
|
|
831
|
-
|
|
832
|
-
this._reportToWorld(world, {
|
|
833
|
-
element_name: selectors.element_name,
|
|
834
|
-
type: Types.SELECT,
|
|
835
|
-
text: `Select option: ${values}`,
|
|
836
|
-
value: values.toString(),
|
|
837
|
-
screenshotId,
|
|
838
|
-
result: error
|
|
839
|
-
? {
|
|
840
|
-
status: "FAILED",
|
|
841
|
-
startTime,
|
|
842
|
-
endTime,
|
|
843
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
844
|
-
}
|
|
845
|
-
: {
|
|
846
|
-
status: "PASSED",
|
|
847
|
-
startTime,
|
|
848
|
-
endTime,
|
|
849
|
-
},
|
|
850
|
-
info: info,
|
|
851
|
-
});
|
|
1053
|
+
_commandFinally(state, this);
|
|
852
1054
|
}
|
|
853
1055
|
}
|
|
854
1056
|
async type(_value, _params = null, options = {}, world = null) {
|
|
855
|
-
const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1057
|
+
const state = {
|
|
1058
|
+
value: _value,
|
|
1059
|
+
_params,
|
|
1060
|
+
options,
|
|
1061
|
+
world,
|
|
1062
|
+
locate: false,
|
|
1063
|
+
scroll: false,
|
|
1064
|
+
highlight: false,
|
|
1065
|
+
type: Types.TYPE_PRESS,
|
|
1066
|
+
text: `Type value: ${_value}`,
|
|
1067
|
+
operation: "type",
|
|
1068
|
+
log: "",
|
|
1069
|
+
};
|
|
864
1070
|
try {
|
|
865
|
-
|
|
866
|
-
const valueSegment =
|
|
1071
|
+
await _preCommand(state, this);
|
|
1072
|
+
const valueSegment = state.value.split("&&");
|
|
867
1073
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
868
1074
|
if (i > 0) {
|
|
869
1075
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -883,68 +1089,76 @@ class StableBrowser {
|
|
|
883
1089
|
await this.page.keyboard.type(value);
|
|
884
1090
|
}
|
|
885
1091
|
}
|
|
886
|
-
return info;
|
|
1092
|
+
return state.info;
|
|
887
1093
|
}
|
|
888
1094
|
catch (e) {
|
|
889
|
-
|
|
890
|
-
this.logger.error("type failed " + info.log);
|
|
891
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
892
|
-
info.screenshotPath = screenshotPath;
|
|
893
|
-
Object.assign(e, { info: info });
|
|
894
|
-
error = e;
|
|
895
|
-
throw e;
|
|
1095
|
+
await _commandError(state, e, this);
|
|
896
1096
|
}
|
|
897
1097
|
finally {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1098
|
+
_commandFinally(state, this);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1102
|
+
const state = {
|
|
1103
|
+
selectors,
|
|
1104
|
+
_params,
|
|
1105
|
+
value,
|
|
1106
|
+
options,
|
|
1107
|
+
world,
|
|
1108
|
+
type: Types.SET_INPUT,
|
|
1109
|
+
text: `Set input value`,
|
|
1110
|
+
operation: "setInputValue",
|
|
1111
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1112
|
+
};
|
|
1113
|
+
try {
|
|
1114
|
+
await _preCommand(state, this);
|
|
1115
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1116
|
+
try {
|
|
1117
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1118
|
+
el.value = value;
|
|
1119
|
+
}, value);
|
|
1120
|
+
}
|
|
1121
|
+
catch (error) {
|
|
1122
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1123
|
+
await _screenshot(state, this);
|
|
1124
|
+
Object.assign(error, { info: state.info });
|
|
1125
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1126
|
+
el.value = value;
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
catch (e) {
|
|
1131
|
+
await _commandError(state, e, this);
|
|
1132
|
+
}
|
|
1133
|
+
finally {
|
|
1134
|
+
_commandFinally(state, this);
|
|
918
1135
|
}
|
|
919
1136
|
}
|
|
920
1137
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1138
|
+
const state = {
|
|
1139
|
+
selectors,
|
|
1140
|
+
_params,
|
|
1141
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1142
|
+
options,
|
|
1143
|
+
world,
|
|
1144
|
+
type: Types.SET_DATE_TIME,
|
|
1145
|
+
text: `Set date time value: ${value}`,
|
|
1146
|
+
operation: "setDateTime",
|
|
1147
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1148
|
+
throwError: false,
|
|
1149
|
+
};
|
|
931
1150
|
try {
|
|
932
|
-
|
|
933
|
-
let element = await this._locate(selectors, info, _params);
|
|
934
|
-
//insert red border around the element
|
|
935
|
-
await this.scrollIfNeeded(element, info);
|
|
936
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
937
|
-
await this._highlightElements(element);
|
|
1151
|
+
await _preCommand(state, this);
|
|
938
1152
|
try {
|
|
939
|
-
await element.click();
|
|
1153
|
+
await state.element.click();
|
|
940
1154
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
941
1155
|
if (format) {
|
|
942
|
-
value = dayjs(value).format(format);
|
|
943
|
-
await element.fill(value);
|
|
1156
|
+
state.value = dayjs(state.value).format(format);
|
|
1157
|
+
await state.element.fill(state.value);
|
|
944
1158
|
}
|
|
945
1159
|
else {
|
|
946
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
947
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1160
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1161
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
948
1162
|
el.value = ""; // clear input
|
|
949
1163
|
el.value = dateTimeValue;
|
|
950
1164
|
}, dateTimeValue);
|
|
@@ -955,21 +1169,21 @@ class StableBrowser {
|
|
|
955
1169
|
await this.waitForPageLoad();
|
|
956
1170
|
}
|
|
957
1171
|
}
|
|
958
|
-
catch (
|
|
1172
|
+
catch (err) {
|
|
959
1173
|
//await this.closeUnexpectedPopups();
|
|
960
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
961
|
-
this.logger.info("Trying again")
|
|
962
|
-
|
|
963
|
-
Object.assign(
|
|
1174
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1175
|
+
this.logger.info("Trying again");
|
|
1176
|
+
await _screenshot(state, this);
|
|
1177
|
+
Object.assign(err, { info: state.info });
|
|
964
1178
|
await element.click();
|
|
965
1179
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
966
1180
|
if (format) {
|
|
967
|
-
value = dayjs(value).format(format);
|
|
968
|
-
await element.fill(value);
|
|
1181
|
+
state.value = dayjs(state.value).format(format);
|
|
1182
|
+
await state.element.fill(state.value);
|
|
969
1183
|
}
|
|
970
1184
|
else {
|
|
971
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
972
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1185
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1186
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
973
1187
|
el.value = ""; // clear input
|
|
974
1188
|
el.value = dateTimeValue;
|
|
975
1189
|
}, dateTimeValue);
|
|
@@ -981,130 +1195,40 @@ class StableBrowser {
|
|
|
981
1195
|
}
|
|
982
1196
|
}
|
|
983
1197
|
}
|
|
984
|
-
catch (
|
|
985
|
-
|
|
986
|
-
throw e;
|
|
987
|
-
}
|
|
988
|
-
finally {
|
|
989
|
-
const endTime = Date.now();
|
|
990
|
-
this._reportToWorld(world, {
|
|
991
|
-
element_name: selectors.element_name,
|
|
992
|
-
type: Types.SET_DATE_TIME,
|
|
993
|
-
screenshotId,
|
|
994
|
-
value: value,
|
|
995
|
-
text: `setDateTime input with value: ${value}`,
|
|
996
|
-
result: error
|
|
997
|
-
? {
|
|
998
|
-
status: "FAILED",
|
|
999
|
-
startTime,
|
|
1000
|
-
endTime,
|
|
1001
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1002
|
-
}
|
|
1003
|
-
: {
|
|
1004
|
-
status: "PASSED",
|
|
1005
|
-
startTime,
|
|
1006
|
-
endTime,
|
|
1007
|
-
},
|
|
1008
|
-
info: info,
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1013
|
-
this._validateSelectors(selectors);
|
|
1014
|
-
const startTime = Date.now();
|
|
1015
|
-
let error = null;
|
|
1016
|
-
let screenshotId = null;
|
|
1017
|
-
let screenshotPath = null;
|
|
1018
|
-
const info = {};
|
|
1019
|
-
info.log = "";
|
|
1020
|
-
info.operation = Types.SET_DATE_TIME;
|
|
1021
|
-
info.selectors = selectors;
|
|
1022
|
-
info.value = value;
|
|
1023
|
-
try {
|
|
1024
|
-
let element = await this._locate(selectors, info, _params);
|
|
1025
|
-
//insert red border around the element
|
|
1026
|
-
await this.scrollIfNeeded(element, info);
|
|
1027
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1028
|
-
await this._highlightElements(element);
|
|
1029
|
-
try {
|
|
1030
|
-
await element.click();
|
|
1031
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1032
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1033
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1034
|
-
el.value = ""; // clear input
|
|
1035
|
-
el.value = dateTimeValue;
|
|
1036
|
-
}, dateTimeValue);
|
|
1037
|
-
}
|
|
1038
|
-
catch (error) {
|
|
1039
|
-
//await this.closeUnexpectedPopups();
|
|
1040
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1041
|
-
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
1042
|
-
info.screenshotPath = screenshotPath;
|
|
1043
|
-
Object.assign(error, { info: info });
|
|
1044
|
-
await element.click();
|
|
1045
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1046
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1047
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1048
|
-
el.value = ""; // clear input
|
|
1049
|
-
el.value = dateTimeValue;
|
|
1050
|
-
}, dateTimeValue);
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
catch (error) {
|
|
1054
|
-
error = e;
|
|
1055
|
-
throw e;
|
|
1198
|
+
catch (e) {
|
|
1199
|
+
await _commandError(state, e, this);
|
|
1056
1200
|
}
|
|
1057
1201
|
finally {
|
|
1058
|
-
|
|
1059
|
-
this._reportToWorld(world, {
|
|
1060
|
-
element_name: selectors.element_name,
|
|
1061
|
-
type: Types.SET_DATE_TIME,
|
|
1062
|
-
screenshotId,
|
|
1063
|
-
value: value,
|
|
1064
|
-
text: `setDateTime input with value: ${value}`,
|
|
1065
|
-
result: error
|
|
1066
|
-
? {
|
|
1067
|
-
status: "FAILED",
|
|
1068
|
-
startTime,
|
|
1069
|
-
endTime,
|
|
1070
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1071
|
-
}
|
|
1072
|
-
: {
|
|
1073
|
-
status: "PASSED",
|
|
1074
|
-
startTime,
|
|
1075
|
-
endTime,
|
|
1076
|
-
},
|
|
1077
|
-
info: info,
|
|
1078
|
-
});
|
|
1202
|
+
_commandFinally(state, this);
|
|
1079
1203
|
}
|
|
1080
1204
|
}
|
|
1081
1205
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1082
|
-
|
|
1083
|
-
const startTime = Date.now();
|
|
1084
|
-
let error = null;
|
|
1085
|
-
let screenshotId = null;
|
|
1086
|
-
let screenshotPath = null;
|
|
1087
|
-
const info = {};
|
|
1088
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1089
|
-
info.operation = "clickType";
|
|
1090
|
-
info.selectors = selectors;
|
|
1206
|
+
_value = unEscapeString(_value);
|
|
1091
1207
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1208
|
+
const state = {
|
|
1209
|
+
selectors,
|
|
1210
|
+
_params,
|
|
1211
|
+
value: newValue,
|
|
1212
|
+
originalValue: _value,
|
|
1213
|
+
options,
|
|
1214
|
+
world,
|
|
1215
|
+
type: Types.FILL,
|
|
1216
|
+
text: `Click type input with value: ${_value}`,
|
|
1217
|
+
operation: "clickType",
|
|
1218
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1219
|
+
};
|
|
1092
1220
|
if (newValue !== _value) {
|
|
1093
1221
|
//this.logger.info(_value + "=" + newValue);
|
|
1094
1222
|
_value = newValue;
|
|
1095
1223
|
}
|
|
1096
|
-
info.value = _value;
|
|
1097
1224
|
try {
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
await this.scrollIfNeeded(element, info);
|
|
1101
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1102
|
-
await this._highlightElements(element);
|
|
1225
|
+
await _preCommand(state, this);
|
|
1226
|
+
state.info.value = _value;
|
|
1103
1227
|
if (options === null || options === undefined || !options.press) {
|
|
1104
1228
|
try {
|
|
1105
|
-
let currentValue = await element.inputValue();
|
|
1229
|
+
let currentValue = await state.element.inputValue();
|
|
1106
1230
|
if (currentValue) {
|
|
1107
|
-
await element.fill("");
|
|
1231
|
+
await state.element.fill("");
|
|
1108
1232
|
}
|
|
1109
1233
|
}
|
|
1110
1234
|
catch (e) {
|
|
@@ -1113,22 +1237,22 @@ class StableBrowser {
|
|
|
1113
1237
|
}
|
|
1114
1238
|
if (options === null || options === undefined || options.press) {
|
|
1115
1239
|
try {
|
|
1116
|
-
await element.click({ timeout: 5000 });
|
|
1240
|
+
await state.element.click({ timeout: 5000 });
|
|
1117
1241
|
}
|
|
1118
1242
|
catch (e) {
|
|
1119
|
-
await element.dispatchEvent("click");
|
|
1243
|
+
await state.element.dispatchEvent("click");
|
|
1120
1244
|
}
|
|
1121
1245
|
}
|
|
1122
1246
|
else {
|
|
1123
1247
|
try {
|
|
1124
|
-
await element.focus();
|
|
1248
|
+
await state.element.focus();
|
|
1125
1249
|
}
|
|
1126
1250
|
catch (e) {
|
|
1127
|
-
await element.dispatchEvent("focus");
|
|
1251
|
+
await state.element.dispatchEvent("focus");
|
|
1128
1252
|
}
|
|
1129
1253
|
}
|
|
1130
1254
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1131
|
-
const valueSegment =
|
|
1255
|
+
const valueSegment = state.value.split("&&");
|
|
1132
1256
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1133
1257
|
if (i > 0) {
|
|
1134
1258
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1148,118 +1272,66 @@ class StableBrowser {
|
|
|
1148
1272
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1149
1273
|
}
|
|
1150
1274
|
}
|
|
1275
|
+
await _screenshot(state, this);
|
|
1151
1276
|
if (enter === true) {
|
|
1152
1277
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1153
1278
|
await this.page.keyboard.press("Enter");
|
|
1154
1279
|
await this.waitForPageLoad();
|
|
1155
1280
|
}
|
|
1156
1281
|
else if (enter === false) {
|
|
1157
|
-
await element.dispatchEvent("change");
|
|
1282
|
+
await state.element.dispatchEvent("change");
|
|
1158
1283
|
//await this.page.keyboard.press("Tab");
|
|
1159
1284
|
}
|
|
1160
1285
|
else {
|
|
1161
1286
|
if (enter !== "" && enter !== null && enter !== undefined) {
|
|
1162
1287
|
await this.page.keyboard.press(enter);
|
|
1163
|
-
await this.waitForPageLoad();
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
return info;
|
|
1167
|
-
}
|
|
1168
|
-
catch (e) {
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
Object.assign(e, { info: info });
|
|
1174
|
-
error = e;
|
|
1175
|
-
throw e;
|
|
1176
|
-
}
|
|
1177
|
-
finally {
|
|
1178
|
-
const endTime = Date.now();
|
|
1179
|
-
this._reportToWorld(world, {
|
|
1180
|
-
element_name: selectors.element_name,
|
|
1181
|
-
type: Types.FILL,
|
|
1182
|
-
screenshotId,
|
|
1183
|
-
value: _value,
|
|
1184
|
-
text: `clickType input with value: ${_value}`,
|
|
1185
|
-
result: error
|
|
1186
|
-
? {
|
|
1187
|
-
status: "FAILED",
|
|
1188
|
-
startTime,
|
|
1189
|
-
endTime,
|
|
1190
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1191
|
-
}
|
|
1192
|
-
: {
|
|
1193
|
-
status: "PASSED",
|
|
1194
|
-
startTime,
|
|
1195
|
-
endTime,
|
|
1196
|
-
},
|
|
1197
|
-
info: info,
|
|
1198
|
-
});
|
|
1288
|
+
await this.waitForPageLoad();
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
return state.info;
|
|
1292
|
+
}
|
|
1293
|
+
catch (e) {
|
|
1294
|
+
await _commandError(state, e, this);
|
|
1295
|
+
}
|
|
1296
|
+
finally {
|
|
1297
|
+
_commandFinally(state, this);
|
|
1199
1298
|
}
|
|
1200
1299
|
}
|
|
1201
1300
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1301
|
+
const state = {
|
|
1302
|
+
selectors,
|
|
1303
|
+
_params,
|
|
1304
|
+
value: unEscapeString(value),
|
|
1305
|
+
options,
|
|
1306
|
+
world,
|
|
1307
|
+
type: Types.FILL,
|
|
1308
|
+
text: `Fill input with value: ${value}`,
|
|
1309
|
+
operation: "fill",
|
|
1310
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1311
|
+
};
|
|
1212
1312
|
try {
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
await
|
|
1216
|
-
await element.fill(value, { timeout: 10000 });
|
|
1217
|
-
await element.dispatchEvent("change");
|
|
1313
|
+
await _preCommand(state, this);
|
|
1314
|
+
await state.element.fill(value);
|
|
1315
|
+
await state.element.dispatchEvent("change");
|
|
1218
1316
|
if (enter) {
|
|
1219
1317
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1220
1318
|
await this.page.keyboard.press("Enter");
|
|
1221
1319
|
}
|
|
1222
1320
|
await this.waitForPageLoad();
|
|
1223
|
-
return info;
|
|
1321
|
+
return state.info;
|
|
1224
1322
|
}
|
|
1225
1323
|
catch (e) {
|
|
1226
|
-
|
|
1227
|
-
this.logger.error("fill failed " + info.log);
|
|
1228
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1229
|
-
info.screenshotPath = screenshotPath;
|
|
1230
|
-
Object.assign(e, { info: info });
|
|
1231
|
-
error = e;
|
|
1232
|
-
throw e;
|
|
1324
|
+
await _commandError(state, e, this);
|
|
1233
1325
|
}
|
|
1234
1326
|
finally {
|
|
1235
|
-
|
|
1236
|
-
this._reportToWorld(world, {
|
|
1237
|
-
element_name: selectors.element_name,
|
|
1238
|
-
type: Types.FILL,
|
|
1239
|
-
screenshotId,
|
|
1240
|
-
value,
|
|
1241
|
-
text: `Fill input with value: ${value}`,
|
|
1242
|
-
result: error
|
|
1243
|
-
? {
|
|
1244
|
-
status: "FAILED",
|
|
1245
|
-
startTime,
|
|
1246
|
-
endTime,
|
|
1247
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1248
|
-
}
|
|
1249
|
-
: {
|
|
1250
|
-
status: "PASSED",
|
|
1251
|
-
startTime,
|
|
1252
|
-
endTime,
|
|
1253
|
-
},
|
|
1254
|
-
info: info,
|
|
1255
|
-
});
|
|
1327
|
+
_commandFinally(state, this);
|
|
1256
1328
|
}
|
|
1257
1329
|
}
|
|
1258
1330
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1259
1331
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1260
1332
|
}
|
|
1261
1333
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1262
|
-
|
|
1334
|
+
_validateSelectors(selectors);
|
|
1263
1335
|
let screenshotId = null;
|
|
1264
1336
|
let screenshotPath = null;
|
|
1265
1337
|
if (!info.log) {
|
|
@@ -1303,165 +1375,124 @@ class StableBrowser {
|
|
|
1303
1375
|
}
|
|
1304
1376
|
}
|
|
1305
1377
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1306
|
-
var _a;
|
|
1307
|
-
this._validateSelectors(selectors);
|
|
1308
1378
|
if (!pattern) {
|
|
1309
1379
|
throw new Error("pattern is null");
|
|
1310
1380
|
}
|
|
1311
1381
|
if (!text) {
|
|
1312
1382
|
throw new Error("text is null");
|
|
1313
1383
|
}
|
|
1384
|
+
const state = {
|
|
1385
|
+
selectors,
|
|
1386
|
+
_params,
|
|
1387
|
+
pattern,
|
|
1388
|
+
value: pattern,
|
|
1389
|
+
options,
|
|
1390
|
+
world,
|
|
1391
|
+
locate: false,
|
|
1392
|
+
scroll: false,
|
|
1393
|
+
screenshot: false,
|
|
1394
|
+
highlight: false,
|
|
1395
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1396
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1397
|
+
operation: "containsPattern",
|
|
1398
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1399
|
+
};
|
|
1314
1400
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1315
1401
|
if (newValue !== text) {
|
|
1316
1402
|
this.logger.info(text + "=" + newValue);
|
|
1317
1403
|
text = newValue;
|
|
1318
1404
|
}
|
|
1319
|
-
const startTime = Date.now();
|
|
1320
|
-
let error = null;
|
|
1321
|
-
let screenshotId = null;
|
|
1322
|
-
let screenshotPath = null;
|
|
1323
|
-
const info = {};
|
|
1324
|
-
info.log =
|
|
1325
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1326
|
-
info.operation = "containsPattern";
|
|
1327
|
-
info.selectors = selectors;
|
|
1328
|
-
info.value = text;
|
|
1329
|
-
info.pattern = pattern;
|
|
1330
1405
|
let foundObj = null;
|
|
1331
1406
|
try {
|
|
1332
|
-
|
|
1407
|
+
await _preCommand(state, this);
|
|
1408
|
+
state.info.pattern = pattern;
|
|
1409
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1333
1410
|
if (foundObj && foundObj.element) {
|
|
1334
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1411
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1335
1412
|
}
|
|
1336
|
-
|
|
1413
|
+
await _screenshot(state, this);
|
|
1337
1414
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1338
1415
|
pattern = pattern.replace("{text}", escapedText);
|
|
1339
1416
|
let regex = new RegExp(pattern, "im");
|
|
1340
|
-
if (!regex.test(foundObj
|
|
1341
|
-
info.foundText = foundObj
|
|
1417
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1418
|
+
state.info.foundText = foundObj?.text;
|
|
1342
1419
|
throw new Error("element doesn't contain text " + text);
|
|
1343
1420
|
}
|
|
1344
|
-
return info;
|
|
1421
|
+
return state.info;
|
|
1345
1422
|
}
|
|
1346
1423
|
catch (e) {
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1350
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1351
|
-
info.screenshotPath = screenshotPath;
|
|
1352
|
-
Object.assign(e, { info: info });
|
|
1353
|
-
error = e;
|
|
1354
|
-
throw e;
|
|
1424
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1425
|
+
await _commandError(state, e, this);
|
|
1355
1426
|
}
|
|
1356
1427
|
finally {
|
|
1357
|
-
|
|
1358
|
-
this._reportToWorld(world, {
|
|
1359
|
-
element_name: selectors.element_name,
|
|
1360
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1361
|
-
value: pattern,
|
|
1362
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1363
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1364
|
-
result: error
|
|
1365
|
-
? {
|
|
1366
|
-
status: "FAILED",
|
|
1367
|
-
startTime,
|
|
1368
|
-
endTime,
|
|
1369
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1370
|
-
}
|
|
1371
|
-
: {
|
|
1372
|
-
status: "PASSED",
|
|
1373
|
-
startTime,
|
|
1374
|
-
endTime,
|
|
1375
|
-
},
|
|
1376
|
-
info: info,
|
|
1377
|
-
});
|
|
1428
|
+
_commandFinally(state, this);
|
|
1378
1429
|
}
|
|
1379
1430
|
}
|
|
1380
1431
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1381
|
-
|
|
1382
|
-
|
|
1432
|
+
const state = {
|
|
1433
|
+
selectors,
|
|
1434
|
+
_params,
|
|
1435
|
+
value: text,
|
|
1436
|
+
options,
|
|
1437
|
+
world,
|
|
1438
|
+
locate: false,
|
|
1439
|
+
scroll: false,
|
|
1440
|
+
screenshot: false,
|
|
1441
|
+
highlight: false,
|
|
1442
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1443
|
+
text: `Verify element contains text: ${text}`,
|
|
1444
|
+
operation: "containsText",
|
|
1445
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1446
|
+
};
|
|
1383
1447
|
if (!text) {
|
|
1384
1448
|
throw new Error("text is null");
|
|
1385
1449
|
}
|
|
1386
|
-
|
|
1387
|
-
let error = null;
|
|
1388
|
-
let screenshotId = null;
|
|
1389
|
-
let screenshotPath = null;
|
|
1390
|
-
const info = {};
|
|
1391
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1392
|
-
info.operation = "containsText";
|
|
1393
|
-
info.selectors = selectors;
|
|
1450
|
+
text = unEscapeString(text);
|
|
1394
1451
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1395
1452
|
if (newValue !== text) {
|
|
1396
1453
|
this.logger.info(text + "=" + newValue);
|
|
1397
1454
|
text = newValue;
|
|
1398
1455
|
}
|
|
1399
|
-
info.value = text;
|
|
1400
1456
|
let foundObj = null;
|
|
1401
1457
|
try {
|
|
1402
|
-
|
|
1458
|
+
await _preCommand(state, this);
|
|
1459
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1403
1460
|
if (foundObj && foundObj.element) {
|
|
1404
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1461
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1405
1462
|
}
|
|
1406
|
-
|
|
1463
|
+
await _screenshot(state, this);
|
|
1407
1464
|
const dateAlternatives = findDateAlternatives(text);
|
|
1408
1465
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1409
1466
|
if (dateAlternatives.date) {
|
|
1410
1467
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1411
|
-
if (
|
|
1412
|
-
|
|
1413
|
-
return info;
|
|
1468
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1469
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1470
|
+
return state.info;
|
|
1414
1471
|
}
|
|
1415
1472
|
}
|
|
1416
1473
|
throw new Error("element doesn't contain text " + text);
|
|
1417
1474
|
}
|
|
1418
1475
|
else if (numberAlternatives.number) {
|
|
1419
1476
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1420
|
-
if (
|
|
1421
|
-
|
|
1422
|
-
return info;
|
|
1477
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1478
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1479
|
+
return state.info;
|
|
1423
1480
|
}
|
|
1424
1481
|
}
|
|
1425
1482
|
throw new Error("element doesn't contain text " + text);
|
|
1426
1483
|
}
|
|
1427
|
-
else if (!
|
|
1428
|
-
info.foundText = foundObj
|
|
1429
|
-
info.value = foundObj
|
|
1484
|
+
else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
|
|
1485
|
+
state.info.foundText = foundObj?.text;
|
|
1486
|
+
state.info.value = foundObj?.value;
|
|
1430
1487
|
throw new Error("element doesn't contain text " + text);
|
|
1431
1488
|
}
|
|
1432
|
-
return info;
|
|
1489
|
+
return state.info;
|
|
1433
1490
|
}
|
|
1434
1491
|
catch (e) {
|
|
1435
|
-
|
|
1436
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1437
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1438
|
-
info.screenshotPath = screenshotPath;
|
|
1439
|
-
Object.assign(e, { info: info });
|
|
1440
|
-
error = e;
|
|
1441
|
-
throw e;
|
|
1492
|
+
await _commandError(state, e, this);
|
|
1442
1493
|
}
|
|
1443
1494
|
finally {
|
|
1444
|
-
|
|
1445
|
-
this._reportToWorld(world, {
|
|
1446
|
-
element_name: selectors.element_name,
|
|
1447
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1448
|
-
text: `Verify element contains text: ${text}`,
|
|
1449
|
-
value: text,
|
|
1450
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1451
|
-
result: error
|
|
1452
|
-
? {
|
|
1453
|
-
status: "FAILED",
|
|
1454
|
-
startTime,
|
|
1455
|
-
endTime,
|
|
1456
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1457
|
-
}
|
|
1458
|
-
: {
|
|
1459
|
-
status: "PASSED",
|
|
1460
|
-
startTime,
|
|
1461
|
-
endTime,
|
|
1462
|
-
},
|
|
1463
|
-
info: info,
|
|
1464
|
-
});
|
|
1495
|
+
_commandFinally(state, this);
|
|
1465
1496
|
}
|
|
1466
1497
|
}
|
|
1467
1498
|
_getDataFile(world = null) {
|
|
@@ -1480,6 +1511,29 @@ class StableBrowser {
|
|
|
1480
1511
|
}
|
|
1481
1512
|
return dataFile;
|
|
1482
1513
|
}
|
|
1514
|
+
async waitForUserInput(message, world = null) {
|
|
1515
|
+
if (!message) {
|
|
1516
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1517
|
+
}
|
|
1518
|
+
else {
|
|
1519
|
+
message = "# Wait for user input. " + message;
|
|
1520
|
+
}
|
|
1521
|
+
message += "\n";
|
|
1522
|
+
const value = await new Promise((resolve) => {
|
|
1523
|
+
const rl = readline.createInterface({
|
|
1524
|
+
input: process.stdin,
|
|
1525
|
+
output: process.stdout,
|
|
1526
|
+
});
|
|
1527
|
+
rl.question(message, (answer) => {
|
|
1528
|
+
rl.close();
|
|
1529
|
+
resolve(answer);
|
|
1530
|
+
});
|
|
1531
|
+
});
|
|
1532
|
+
if (value) {
|
|
1533
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1534
|
+
}
|
|
1535
|
+
this.setTestData({ userInput: value }, world);
|
|
1536
|
+
}
|
|
1483
1537
|
setTestData(testData, world = null) {
|
|
1484
1538
|
if (!testData) {
|
|
1485
1539
|
return;
|
|
@@ -1507,7 +1561,7 @@ class StableBrowser {
|
|
|
1507
1561
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1508
1562
|
const results = [];
|
|
1509
1563
|
return new Promise((resolve, reject) => {
|
|
1510
|
-
const readableStream = new
|
|
1564
|
+
const readableStream = new Readable();
|
|
1511
1565
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1512
1566
|
readableStream.push(data);
|
|
1513
1567
|
readableStream.push(null);
|
|
@@ -1667,7 +1721,6 @@ class StableBrowser {
|
|
|
1667
1721
|
}
|
|
1668
1722
|
async takeScreenshot(screenshotPath) {
|
|
1669
1723
|
const playContext = this.context.playContext;
|
|
1670
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1671
1724
|
// Using CDP to capture the screenshot
|
|
1672
1725
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1673
1726
|
document.body.scrollWidth,
|
|
@@ -1677,164 +1730,105 @@ class StableBrowser {
|
|
|
1677
1730
|
document.body.clientWidth,
|
|
1678
1731
|
document.documentElement.clientWidth,
|
|
1679
1732
|
])));
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
}
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
await client.detach();
|
|
1733
|
+
let screenshotBuffer = null;
|
|
1734
|
+
if (this.context.browserName === "chromium") {
|
|
1735
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1736
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1737
|
+
format: "png",
|
|
1738
|
+
// clip: {
|
|
1739
|
+
// x: 0,
|
|
1740
|
+
// y: 0,
|
|
1741
|
+
// width: viewportWidth,
|
|
1742
|
+
// height: viewportHeight,
|
|
1743
|
+
// scale: 1,
|
|
1744
|
+
// },
|
|
1745
|
+
});
|
|
1746
|
+
await client.detach();
|
|
1747
|
+
if (!screenshotPath) {
|
|
1748
|
+
return data;
|
|
1749
|
+
}
|
|
1750
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1751
|
+
}
|
|
1752
|
+
else {
|
|
1753
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1754
|
+
}
|
|
1755
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1756
|
+
// Get the image dimensions
|
|
1757
|
+
const { width, height } = image.bitmap;
|
|
1758
|
+
const resizeRatio = viewportWidth / width;
|
|
1759
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1760
|
+
if (width > viewportWidth) {
|
|
1761
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1762
|
+
await image.write(screenshotPath);
|
|
1763
|
+
}
|
|
1764
|
+
else {
|
|
1765
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1766
|
+
}
|
|
1715
1767
|
}
|
|
1716
1768
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1769
|
+
const state = {
|
|
1770
|
+
selectors,
|
|
1771
|
+
_params,
|
|
1772
|
+
options,
|
|
1773
|
+
world,
|
|
1774
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1775
|
+
text: `Verify element exists in page`,
|
|
1776
|
+
operation: "verifyElementExistInPage",
|
|
1777
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1778
|
+
};
|
|
1722
1779
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1723
|
-
const info = {};
|
|
1724
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1725
|
-
info.operation = "verify";
|
|
1726
|
-
info.selectors = selectors;
|
|
1727
1780
|
try {
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
}
|
|
1732
|
-
await this._highlightElements(element);
|
|
1733
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1734
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1735
|
-
return info;
|
|
1781
|
+
await _preCommand(state, this);
|
|
1782
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1783
|
+
return state.info;
|
|
1736
1784
|
}
|
|
1737
1785
|
catch (e) {
|
|
1738
|
-
|
|
1739
|
-
this.logger.error("verify failed " + info.log);
|
|
1740
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1741
|
-
info.screenshotPath = screenshotPath;
|
|
1742
|
-
Object.assign(e, { info: info });
|
|
1743
|
-
error = e;
|
|
1744
|
-
throw e;
|
|
1786
|
+
await _commandError(state, e, this);
|
|
1745
1787
|
}
|
|
1746
1788
|
finally {
|
|
1747
|
-
|
|
1748
|
-
this._reportToWorld(world, {
|
|
1749
|
-
element_name: selectors.element_name,
|
|
1750
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1751
|
-
text: "Verify element exists in page",
|
|
1752
|
-
screenshotId,
|
|
1753
|
-
result: error
|
|
1754
|
-
? {
|
|
1755
|
-
status: "FAILED",
|
|
1756
|
-
startTime,
|
|
1757
|
-
endTime,
|
|
1758
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1759
|
-
}
|
|
1760
|
-
: {
|
|
1761
|
-
status: "PASSED",
|
|
1762
|
-
startTime,
|
|
1763
|
-
endTime,
|
|
1764
|
-
},
|
|
1765
|
-
info: info,
|
|
1766
|
-
});
|
|
1789
|
+
_commandFinally(state, this);
|
|
1767
1790
|
}
|
|
1768
1791
|
}
|
|
1769
1792
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1793
|
+
const state = {
|
|
1794
|
+
selectors,
|
|
1795
|
+
_params,
|
|
1796
|
+
attribute,
|
|
1797
|
+
variable,
|
|
1798
|
+
options,
|
|
1799
|
+
world,
|
|
1800
|
+
type: Types.EXTRACT,
|
|
1801
|
+
text: `Extract attribute from element`,
|
|
1802
|
+
operation: "extractAttribute",
|
|
1803
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1804
|
+
};
|
|
1775
1805
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1776
|
-
const info = {};
|
|
1777
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1778
|
-
info.operation = "extract";
|
|
1779
|
-
info.selectors = selectors;
|
|
1780
1806
|
try {
|
|
1781
|
-
|
|
1782
|
-
await this._highlightElements(element);
|
|
1783
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1807
|
+
await _preCommand(state, this);
|
|
1784
1808
|
switch (attribute) {
|
|
1785
1809
|
case "inner_text":
|
|
1786
|
-
|
|
1810
|
+
state.value = await state.element.innerText();
|
|
1787
1811
|
break;
|
|
1788
1812
|
case "href":
|
|
1789
|
-
|
|
1813
|
+
state.value = await state.element.getAttribute("href");
|
|
1790
1814
|
break;
|
|
1791
1815
|
case "value":
|
|
1792
|
-
|
|
1816
|
+
state.value = await state.element.inputValue();
|
|
1793
1817
|
break;
|
|
1794
1818
|
default:
|
|
1795
|
-
|
|
1819
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1796
1820
|
break;
|
|
1797
1821
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
this.setTestData({ [variable]: info.value }, world);
|
|
1803
|
-
this.logger.info("set test data: " + variable + "=" + info.value);
|
|
1804
|
-
return info;
|
|
1822
|
+
state.info.value = state.value;
|
|
1823
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1824
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1825
|
+
return state.info;
|
|
1805
1826
|
}
|
|
1806
1827
|
catch (e) {
|
|
1807
|
-
|
|
1808
|
-
this.logger.error("extract failed " + info.log);
|
|
1809
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1810
|
-
info.screenshotPath = screenshotPath;
|
|
1811
|
-
Object.assign(e, { info: info });
|
|
1812
|
-
error = e;
|
|
1813
|
-
throw e;
|
|
1828
|
+
await _commandError(state, e, this);
|
|
1814
1829
|
}
|
|
1815
1830
|
finally {
|
|
1816
|
-
|
|
1817
|
-
this._reportToWorld(world, {
|
|
1818
|
-
element_name: selectors.element_name,
|
|
1819
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1820
|
-
variable: variable,
|
|
1821
|
-
value: info.value,
|
|
1822
|
-
text: "Extract attribute from element",
|
|
1823
|
-
screenshotId,
|
|
1824
|
-
result: error
|
|
1825
|
-
? {
|
|
1826
|
-
status: "FAILED",
|
|
1827
|
-
startTime,
|
|
1828
|
-
endTime,
|
|
1829
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1830
|
-
}
|
|
1831
|
-
: {
|
|
1832
|
-
status: "PASSED",
|
|
1833
|
-
startTime,
|
|
1834
|
-
endTime,
|
|
1835
|
-
},
|
|
1836
|
-
info: info,
|
|
1837
|
-
});
|
|
1831
|
+
_commandFinally(state, this);
|
|
1838
1832
|
}
|
|
1839
1833
|
}
|
|
1840
1834
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1911,7 +1905,8 @@ class StableBrowser {
|
|
|
1911
1905
|
catch (e) {
|
|
1912
1906
|
errorCount++;
|
|
1913
1907
|
if (errorCount > 3) {
|
|
1914
|
-
throw e;
|
|
1908
|
+
// throw e;
|
|
1909
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1915
1910
|
}
|
|
1916
1911
|
// ignore
|
|
1917
1912
|
}
|
|
@@ -2022,7 +2017,8 @@ class StableBrowser {
|
|
|
2022
2017
|
info.screenshotPath = screenshotPath;
|
|
2023
2018
|
Object.assign(e, { info: info });
|
|
2024
2019
|
error = e;
|
|
2025
|
-
throw e;
|
|
2020
|
+
// throw e;
|
|
2021
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2026
2022
|
}
|
|
2027
2023
|
finally {
|
|
2028
2024
|
const endTime = Date.now();
|
|
@@ -2035,7 +2031,7 @@ class StableBrowser {
|
|
|
2035
2031
|
status: "FAILED",
|
|
2036
2032
|
startTime,
|
|
2037
2033
|
endTime,
|
|
2038
|
-
message: error
|
|
2034
|
+
message: error?.message,
|
|
2039
2035
|
}
|
|
2040
2036
|
: {
|
|
2041
2037
|
status: "PASSED",
|
|
@@ -2047,52 +2043,59 @@ class StableBrowser {
|
|
|
2047
2043
|
}
|
|
2048
2044
|
}
|
|
2049
2045
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2050
|
-
|
|
2046
|
+
text = unEscapeString(text);
|
|
2047
|
+
const state = {
|
|
2048
|
+
text_search: text,
|
|
2049
|
+
options,
|
|
2050
|
+
world,
|
|
2051
|
+
locate: false,
|
|
2052
|
+
scroll: false,
|
|
2053
|
+
highlight: false,
|
|
2054
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2055
|
+
text: `Verify text exists in page`,
|
|
2056
|
+
operation: "verifyTextExistInPage",
|
|
2057
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2058
|
+
};
|
|
2051
2059
|
const timeout = this._getLoadTimeout(options);
|
|
2052
|
-
let error = null;
|
|
2053
|
-
let screenshotId = null;
|
|
2054
|
-
let screenshotPath = null;
|
|
2055
2060
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2056
|
-
const info = {};
|
|
2057
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2058
|
-
info.operation = "verifyTextExistInPage";
|
|
2059
2061
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2060
2062
|
if (newValue !== text) {
|
|
2061
2063
|
this.logger.info(text + "=" + newValue);
|
|
2062
2064
|
text = newValue;
|
|
2063
2065
|
}
|
|
2064
|
-
info.text = text;
|
|
2065
2066
|
let dateAlternatives = findDateAlternatives(text);
|
|
2066
2067
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2067
2068
|
try {
|
|
2069
|
+
await _preCommand(state, this);
|
|
2070
|
+
state.info.text = text;
|
|
2068
2071
|
while (true) {
|
|
2069
2072
|
const frames = this.page.frames();
|
|
2070
2073
|
let results = [];
|
|
2071
2074
|
for (let i = 0; i < frames.length; i++) {
|
|
2072
2075
|
if (dateAlternatives.date) {
|
|
2073
2076
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2074
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "
|
|
2077
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2075
2078
|
result.frame = frames[i];
|
|
2076
2079
|
results.push(result);
|
|
2077
2080
|
}
|
|
2078
2081
|
}
|
|
2079
2082
|
else if (numberAlternatives.number) {
|
|
2080
2083
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2081
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "
|
|
2084
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2082
2085
|
result.frame = frames[i];
|
|
2083
2086
|
results.push(result);
|
|
2084
2087
|
}
|
|
2085
2088
|
}
|
|
2086
2089
|
else {
|
|
2087
|
-
const result = await this._locateElementByText(frames[i], text, "
|
|
2090
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2088
2091
|
result.frame = frames[i];
|
|
2089
2092
|
results.push(result);
|
|
2090
2093
|
}
|
|
2091
2094
|
}
|
|
2092
|
-
info.results = results;
|
|
2095
|
+
state.info.results = results;
|
|
2093
2096
|
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2094
2097
|
if (resultWithElementsFound.length === 0) {
|
|
2095
|
-
if (Date.now() - startTime > timeout) {
|
|
2098
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2096
2099
|
throw new Error(`Text ${text} not found in page`);
|
|
2097
2100
|
}
|
|
2098
2101
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2104,44 +2107,20 @@ class StableBrowser {
|
|
|
2104
2107
|
await this._highlightElements(frame, dataAttribute);
|
|
2105
2108
|
const element = await frame.$(dataAttribute);
|
|
2106
2109
|
if (element) {
|
|
2107
|
-
await this.scrollIfNeeded(element, info);
|
|
2110
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2108
2111
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2109
2112
|
}
|
|
2110
2113
|
}
|
|
2111
|
-
|
|
2112
|
-
return info;
|
|
2114
|
+
await _screenshot(state, this);
|
|
2115
|
+
return state.info;
|
|
2113
2116
|
}
|
|
2114
2117
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2115
2118
|
}
|
|
2116
2119
|
catch (e) {
|
|
2117
|
-
|
|
2118
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2119
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2120
|
-
info.screenshotPath = screenshotPath;
|
|
2121
|
-
Object.assign(e, { info: info });
|
|
2122
|
-
error = e;
|
|
2123
|
-
throw e;
|
|
2120
|
+
await _commandError(state, e, this);
|
|
2124
2121
|
}
|
|
2125
2122
|
finally {
|
|
2126
|
-
|
|
2127
|
-
this._reportToWorld(world, {
|
|
2128
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2129
|
-
text: "Verify text exists in page",
|
|
2130
|
-
screenshotId,
|
|
2131
|
-
result: error
|
|
2132
|
-
? {
|
|
2133
|
-
status: "FAILED",
|
|
2134
|
-
startTime,
|
|
2135
|
-
endTime,
|
|
2136
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2137
|
-
}
|
|
2138
|
-
: {
|
|
2139
|
-
status: "PASSED",
|
|
2140
|
-
startTime,
|
|
2141
|
-
endTime,
|
|
2142
|
-
},
|
|
2143
|
-
info: info,
|
|
2144
|
-
});
|
|
2123
|
+
_commandFinally(state, this);
|
|
2145
2124
|
}
|
|
2146
2125
|
}
|
|
2147
2126
|
_getServerUrl() {
|
|
@@ -2204,7 +2183,8 @@ class StableBrowser {
|
|
|
2204
2183
|
info.screenshotPath = screenshotPath;
|
|
2205
2184
|
Object.assign(e, { info: info });
|
|
2206
2185
|
error = e;
|
|
2207
|
-
throw e;
|
|
2186
|
+
// throw e;
|
|
2187
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2208
2188
|
}
|
|
2209
2189
|
finally {
|
|
2210
2190
|
const endTime = Date.now();
|
|
@@ -2217,7 +2197,7 @@ class StableBrowser {
|
|
|
2217
2197
|
status: "FAILED",
|
|
2218
2198
|
startTime,
|
|
2219
2199
|
endTime,
|
|
2220
|
-
message: error
|
|
2200
|
+
message: error?.message,
|
|
2221
2201
|
}
|
|
2222
2202
|
: {
|
|
2223
2203
|
status: "PASSED",
|
|
@@ -2249,7 +2229,7 @@ class StableBrowser {
|
|
|
2249
2229
|
this.logger.info("Table data verified");
|
|
2250
2230
|
}
|
|
2251
2231
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2252
|
-
|
|
2232
|
+
_validateSelectors(selectors);
|
|
2253
2233
|
const startTime = Date.now();
|
|
2254
2234
|
let error = null;
|
|
2255
2235
|
let screenshotId = null;
|
|
@@ -2271,7 +2251,8 @@ class StableBrowser {
|
|
|
2271
2251
|
info.screenshotPath = screenshotPath;
|
|
2272
2252
|
Object.assign(e, { info: info });
|
|
2273
2253
|
error = e;
|
|
2274
|
-
throw e;
|
|
2254
|
+
// throw e;
|
|
2255
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2275
2256
|
}
|
|
2276
2257
|
finally {
|
|
2277
2258
|
const endTime = Date.now();
|
|
@@ -2285,7 +2266,7 @@ class StableBrowser {
|
|
|
2285
2266
|
status: "FAILED",
|
|
2286
2267
|
startTime,
|
|
2287
2268
|
endTime,
|
|
2288
|
-
message: error
|
|
2269
|
+
message: error?.message,
|
|
2289
2270
|
}
|
|
2290
2271
|
: {
|
|
2291
2272
|
status: "PASSED",
|
|
@@ -2297,7 +2278,7 @@ class StableBrowser {
|
|
|
2297
2278
|
}
|
|
2298
2279
|
}
|
|
2299
2280
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2300
|
-
|
|
2281
|
+
_validateSelectors(selectors);
|
|
2301
2282
|
if (!query) {
|
|
2302
2283
|
throw new Error("query is null");
|
|
2303
2284
|
}
|
|
@@ -2436,7 +2417,8 @@ class StableBrowser {
|
|
|
2436
2417
|
info.screenshotPath = screenshotPath;
|
|
2437
2418
|
Object.assign(e, { info: info });
|
|
2438
2419
|
error = e;
|
|
2439
|
-
throw e;
|
|
2420
|
+
// throw e;
|
|
2421
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2440
2422
|
}
|
|
2441
2423
|
finally {
|
|
2442
2424
|
const endTime = Date.now();
|
|
@@ -2450,7 +2432,7 @@ class StableBrowser {
|
|
|
2450
2432
|
status: "FAILED",
|
|
2451
2433
|
startTime,
|
|
2452
2434
|
endTime,
|
|
2453
|
-
message: error
|
|
2435
|
+
message: error?.message,
|
|
2454
2436
|
}
|
|
2455
2437
|
: {
|
|
2456
2438
|
status: "PASSED",
|
|
@@ -2462,27 +2444,7 @@ class StableBrowser {
|
|
|
2462
2444
|
}
|
|
2463
2445
|
}
|
|
2464
2446
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2465
|
-
|
|
2466
|
-
return value;
|
|
2467
|
-
}
|
|
2468
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2469
|
-
let regex = /{{(.*?)}}/g;
|
|
2470
|
-
let matches = value.match(regex);
|
|
2471
|
-
if (matches) {
|
|
2472
|
-
const testData = this.getTestData(world);
|
|
2473
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2474
|
-
let match = matches[i];
|
|
2475
|
-
let key = match.substring(2, match.length - 2);
|
|
2476
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2477
|
-
if (newValue !== null) {
|
|
2478
|
-
value = value.replace(match, newValue);
|
|
2479
|
-
}
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2483
|
-
return await decrypt(value, null, totpWait);
|
|
2484
|
-
}
|
|
2485
|
-
return value;
|
|
2447
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2486
2448
|
}
|
|
2487
2449
|
_getLoadTimeout(options) {
|
|
2488
2450
|
let timeout = 15000;
|
|
@@ -2519,13 +2481,13 @@ class StableBrowser {
|
|
|
2519
2481
|
}
|
|
2520
2482
|
catch (e) {
|
|
2521
2483
|
if (e.label === "networkidle") {
|
|
2522
|
-
console.log("
|
|
2484
|
+
console.log("waited for the network to be idle timeout");
|
|
2523
2485
|
}
|
|
2524
2486
|
else if (e.label === "load") {
|
|
2525
|
-
console.log("
|
|
2487
|
+
console.log("waited for the load timeout");
|
|
2526
2488
|
}
|
|
2527
2489
|
else if (e.label === "domcontentloaded") {
|
|
2528
|
-
console.log("
|
|
2490
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2529
2491
|
}
|
|
2530
2492
|
console.log(".");
|
|
2531
2493
|
}
|
|
@@ -2542,7 +2504,7 @@ class StableBrowser {
|
|
|
2542
2504
|
status: "FAILED",
|
|
2543
2505
|
startTime,
|
|
2544
2506
|
endTime,
|
|
2545
|
-
message: error
|
|
2507
|
+
message: error?.message,
|
|
2546
2508
|
}
|
|
2547
2509
|
: {
|
|
2548
2510
|
status: "PASSED",
|
|
@@ -2553,46 +2515,28 @@ class StableBrowser {
|
|
|
2553
2515
|
}
|
|
2554
2516
|
}
|
|
2555
2517
|
async closePage(options = {}, world = null) {
|
|
2556
|
-
const
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2518
|
+
const state = {
|
|
2519
|
+
options,
|
|
2520
|
+
world,
|
|
2521
|
+
locate: false,
|
|
2522
|
+
scroll: false,
|
|
2523
|
+
highlight: false,
|
|
2524
|
+
type: Types.CLOSE_PAGE,
|
|
2525
|
+
text: `Close page`,
|
|
2526
|
+
operation: "closePage",
|
|
2527
|
+
log: "***** close page *****\n",
|
|
2528
|
+
throwError: false,
|
|
2529
|
+
};
|
|
2561
2530
|
try {
|
|
2531
|
+
await _preCommand(state, this);
|
|
2562
2532
|
await this.page.close();
|
|
2563
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2564
|
-
this.context.pages.pop();
|
|
2565
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2566
|
-
this.context.page = this.page;
|
|
2567
|
-
let title = await this.page.title();
|
|
2568
|
-
console.log("Switched to page " + title);
|
|
2569
|
-
}
|
|
2570
2533
|
}
|
|
2571
2534
|
catch (e) {
|
|
2572
2535
|
console.log(".");
|
|
2536
|
+
await _commandError(state, e, this);
|
|
2573
2537
|
}
|
|
2574
2538
|
finally {
|
|
2575
|
-
|
|
2576
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2577
|
-
const endTime = Date.now();
|
|
2578
|
-
this._reportToWorld(world, {
|
|
2579
|
-
type: Types.CLOSE_PAGE,
|
|
2580
|
-
text: "close page",
|
|
2581
|
-
screenshotId,
|
|
2582
|
-
result: error
|
|
2583
|
-
? {
|
|
2584
|
-
status: "FAILED",
|
|
2585
|
-
startTime,
|
|
2586
|
-
endTime,
|
|
2587
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2588
|
-
}
|
|
2589
|
-
: {
|
|
2590
|
-
status: "PASSED",
|
|
2591
|
-
startTime,
|
|
2592
|
-
endTime,
|
|
2593
|
-
},
|
|
2594
|
-
info: info,
|
|
2595
|
-
});
|
|
2539
|
+
_commandFinally(state, this);
|
|
2596
2540
|
}
|
|
2597
2541
|
}
|
|
2598
2542
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
@@ -2612,6 +2556,7 @@ class StableBrowser {
|
|
|
2612
2556
|
}
|
|
2613
2557
|
catch (e) {
|
|
2614
2558
|
console.log(".");
|
|
2559
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2615
2560
|
}
|
|
2616
2561
|
finally {
|
|
2617
2562
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -2626,7 +2571,7 @@ class StableBrowser {
|
|
|
2626
2571
|
status: "FAILED",
|
|
2627
2572
|
startTime,
|
|
2628
2573
|
endTime,
|
|
2629
|
-
message: error
|
|
2574
|
+
message: error?.message,
|
|
2630
2575
|
}
|
|
2631
2576
|
: {
|
|
2632
2577
|
status: "PASSED",
|
|
@@ -2648,6 +2593,7 @@ class StableBrowser {
|
|
|
2648
2593
|
}
|
|
2649
2594
|
catch (e) {
|
|
2650
2595
|
console.log(".");
|
|
2596
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2651
2597
|
}
|
|
2652
2598
|
finally {
|
|
2653
2599
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -2662,7 +2608,7 @@ class StableBrowser {
|
|
|
2662
2608
|
status: "FAILED",
|
|
2663
2609
|
startTime,
|
|
2664
2610
|
endTime,
|
|
2665
|
-
message: error
|
|
2611
|
+
message: error?.message,
|
|
2666
2612
|
}
|
|
2667
2613
|
: {
|
|
2668
2614
|
status: "PASSED",
|
|
@@ -2675,33 +2621,18 @@ class StableBrowser {
|
|
|
2675
2621
|
}
|
|
2676
2622
|
async scrollIfNeeded(element, info) {
|
|
2677
2623
|
try {
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
if (rect &&
|
|
2681
|
-
rect.top >= 0 &&
|
|
2682
|
-
rect.left >= 0 &&
|
|
2683
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2684
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2685
|
-
return false;
|
|
2686
|
-
}
|
|
2687
|
-
else {
|
|
2688
|
-
node.scrollIntoView({
|
|
2689
|
-
behavior: "smooth",
|
|
2690
|
-
block: "center",
|
|
2691
|
-
inline: "center",
|
|
2692
|
-
});
|
|
2693
|
-
return true;
|
|
2694
|
-
}
|
|
2624
|
+
await element.scrollIntoViewIfNeeded({
|
|
2625
|
+
timeout: 2000,
|
|
2695
2626
|
});
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
}
|
|
2627
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2628
|
+
if (info) {
|
|
2629
|
+
info.box = await element.boundingBox({
|
|
2630
|
+
timeout: 1000,
|
|
2631
|
+
});
|
|
2701
2632
|
}
|
|
2702
2633
|
}
|
|
2703
2634
|
catch (e) {
|
|
2704
|
-
console.log("
|
|
2635
|
+
console.log("#-#");
|
|
2705
2636
|
}
|
|
2706
2637
|
}
|
|
2707
2638
|
_reportToWorld(world, properties) {
|
|
@@ -2862,5 +2793,10 @@ const KEYBOARD_EVENTS = [
|
|
|
2862
2793
|
"TVAntennaCable",
|
|
2863
2794
|
"TVAudioDescription",
|
|
2864
2795
|
];
|
|
2796
|
+
function unEscapeString(str) {
|
|
2797
|
+
const placeholder = "__NEWLINE__";
|
|
2798
|
+
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2799
|
+
return str;
|
|
2800
|
+
}
|
|
2865
2801
|
export { StableBrowser };
|
|
2866
2802
|
//# sourceMappingURL=stable_browser.js.map
|