automation_model 1.0.399-dev → 1.0.399-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 +6 -2
- package/lib/auto_page.js.map +1 -1
- package/lib/axe/axe.mini.js +12 -0
- package/lib/browser_manager.js +8 -3
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +5 -0
- package/lib/command_common.js +107 -0
- package/lib/command_common.js.map +1 -0
- package/lib/environment.d.ts +3 -0
- package/lib/environment.js +5 -2
- package/lib/environment.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +38 -3
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.d.ts +7 -0
- package/lib/locate_element.js +213 -0
- package/lib/locate_element.js.map +1 -0
- package/lib/stable_browser.d.ts +31 -16
- package/lib/stable_browser.js +694 -699
- 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.js +2 -2
- package/lib/utils.js.map +1 -1
- package/package.json +9 -7
package/lib/stable_browser.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
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";
|
|
@@ -13,6 +13,11 @@ import { getTableCells, getTableData } from "./table_analyze.js";
|
|
|
13
13
|
import objectPath from "object-path";
|
|
14
14
|
import { decrypt } from "./utils.js";
|
|
15
15
|
import csv from "csv-parser";
|
|
16
|
+
import { Readable } from "node:stream";
|
|
17
|
+
import readline from "readline";
|
|
18
|
+
import { getContext } from "./init_browser.js";
|
|
19
|
+
import { locate_element } from "./locate_element.js";
|
|
20
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.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,36 @@ 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
|
+
}
|
|
96
|
+
registerEventListeners(context) {
|
|
97
|
+
this.registerConsoleLogListener(this.page, context);
|
|
98
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
99
|
+
if (!context.pageLoading) {
|
|
100
|
+
context.pageLoading = { status: false };
|
|
101
|
+
}
|
|
80
102
|
context.playContext.on("page", async function (page) {
|
|
81
103
|
context.pageLoading.status = true;
|
|
82
104
|
this.page = page;
|
|
83
105
|
context.page = page;
|
|
84
106
|
context.pages.push(page);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
107
|
+
page.on("close", async () => {
|
|
108
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
109
|
+
this.context.pages.pop();
|
|
110
|
+
this.page = this.context.pages[this.context.pages.length - 1];
|
|
111
|
+
this.context.page = this.page;
|
|
112
|
+
try {
|
|
113
|
+
let title = await this.page.title();
|
|
114
|
+
console.log("Switched to page " + title);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error("Error on page close", error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
88
121
|
try {
|
|
89
122
|
await this.waitForPageLoad();
|
|
90
123
|
console.log("Switch page: " + (await page.title()));
|
|
@@ -95,6 +128,36 @@ class StableBrowser {
|
|
|
95
128
|
context.pageLoading.status = false;
|
|
96
129
|
}.bind(this));
|
|
97
130
|
}
|
|
131
|
+
async switchApp(appName) {
|
|
132
|
+
// check if the current app (this.appName) is the same as the new app
|
|
133
|
+
if (this.appName === appName) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
let navigate = false;
|
|
137
|
+
if (!apps[appName]) {
|
|
138
|
+
let newContext = await getContext(null, false, this.logger, appName, false, this);
|
|
139
|
+
navigate = true;
|
|
140
|
+
apps[appName] = {
|
|
141
|
+
context: newContext,
|
|
142
|
+
browser: newContext.browser,
|
|
143
|
+
page: newContext.page,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const tempContext = {};
|
|
147
|
+
this._copyContext(this, tempContext);
|
|
148
|
+
this._copyContext(apps[appName], this);
|
|
149
|
+
apps[this.appName] = tempContext;
|
|
150
|
+
this.appName = appName;
|
|
151
|
+
if (navigate) {
|
|
152
|
+
await this.goto(this.context.environment.baseUrl);
|
|
153
|
+
await this.waitForPageLoad();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
_copyContext(from, to) {
|
|
157
|
+
to.browser = from.browser;
|
|
158
|
+
to.page = from.page;
|
|
159
|
+
to.context = from.context;
|
|
160
|
+
}
|
|
98
161
|
getWebLogFile(logFolder) {
|
|
99
162
|
if (!fs.existsSync(logFolder)) {
|
|
100
163
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -106,37 +169,63 @@ class StableBrowser {
|
|
|
106
169
|
const fileName = nextIndex + ".json";
|
|
107
170
|
return path.join(logFolder, fileName);
|
|
108
171
|
}
|
|
109
|
-
registerConsoleLogListener(page, context
|
|
172
|
+
registerConsoleLogListener(page, context) {
|
|
110
173
|
if (!this.context.webLogger) {
|
|
111
174
|
this.context.webLogger = [];
|
|
112
175
|
}
|
|
113
176
|
page.on("console", async (msg) => {
|
|
114
|
-
|
|
177
|
+
const obj = {
|
|
115
178
|
type: msg.type(),
|
|
116
179
|
text: msg.text(),
|
|
117
180
|
location: msg.location(),
|
|
118
181
|
time: new Date().toISOString(),
|
|
119
|
-
}
|
|
120
|
-
|
|
182
|
+
};
|
|
183
|
+
this.context.webLogger.push(obj);
|
|
184
|
+
if (msg.type() === "error") {
|
|
185
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
186
|
+
}
|
|
121
187
|
});
|
|
122
188
|
}
|
|
123
|
-
registerRequestListener() {
|
|
124
|
-
this.
|
|
189
|
+
registerRequestListener(page, context, logFile) {
|
|
190
|
+
if (!this.context.networkLogger) {
|
|
191
|
+
this.context.networkLogger = [];
|
|
192
|
+
}
|
|
193
|
+
page.on("request", async (data) => {
|
|
194
|
+
const startTime = new Date().getTime();
|
|
125
195
|
try {
|
|
126
|
-
const pageUrl = new URL(
|
|
196
|
+
const pageUrl = new URL(page.url());
|
|
127
197
|
const requestUrl = new URL(data.url());
|
|
128
198
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
129
199
|
const method = data.method();
|
|
130
|
-
if (
|
|
200
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
131
201
|
const token = await data.headerValue("Authorization");
|
|
132
202
|
if (token) {
|
|
133
|
-
|
|
203
|
+
context.authtoken = token;
|
|
134
204
|
}
|
|
135
205
|
}
|
|
136
206
|
}
|
|
207
|
+
const response = await data.response();
|
|
208
|
+
const endTime = new Date().getTime();
|
|
209
|
+
const obj = {
|
|
210
|
+
url: data.url(),
|
|
211
|
+
method: data.method(),
|
|
212
|
+
postData: data.postData(),
|
|
213
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
214
|
+
duration: endTime - startTime,
|
|
215
|
+
startTime,
|
|
216
|
+
};
|
|
217
|
+
context.networkLogger.push(obj);
|
|
218
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
137
219
|
}
|
|
138
220
|
catch (error) {
|
|
139
221
|
console.error("Error in request listener", error);
|
|
222
|
+
context.networkLogger.push({
|
|
223
|
+
error: "not able to listen",
|
|
224
|
+
message: error.message,
|
|
225
|
+
stack: error.stack,
|
|
226
|
+
time: new Date().toISOString(),
|
|
227
|
+
});
|
|
228
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
140
229
|
}
|
|
141
230
|
});
|
|
142
231
|
}
|
|
@@ -151,20 +240,6 @@ class StableBrowser {
|
|
|
151
240
|
timeout: 60000,
|
|
152
241
|
});
|
|
153
242
|
}
|
|
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
243
|
_fixUsingParams(text, _params) {
|
|
169
244
|
if (!_params || typeof text !== "string") {
|
|
170
245
|
return text;
|
|
@@ -179,27 +254,84 @@ class StableBrowser {
|
|
|
179
254
|
}
|
|
180
255
|
return text;
|
|
181
256
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
257
|
+
_fixLocatorUsingParams(locator, _params) {
|
|
258
|
+
// check if not null
|
|
259
|
+
if (!locator) {
|
|
260
|
+
return locator;
|
|
261
|
+
}
|
|
262
|
+
// clone the locator
|
|
263
|
+
locator = JSON.parse(JSON.stringify(locator));
|
|
264
|
+
this.scanAndManipulate(locator, _params);
|
|
265
|
+
return locator;
|
|
266
|
+
}
|
|
267
|
+
_isObject(value) {
|
|
268
|
+
return value && typeof value === "object" && value.constructor === Object;
|
|
269
|
+
}
|
|
270
|
+
scanAndManipulate(currentObj, _params) {
|
|
271
|
+
for (const key in currentObj) {
|
|
272
|
+
if (typeof currentObj[key] === "string") {
|
|
273
|
+
// Perform string manipulation
|
|
274
|
+
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
275
|
+
}
|
|
276
|
+
else if (this._isObject(currentObj[key])) {
|
|
277
|
+
// Recursively scan nested objects
|
|
278
|
+
this.scanAndManipulate(currentObj[key], _params);
|
|
279
|
+
}
|
|
185
280
|
}
|
|
281
|
+
}
|
|
282
|
+
_getLocator(locator, scope, _params) {
|
|
283
|
+
locator = this._fixLocatorUsingParams(locator, _params);
|
|
284
|
+
let locatorReturn;
|
|
186
285
|
if (locator.role) {
|
|
187
286
|
if (locator.role[1].nameReg) {
|
|
188
287
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
189
288
|
delete locator.role[1].nameReg;
|
|
190
289
|
}
|
|
191
|
-
if (locator.role[1].name) {
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
290
|
+
// if (locator.role[1].name) {
|
|
291
|
+
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
292
|
+
// }
|
|
293
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
195
294
|
}
|
|
196
295
|
if (locator.css) {
|
|
197
|
-
|
|
296
|
+
locatorReturn = scope.locator(locator.css);
|
|
198
297
|
}
|
|
199
|
-
|
|
298
|
+
// handle role/name locators
|
|
299
|
+
// locator.selector will be something like: textbox[name="Username"i]
|
|
300
|
+
if (locator.engine === "internal:role") {
|
|
301
|
+
// extract the role, name and the i/s flags using regex
|
|
302
|
+
const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
|
|
303
|
+
if (match) {
|
|
304
|
+
const role = match[1];
|
|
305
|
+
const name = match[3];
|
|
306
|
+
const flags = match[4];
|
|
307
|
+
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (locator?.engine) {
|
|
311
|
+
if (locator.engine === "css") {
|
|
312
|
+
locatorReturn = scope.locator(locator.selector);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
let selector = locator.selector;
|
|
316
|
+
if (locator.engine === "internal:attr") {
|
|
317
|
+
if (!selector.startsWith("[")) {
|
|
318
|
+
selector = `[${selector}]`;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (!locatorReturn) {
|
|
325
|
+
console.error(locator);
|
|
326
|
+
throw new Error("Locator undefined");
|
|
327
|
+
}
|
|
328
|
+
return locatorReturn;
|
|
200
329
|
}
|
|
201
330
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
202
|
-
|
|
331
|
+
if (css && css.locator) {
|
|
332
|
+
css = css.locator;
|
|
333
|
+
}
|
|
334
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
|
|
203
335
|
if (result.elementCount === 0) {
|
|
204
336
|
return;
|
|
205
337
|
}
|
|
@@ -214,7 +346,7 @@ class StableBrowser {
|
|
|
214
346
|
}
|
|
215
347
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
216
348
|
//const stringifyText = JSON.stringify(text);
|
|
217
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
349
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
218
350
|
function isParent(parent, child) {
|
|
219
351
|
let currentNode = child.parentNode;
|
|
220
352
|
while (currentNode !== null) {
|
|
@@ -226,6 +358,15 @@ class StableBrowser {
|
|
|
226
358
|
return false;
|
|
227
359
|
}
|
|
228
360
|
document.isParent = isParent;
|
|
361
|
+
function getRegex(str) {
|
|
362
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
363
|
+
if (!match) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
let [_, pattern, flags] = match;
|
|
367
|
+
return new RegExp(pattern, flags);
|
|
368
|
+
}
|
|
369
|
+
document.getRegex = getRegex;
|
|
229
370
|
function collectAllShadowDomElements(element, result = []) {
|
|
230
371
|
// Check and add the element if it has a shadow root
|
|
231
372
|
if (element.shadowRoot) {
|
|
@@ -244,6 +385,10 @@ class StableBrowser {
|
|
|
244
385
|
if (!tag) {
|
|
245
386
|
tag = "*";
|
|
246
387
|
}
|
|
388
|
+
let regexpSearch = document.getRegex(text);
|
|
389
|
+
if (regexpSearch) {
|
|
390
|
+
regex = true;
|
|
391
|
+
}
|
|
247
392
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
248
393
|
let shadowHosts = [];
|
|
249
394
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -259,7 +404,9 @@ class StableBrowser {
|
|
|
259
404
|
let randomToken = null;
|
|
260
405
|
const foundElements = [];
|
|
261
406
|
if (regex) {
|
|
262
|
-
|
|
407
|
+
if (!regexpSearch) {
|
|
408
|
+
regexpSearch = new RegExp(text, "im");
|
|
409
|
+
}
|
|
263
410
|
for (let i = 0; i < elements.length; i++) {
|
|
264
411
|
const element = elements[i];
|
|
265
412
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -273,8 +420,8 @@ class StableBrowser {
|
|
|
273
420
|
for (let i = 0; i < elements.length; i++) {
|
|
274
421
|
const element = elements[i];
|
|
275
422
|
if (partial) {
|
|
276
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
277
|
-
(element.value && element.value.includes(text))) {
|
|
423
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
424
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
278
425
|
foundElements.push(element);
|
|
279
426
|
}
|
|
280
427
|
}
|
|
@@ -319,6 +466,12 @@ class StableBrowser {
|
|
|
319
466
|
}
|
|
320
467
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
321
468
|
let locatorSearch = selectorHierarchy[index];
|
|
469
|
+
try {
|
|
470
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
471
|
+
}
|
|
472
|
+
catch (e) {
|
|
473
|
+
console.error(e);
|
|
474
|
+
}
|
|
322
475
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
323
476
|
let locator = null;
|
|
324
477
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
@@ -407,15 +560,20 @@ class StableBrowser {
|
|
|
407
560
|
if (result.foundElements.length > 0) {
|
|
408
561
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
409
562
|
await dialogCloseLocator.click();
|
|
563
|
+
// wait for the dialog to close
|
|
564
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
410
565
|
return { rerun: true };
|
|
411
566
|
}
|
|
412
567
|
}
|
|
413
568
|
}
|
|
414
569
|
return { rerun: false };
|
|
415
570
|
}
|
|
416
|
-
async _locate(selectors, info, _params, timeout
|
|
571
|
+
async _locate(selectors, info, _params, timeout) {
|
|
572
|
+
if (!timeout) {
|
|
573
|
+
timeout = 30000;
|
|
574
|
+
}
|
|
417
575
|
for (let i = 0; i < 3; i++) {
|
|
418
|
-
info.log += "attempt " + i + ":
|
|
576
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
419
577
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
420
578
|
let selector = selectors.locators[j];
|
|
421
579
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -427,17 +585,44 @@ class StableBrowser {
|
|
|
427
585
|
}
|
|
428
586
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
429
587
|
}
|
|
430
|
-
async
|
|
431
|
-
let highPriorityTimeout = 5000;
|
|
432
|
-
let visibleOnlyTimeout = 6000;
|
|
433
|
-
let startTime = performance.now();
|
|
434
|
-
let locatorsCount = 0;
|
|
435
|
-
//let arrayMode = Array.isArray(selectors);
|
|
588
|
+
async _findFrameScope(selectors, timeout = 30000) {
|
|
436
589
|
let scope = this.page;
|
|
590
|
+
if (selectors.frame) {
|
|
591
|
+
return selectors.frame;
|
|
592
|
+
}
|
|
437
593
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
438
|
-
|
|
594
|
+
const findFrame = async (frame, framescope) => {
|
|
595
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
596
|
+
let frameLocator = frame.selectors[i];
|
|
597
|
+
if (frameLocator.css) {
|
|
598
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
599
|
+
if (frameLocator.index) {
|
|
600
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
604
|
+
timeout: 5000,
|
|
605
|
+
});
|
|
606
|
+
framescope = testframescope;
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
console.error("frame not found " + frameLocator.css);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
if (frame.children) {
|
|
615
|
+
return await findFrame(frame.children, framescope);
|
|
616
|
+
}
|
|
617
|
+
return framescope;
|
|
618
|
+
};
|
|
439
619
|
while (true) {
|
|
440
620
|
let frameFound = false;
|
|
621
|
+
if (selectors.nestFrmLoc) {
|
|
622
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
623
|
+
frameFound = true;
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
441
626
|
if (selectors.frameLocators) {
|
|
442
627
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
443
628
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -463,6 +648,25 @@ class StableBrowser {
|
|
|
463
648
|
}
|
|
464
649
|
}
|
|
465
650
|
}
|
|
651
|
+
if (!scope) {
|
|
652
|
+
scope = this.page;
|
|
653
|
+
}
|
|
654
|
+
return scope;
|
|
655
|
+
}
|
|
656
|
+
async _getDocumentBody(selectors, timeout = 30000) {
|
|
657
|
+
let scope = await this._findFrameScope(selectors, timeout);
|
|
658
|
+
return scope.evaluate(() => {
|
|
659
|
+
var bodyContent = document.body.innerHTML;
|
|
660
|
+
return bodyContent;
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
664
|
+
let highPriorityTimeout = 5000;
|
|
665
|
+
let visibleOnlyTimeout = 6000;
|
|
666
|
+
let startTime = performance.now();
|
|
667
|
+
let locatorsCount = 0;
|
|
668
|
+
//let arrayMode = Array.isArray(selectors);
|
|
669
|
+
let scope = await this._findFrameScope(selectors, timeout);
|
|
466
670
|
let selectorsLocators = null;
|
|
467
671
|
selectorsLocators = selectors.locators;
|
|
468
672
|
// group selectors by priority
|
|
@@ -598,83 +802,121 @@ class StableBrowser {
|
|
|
598
802
|
}
|
|
599
803
|
return result;
|
|
600
804
|
}
|
|
601
|
-
async
|
|
602
|
-
this._validateSelectors(selectors);
|
|
805
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
603
806
|
const startTime = Date.now();
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
807
|
+
let timeout = 30000;
|
|
808
|
+
if (options && options.timeout) {
|
|
809
|
+
timeout = options.timeout;
|
|
810
|
+
}
|
|
811
|
+
while (true) {
|
|
812
|
+
try {
|
|
813
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
814
|
+
if (result?.elementNumber >= 0) {
|
|
815
|
+
const selectors = {
|
|
816
|
+
frame: result?.frame,
|
|
817
|
+
locators: [
|
|
818
|
+
{
|
|
819
|
+
css: result?.css,
|
|
820
|
+
},
|
|
821
|
+
],
|
|
822
|
+
};
|
|
823
|
+
await this.click(selectors, _params, options, world);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
catch (e) {
|
|
828
|
+
if (performance.now() - startTime > timeout) {
|
|
829
|
+
throw e;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
836
|
+
const startTime = Date.now();
|
|
837
|
+
let timeout = 30000;
|
|
838
|
+
if (options && options.timeout) {
|
|
839
|
+
timeout = options.timeout;
|
|
840
|
+
}
|
|
841
|
+
while (true) {
|
|
842
|
+
try {
|
|
843
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
844
|
+
if (result?.elementNumber >= 0) {
|
|
845
|
+
const selectors = {
|
|
846
|
+
frame: result?.frame,
|
|
847
|
+
locators: [
|
|
848
|
+
{
|
|
849
|
+
css: result?.css,
|
|
850
|
+
},
|
|
851
|
+
],
|
|
852
|
+
};
|
|
853
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
catch (e) {
|
|
858
|
+
if (performance.now() - startTime > timeout) {
|
|
859
|
+
throw e;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
async click(selectors, _params, options = {}, world = null) {
|
|
866
|
+
const state = {
|
|
867
|
+
selectors,
|
|
868
|
+
_params,
|
|
869
|
+
options,
|
|
870
|
+
world,
|
|
871
|
+
text: "Click element",
|
|
872
|
+
type: Types.CLICK,
|
|
873
|
+
operation: "click",
|
|
874
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
875
|
+
};
|
|
611
876
|
try {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
877
|
+
await _preCommand(state, this);
|
|
878
|
+
if (state.options && state.options.context) {
|
|
879
|
+
state.selectors.locators[0].text = state.options.context;
|
|
880
|
+
}
|
|
615
881
|
try {
|
|
616
|
-
await
|
|
617
|
-
await element.click({ timeout: 5000 });
|
|
882
|
+
await state.element.click();
|
|
618
883
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
619
884
|
}
|
|
620
885
|
catch (e) {
|
|
621
886
|
// await this.closeUnexpectedPopups();
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
await element.click({ timeout: 10000, force: true });
|
|
887
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
888
|
+
await state.element.dispatchEvent("click");
|
|
625
889
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
626
890
|
}
|
|
627
891
|
await this.waitForPageLoad();
|
|
628
|
-
return info;
|
|
892
|
+
return state.info;
|
|
629
893
|
}
|
|
630
894
|
catch (e) {
|
|
631
|
-
|
|
632
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
633
|
-
info.screenshotPath = screenshotPath;
|
|
634
|
-
Object.assign(e, { info: info });
|
|
635
|
-
error = e;
|
|
636
|
-
throw e;
|
|
895
|
+
await _commandError(state, e, this);
|
|
637
896
|
}
|
|
638
897
|
finally {
|
|
639
|
-
|
|
640
|
-
this._reportToWorld(world, {
|
|
641
|
-
element_name: selectors.element_name,
|
|
642
|
-
type: Types.CLICK,
|
|
643
|
-
text: `Click element`,
|
|
644
|
-
screenshotId,
|
|
645
|
-
result: error
|
|
646
|
-
? {
|
|
647
|
-
status: "FAILED",
|
|
648
|
-
startTime,
|
|
649
|
-
endTime,
|
|
650
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
651
|
-
}
|
|
652
|
-
: {
|
|
653
|
-
status: "PASSED",
|
|
654
|
-
startTime,
|
|
655
|
-
endTime,
|
|
656
|
-
},
|
|
657
|
-
info: info,
|
|
658
|
-
});
|
|
898
|
+
_commandFinally(state, this);
|
|
659
899
|
}
|
|
660
900
|
}
|
|
661
901
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
902
|
+
const state = {
|
|
903
|
+
selectors,
|
|
904
|
+
_params,
|
|
905
|
+
options,
|
|
906
|
+
world,
|
|
907
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
908
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
909
|
+
operation: "setCheck",
|
|
910
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
911
|
+
};
|
|
672
912
|
try {
|
|
673
|
-
|
|
674
|
-
|
|
913
|
+
await _preCommand(state, this);
|
|
914
|
+
state.info.checked = checked;
|
|
915
|
+
// let element = await this._locate(selectors, info, _params);
|
|
916
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
675
917
|
try {
|
|
676
|
-
await this._highlightElements(element);
|
|
677
|
-
await element.setChecked(checked
|
|
918
|
+
// await this._highlightElements(element);
|
|
919
|
+
await state.element.setChecked(checked);
|
|
678
920
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
679
921
|
}
|
|
680
922
|
catch (e) {
|
|
@@ -683,179 +925,108 @@ class StableBrowser {
|
|
|
683
925
|
}
|
|
684
926
|
else {
|
|
685
927
|
//await this.closeUnexpectedPopups();
|
|
686
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
687
|
-
element = await this._locate(selectors, info, _params);
|
|
688
|
-
await element.setChecked(checked, { timeout: 5000, force: true });
|
|
928
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
929
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
930
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
689
931
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
690
932
|
}
|
|
691
933
|
}
|
|
692
934
|
await this.waitForPageLoad();
|
|
693
|
-
return info;
|
|
935
|
+
return state.info;
|
|
694
936
|
}
|
|
695
937
|
catch (e) {
|
|
696
|
-
|
|
697
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
698
|
-
info.screenshotPath = screenshotPath;
|
|
699
|
-
Object.assign(e, { info: info });
|
|
700
|
-
error = e;
|
|
701
|
-
throw e;
|
|
938
|
+
await _commandError(state, e, this);
|
|
702
939
|
}
|
|
703
940
|
finally {
|
|
704
|
-
|
|
705
|
-
this._reportToWorld(world, {
|
|
706
|
-
element_name: selectors.element_name,
|
|
707
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
708
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
709
|
-
screenshotId,
|
|
710
|
-
result: error
|
|
711
|
-
? {
|
|
712
|
-
status: "FAILED",
|
|
713
|
-
startTime,
|
|
714
|
-
endTime,
|
|
715
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
716
|
-
}
|
|
717
|
-
: {
|
|
718
|
-
status: "PASSED",
|
|
719
|
-
startTime,
|
|
720
|
-
endTime,
|
|
721
|
-
},
|
|
722
|
-
info: info,
|
|
723
|
-
});
|
|
941
|
+
_commandFinally(state, this);
|
|
724
942
|
}
|
|
725
943
|
}
|
|
726
944
|
async hover(selectors, _params, options = {}, world = null) {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
945
|
+
const state = {
|
|
946
|
+
selectors,
|
|
947
|
+
_params,
|
|
948
|
+
options,
|
|
949
|
+
world,
|
|
950
|
+
type: Types.HOVER,
|
|
951
|
+
text: `Hover element`,
|
|
952
|
+
operation: "hover",
|
|
953
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
954
|
+
};
|
|
736
955
|
try {
|
|
737
|
-
|
|
738
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
956
|
+
await _preCommand(state, this);
|
|
739
957
|
try {
|
|
740
|
-
await
|
|
741
|
-
await element.hover({ timeout: 10000 });
|
|
958
|
+
await state.element.hover();
|
|
742
959
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
743
960
|
}
|
|
744
961
|
catch (e) {
|
|
745
962
|
//await this.closeUnexpectedPopups();
|
|
746
|
-
info.log += "hover failed, will try again" + "\n";
|
|
747
|
-
element = await this._locate(selectors, info, _params);
|
|
748
|
-
await element.hover({ timeout: 10000 });
|
|
963
|
+
state.info.log += "hover failed, will try again" + "\n";
|
|
964
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
965
|
+
await state.element.hover({ timeout: 10000 });
|
|
749
966
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
750
967
|
}
|
|
751
968
|
await this.waitForPageLoad();
|
|
752
|
-
return info;
|
|
969
|
+
return state.info;
|
|
753
970
|
}
|
|
754
971
|
catch (e) {
|
|
755
|
-
|
|
756
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
757
|
-
info.screenshotPath = screenshotPath;
|
|
758
|
-
Object.assign(e, { info: info });
|
|
759
|
-
error = e;
|
|
760
|
-
throw e;
|
|
972
|
+
await _commandError(state, e, this);
|
|
761
973
|
}
|
|
762
974
|
finally {
|
|
763
|
-
|
|
764
|
-
this._reportToWorld(world, {
|
|
765
|
-
element_name: selectors.element_name,
|
|
766
|
-
type: Types.HOVER,
|
|
767
|
-
text: `Hover element`,
|
|
768
|
-
screenshotId,
|
|
769
|
-
result: error
|
|
770
|
-
? {
|
|
771
|
-
status: "FAILED",
|
|
772
|
-
startTime,
|
|
773
|
-
endTime,
|
|
774
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
775
|
-
}
|
|
776
|
-
: {
|
|
777
|
-
status: "PASSED",
|
|
778
|
-
startTime,
|
|
779
|
-
endTime,
|
|
780
|
-
},
|
|
781
|
-
info: info,
|
|
782
|
-
});
|
|
975
|
+
_commandFinally(state, this);
|
|
783
976
|
}
|
|
784
977
|
}
|
|
785
978
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
786
|
-
this._validateSelectors(selectors);
|
|
787
979
|
if (!values) {
|
|
788
980
|
throw new Error("values is null");
|
|
789
981
|
}
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
982
|
+
const state = {
|
|
983
|
+
selectors,
|
|
984
|
+
_params,
|
|
985
|
+
options,
|
|
986
|
+
world,
|
|
987
|
+
value: values.toString(),
|
|
988
|
+
type: Types.SELECT,
|
|
989
|
+
text: `Select option: ${values}`,
|
|
990
|
+
operation: "selectOption",
|
|
991
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
992
|
+
};
|
|
798
993
|
try {
|
|
799
|
-
|
|
800
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
994
|
+
await _preCommand(state, this);
|
|
801
995
|
try {
|
|
802
|
-
await
|
|
803
|
-
await element.selectOption(values, { timeout: 5000 });
|
|
996
|
+
await state.element.selectOption(values);
|
|
804
997
|
}
|
|
805
998
|
catch (e) {
|
|
806
999
|
//await this.closeUnexpectedPopups();
|
|
807
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
808
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1000
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1001
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
809
1002
|
}
|
|
810
1003
|
await this.waitForPageLoad();
|
|
811
|
-
return info;
|
|
1004
|
+
return state.info;
|
|
812
1005
|
}
|
|
813
1006
|
catch (e) {
|
|
814
|
-
|
|
815
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
816
|
-
info.screenshotPath = screenshotPath;
|
|
817
|
-
Object.assign(e, { info: info });
|
|
818
|
-
this.logger.info("click failed, will try next selector");
|
|
819
|
-
error = e;
|
|
820
|
-
throw e;
|
|
1007
|
+
await _commandError(state, e, this);
|
|
821
1008
|
}
|
|
822
1009
|
finally {
|
|
823
|
-
|
|
824
|
-
this._reportToWorld(world, {
|
|
825
|
-
element_name: selectors.element_name,
|
|
826
|
-
type: Types.SELECT,
|
|
827
|
-
text: `Select option: ${values}`,
|
|
828
|
-
value: values.toString(),
|
|
829
|
-
screenshotId,
|
|
830
|
-
result: error
|
|
831
|
-
? {
|
|
832
|
-
status: "FAILED",
|
|
833
|
-
startTime,
|
|
834
|
-
endTime,
|
|
835
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
836
|
-
}
|
|
837
|
-
: {
|
|
838
|
-
status: "PASSED",
|
|
839
|
-
startTime,
|
|
840
|
-
endTime,
|
|
841
|
-
},
|
|
842
|
-
info: info,
|
|
843
|
-
});
|
|
1010
|
+
_commandFinally(state, this);
|
|
844
1011
|
}
|
|
845
1012
|
}
|
|
846
1013
|
async type(_value, _params = null, options = {}, world = null) {
|
|
847
|
-
const
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1014
|
+
const state = {
|
|
1015
|
+
value: _value,
|
|
1016
|
+
_params,
|
|
1017
|
+
options,
|
|
1018
|
+
world,
|
|
1019
|
+
locate: false,
|
|
1020
|
+
scroll: false,
|
|
1021
|
+
highlight: false,
|
|
1022
|
+
type: Types.TYPE_PRESS,
|
|
1023
|
+
text: `Type value: ${_value}`,
|
|
1024
|
+
operation: "type",
|
|
1025
|
+
log: "",
|
|
1026
|
+
};
|
|
856
1027
|
try {
|
|
857
|
-
|
|
858
|
-
const valueSegment =
|
|
1028
|
+
await _preCommand(state, this);
|
|
1029
|
+
const valueSegment = state.value.split("&&");
|
|
859
1030
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
860
1031
|
if (i > 0) {
|
|
861
1032
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -875,134 +1046,53 @@ class StableBrowser {
|
|
|
875
1046
|
await this.page.keyboard.type(value);
|
|
876
1047
|
}
|
|
877
1048
|
}
|
|
878
|
-
return info;
|
|
1049
|
+
return state.info;
|
|
879
1050
|
}
|
|
880
1051
|
catch (e) {
|
|
881
|
-
|
|
882
|
-
this.logger.error("type failed " + info.log);
|
|
883
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
884
|
-
info.screenshotPath = screenshotPath;
|
|
885
|
-
Object.assign(e, { info: info });
|
|
886
|
-
error = e;
|
|
887
|
-
throw e;
|
|
1052
|
+
await _commandError(state, e, this);
|
|
888
1053
|
}
|
|
889
1054
|
finally {
|
|
890
|
-
|
|
891
|
-
this._reportToWorld(world, {
|
|
892
|
-
type: Types.TYPE_PRESS,
|
|
893
|
-
screenshotId,
|
|
894
|
-
value: _value,
|
|
895
|
-
text: `type value: ${_value}`,
|
|
896
|
-
result: error
|
|
897
|
-
? {
|
|
898
|
-
status: "FAILED",
|
|
899
|
-
startTime,
|
|
900
|
-
endTime,
|
|
901
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
902
|
-
}
|
|
903
|
-
: {
|
|
904
|
-
status: "PASSED",
|
|
905
|
-
startTime,
|
|
906
|
-
endTime,
|
|
907
|
-
},
|
|
908
|
-
info: info,
|
|
909
|
-
});
|
|
1055
|
+
_commandFinally(state, this);
|
|
910
1056
|
}
|
|
911
1057
|
}
|
|
912
|
-
async
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1058
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1059
|
+
const state = {
|
|
1060
|
+
selectors,
|
|
1061
|
+
_params,
|
|
1062
|
+
value,
|
|
1063
|
+
options,
|
|
1064
|
+
world,
|
|
1065
|
+
type: Types.SET_INPUT,
|
|
1066
|
+
text: `Set input value`,
|
|
1067
|
+
operation: "setInputValue",
|
|
1068
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1069
|
+
};
|
|
923
1070
|
try {
|
|
924
|
-
|
|
925
|
-
let
|
|
926
|
-
//insert red border around the element
|
|
927
|
-
await this.scrollIfNeeded(element, info);
|
|
928
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
929
|
-
await this._highlightElements(element);
|
|
1071
|
+
await _preCommand(state, this);
|
|
1072
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
930
1073
|
try {
|
|
931
|
-
await element.
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
value = dayjs(value).format(format);
|
|
935
|
-
await element.fill(value);
|
|
936
|
-
}
|
|
937
|
-
else {
|
|
938
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
939
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
940
|
-
el.value = ""; // clear input
|
|
941
|
-
el.value = dateTimeValue;
|
|
942
|
-
}, dateTimeValue);
|
|
943
|
-
}
|
|
944
|
-
if (enter) {
|
|
945
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
946
|
-
await this.page.keyboard.press("Enter");
|
|
947
|
-
await this.waitForPageLoad();
|
|
948
|
-
}
|
|
1074
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1075
|
+
el.value = value;
|
|
1076
|
+
}, value);
|
|
949
1077
|
}
|
|
950
1078
|
catch (error) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
958
|
-
if (format) {
|
|
959
|
-
value = dayjs(value).format(format);
|
|
960
|
-
await element.fill(value);
|
|
961
|
-
}
|
|
962
|
-
else {
|
|
963
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
964
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
965
|
-
el.value = ""; // clear input
|
|
966
|
-
el.value = dateTimeValue;
|
|
967
|
-
}, dateTimeValue);
|
|
968
|
-
}
|
|
969
|
-
if (enter) {
|
|
970
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
971
|
-
await this.page.keyboard.press("Enter");
|
|
972
|
-
await this.waitForPageLoad();
|
|
973
|
-
}
|
|
1079
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1080
|
+
await _screenshot(state, this);
|
|
1081
|
+
Object.assign(error, { info: state.info });
|
|
1082
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1083
|
+
el.value = value;
|
|
1084
|
+
});
|
|
974
1085
|
}
|
|
975
1086
|
}
|
|
976
|
-
catch (
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
const endTime = Date.now();
|
|
982
|
-
this._reportToWorld(world, {
|
|
983
|
-
element_name: selectors.element_name,
|
|
984
|
-
type: Types.SET_DATE_TIME,
|
|
985
|
-
screenshotId,
|
|
986
|
-
value: value,
|
|
987
|
-
text: `setDateTime input with value: ${value}`,
|
|
988
|
-
result: error
|
|
989
|
-
? {
|
|
990
|
-
status: "FAILED",
|
|
991
|
-
startTime,
|
|
992
|
-
endTime,
|
|
993
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
994
|
-
}
|
|
995
|
-
: {
|
|
996
|
-
status: "PASSED",
|
|
997
|
-
startTime,
|
|
998
|
-
endTime,
|
|
999
|
-
},
|
|
1000
|
-
info: info,
|
|
1001
|
-
});
|
|
1087
|
+
catch (e) {
|
|
1088
|
+
await _commandError(state, e, this);
|
|
1089
|
+
}
|
|
1090
|
+
finally {
|
|
1091
|
+
_commandFinally(state, this);
|
|
1002
1092
|
}
|
|
1003
1093
|
}
|
|
1004
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1005
|
-
|
|
1094
|
+
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1095
|
+
_validateSelectors(selectors);
|
|
1006
1096
|
const startTime = Date.now();
|
|
1007
1097
|
let error = null;
|
|
1008
1098
|
let screenshotId = null;
|
|
@@ -1013,6 +1103,7 @@ class StableBrowser {
|
|
|
1013
1103
|
info.selectors = selectors;
|
|
1014
1104
|
info.value = value;
|
|
1015
1105
|
try {
|
|
1106
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1016
1107
|
let element = await this._locate(selectors, info, _params);
|
|
1017
1108
|
//insert red border around the element
|
|
1018
1109
|
await this.scrollIfNeeded(element, info);
|
|
@@ -1021,28 +1112,51 @@ class StableBrowser {
|
|
|
1021
1112
|
try {
|
|
1022
1113
|
await element.click();
|
|
1023
1114
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1115
|
+
if (format) {
|
|
1116
|
+
value = dayjs(value).format(format);
|
|
1117
|
+
await element.fill(value);
|
|
1118
|
+
}
|
|
1119
|
+
else {
|
|
1120
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1121
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1122
|
+
el.value = ""; // clear input
|
|
1123
|
+
el.value = dateTimeValue;
|
|
1124
|
+
}, dateTimeValue);
|
|
1125
|
+
}
|
|
1126
|
+
if (enter) {
|
|
1127
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1128
|
+
await this.page.keyboard.press("Enter");
|
|
1129
|
+
await this.waitForPageLoad();
|
|
1130
|
+
}
|
|
1029
1131
|
}
|
|
1030
|
-
catch (
|
|
1132
|
+
catch (err) {
|
|
1031
1133
|
//await this.closeUnexpectedPopups();
|
|
1032
1134
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1033
|
-
this.logger.info("Trying again")
|
|
1135
|
+
this.logger.info("Trying again");
|
|
1136
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1034
1137
|
info.screenshotPath = screenshotPath;
|
|
1035
|
-
Object.assign(
|
|
1138
|
+
Object.assign(err, { info: info });
|
|
1036
1139
|
await element.click();
|
|
1037
1140
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1141
|
+
if (format) {
|
|
1142
|
+
value = dayjs(value).format(format);
|
|
1143
|
+
await element.fill(value);
|
|
1144
|
+
}
|
|
1145
|
+
else {
|
|
1146
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1147
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1148
|
+
el.value = ""; // clear input
|
|
1149
|
+
el.value = dateTimeValue;
|
|
1150
|
+
}, dateTimeValue);
|
|
1151
|
+
}
|
|
1152
|
+
if (enter) {
|
|
1153
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1154
|
+
await this.page.keyboard.press("Enter");
|
|
1155
|
+
await this.waitForPageLoad();
|
|
1156
|
+
}
|
|
1043
1157
|
}
|
|
1044
1158
|
}
|
|
1045
|
-
catch (
|
|
1159
|
+
catch (e) {
|
|
1046
1160
|
error = e;
|
|
1047
1161
|
throw e;
|
|
1048
1162
|
}
|
|
@@ -1071,32 +1185,31 @@ class StableBrowser {
|
|
|
1071
1185
|
}
|
|
1072
1186
|
}
|
|
1073
1187
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1188
|
+
const state = {
|
|
1189
|
+
selectors,
|
|
1190
|
+
_params,
|
|
1191
|
+
value: unEscapeString(_value),
|
|
1192
|
+
options,
|
|
1193
|
+
world,
|
|
1194
|
+
type: Types.FILL,
|
|
1195
|
+
text: `Click type input with value: ${_value}`,
|
|
1196
|
+
operation: "clickType",
|
|
1197
|
+
log: "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n",
|
|
1198
|
+
};
|
|
1199
|
+
const newValue = await this._replaceWithLocalData(state.value, world);
|
|
1084
1200
|
if (newValue !== _value) {
|
|
1085
1201
|
//this.logger.info(_value + "=" + newValue);
|
|
1086
1202
|
_value = newValue;
|
|
1203
|
+
state.value = newValue;
|
|
1087
1204
|
}
|
|
1088
|
-
info.value = _value;
|
|
1089
1205
|
try {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
await this.scrollIfNeeded(element, info);
|
|
1093
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1094
|
-
await this._highlightElements(element);
|
|
1206
|
+
await _preCommand(state, this);
|
|
1207
|
+
state.info.value = _value;
|
|
1095
1208
|
if (options === null || options === undefined || !options.press) {
|
|
1096
1209
|
try {
|
|
1097
|
-
let currentValue = await element.inputValue();
|
|
1210
|
+
let currentValue = await state.element.inputValue();
|
|
1098
1211
|
if (currentValue) {
|
|
1099
|
-
await element.fill("");
|
|
1212
|
+
await state.element.fill("");
|
|
1100
1213
|
}
|
|
1101
1214
|
}
|
|
1102
1215
|
catch (e) {
|
|
@@ -1105,22 +1218,22 @@ class StableBrowser {
|
|
|
1105
1218
|
}
|
|
1106
1219
|
if (options === null || options === undefined || options.press) {
|
|
1107
1220
|
try {
|
|
1108
|
-
await element.click({ timeout: 5000 });
|
|
1221
|
+
await state.element.click({ timeout: 5000 });
|
|
1109
1222
|
}
|
|
1110
1223
|
catch (e) {
|
|
1111
|
-
await element.dispatchEvent("click");
|
|
1224
|
+
await state.element.dispatchEvent("click");
|
|
1112
1225
|
}
|
|
1113
1226
|
}
|
|
1114
1227
|
else {
|
|
1115
1228
|
try {
|
|
1116
|
-
await element.focus();
|
|
1229
|
+
await state.element.focus();
|
|
1117
1230
|
}
|
|
1118
1231
|
catch (e) {
|
|
1119
|
-
await element.dispatchEvent("focus");
|
|
1232
|
+
await state.element.dispatchEvent("focus");
|
|
1120
1233
|
}
|
|
1121
1234
|
}
|
|
1122
1235
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1123
|
-
const valueSegment =
|
|
1236
|
+
const valueSegment = state.value.split("&&");
|
|
1124
1237
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1125
1238
|
if (i > 0) {
|
|
1126
1239
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1140,13 +1253,14 @@ class StableBrowser {
|
|
|
1140
1253
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1141
1254
|
}
|
|
1142
1255
|
}
|
|
1256
|
+
await _screenshot(state, this);
|
|
1143
1257
|
if (enter === true) {
|
|
1144
1258
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1145
1259
|
await this.page.keyboard.press("Enter");
|
|
1146
1260
|
await this.waitForPageLoad();
|
|
1147
1261
|
}
|
|
1148
1262
|
else if (enter === false) {
|
|
1149
|
-
await element.dispatchEvent("change");
|
|
1263
|
+
await state.element.dispatchEvent("change");
|
|
1150
1264
|
//await this.page.keyboard.press("Tab");
|
|
1151
1265
|
}
|
|
1152
1266
|
else {
|
|
@@ -1155,103 +1269,50 @@ class StableBrowser {
|
|
|
1155
1269
|
await this.waitForPageLoad();
|
|
1156
1270
|
}
|
|
1157
1271
|
}
|
|
1158
|
-
return info;
|
|
1272
|
+
return state.info;
|
|
1159
1273
|
}
|
|
1160
1274
|
catch (e) {
|
|
1161
|
-
|
|
1162
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1163
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1164
|
-
info.screenshotPath = screenshotPath;
|
|
1165
|
-
Object.assign(e, { info: info });
|
|
1166
|
-
error = e;
|
|
1167
|
-
throw e;
|
|
1275
|
+
await _commandError(state, e, this);
|
|
1168
1276
|
}
|
|
1169
1277
|
finally {
|
|
1170
|
-
|
|
1171
|
-
this._reportToWorld(world, {
|
|
1172
|
-
element_name: selectors.element_name,
|
|
1173
|
-
type: Types.FILL,
|
|
1174
|
-
screenshotId,
|
|
1175
|
-
value: _value,
|
|
1176
|
-
text: `clickType input with value: ${_value}`,
|
|
1177
|
-
result: error
|
|
1178
|
-
? {
|
|
1179
|
-
status: "FAILED",
|
|
1180
|
-
startTime,
|
|
1181
|
-
endTime,
|
|
1182
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1183
|
-
}
|
|
1184
|
-
: {
|
|
1185
|
-
status: "PASSED",
|
|
1186
|
-
startTime,
|
|
1187
|
-
endTime,
|
|
1188
|
-
},
|
|
1189
|
-
info: info,
|
|
1190
|
-
});
|
|
1278
|
+
_commandFinally(state, this);
|
|
1191
1279
|
}
|
|
1192
1280
|
}
|
|
1193
1281
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1282
|
+
const state = {
|
|
1283
|
+
selectors,
|
|
1284
|
+
_params,
|
|
1285
|
+
value: unEscapeString(value),
|
|
1286
|
+
options,
|
|
1287
|
+
world,
|
|
1288
|
+
type: Types.FILL,
|
|
1289
|
+
text: `Fill input with value: ${value}`,
|
|
1290
|
+
operation: "fill",
|
|
1291
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1292
|
+
};
|
|
1204
1293
|
try {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
await
|
|
1208
|
-
await element.fill(value, { timeout: 10000 });
|
|
1209
|
-
await element.dispatchEvent("change");
|
|
1294
|
+
await _preCommand(state, this);
|
|
1295
|
+
await state.element.fill(value);
|
|
1296
|
+
await state.element.dispatchEvent("change");
|
|
1210
1297
|
if (enter) {
|
|
1211
1298
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1212
1299
|
await this.page.keyboard.press("Enter");
|
|
1213
1300
|
}
|
|
1214
1301
|
await this.waitForPageLoad();
|
|
1215
|
-
return info;
|
|
1302
|
+
return state.info;
|
|
1216
1303
|
}
|
|
1217
1304
|
catch (e) {
|
|
1218
|
-
|
|
1219
|
-
this.logger.error("fill failed " + info.log);
|
|
1220
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1221
|
-
info.screenshotPath = screenshotPath;
|
|
1222
|
-
Object.assign(e, { info: info });
|
|
1223
|
-
error = e;
|
|
1224
|
-
throw e;
|
|
1305
|
+
await _commandError(state, e, this);
|
|
1225
1306
|
}
|
|
1226
1307
|
finally {
|
|
1227
|
-
|
|
1228
|
-
this._reportToWorld(world, {
|
|
1229
|
-
element_name: selectors.element_name,
|
|
1230
|
-
type: Types.FILL,
|
|
1231
|
-
screenshotId,
|
|
1232
|
-
value,
|
|
1233
|
-
text: `Fill input with value: ${value}`,
|
|
1234
|
-
result: error
|
|
1235
|
-
? {
|
|
1236
|
-
status: "FAILED",
|
|
1237
|
-
startTime,
|
|
1238
|
-
endTime,
|
|
1239
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1240
|
-
}
|
|
1241
|
-
: {
|
|
1242
|
-
status: "PASSED",
|
|
1243
|
-
startTime,
|
|
1244
|
-
endTime,
|
|
1245
|
-
},
|
|
1246
|
-
info: info,
|
|
1247
|
-
});
|
|
1308
|
+
_commandFinally(state, this);
|
|
1248
1309
|
}
|
|
1249
1310
|
}
|
|
1250
1311
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1251
1312
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1252
1313
|
}
|
|
1253
1314
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1254
|
-
|
|
1315
|
+
_validateSelectors(selectors);
|
|
1255
1316
|
let screenshotId = null;
|
|
1256
1317
|
let screenshotPath = null;
|
|
1257
1318
|
if (!info.log) {
|
|
@@ -1295,165 +1356,124 @@ class StableBrowser {
|
|
|
1295
1356
|
}
|
|
1296
1357
|
}
|
|
1297
1358
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1298
|
-
var _a;
|
|
1299
|
-
this._validateSelectors(selectors);
|
|
1300
1359
|
if (!pattern) {
|
|
1301
1360
|
throw new Error("pattern is null");
|
|
1302
1361
|
}
|
|
1303
1362
|
if (!text) {
|
|
1304
1363
|
throw new Error("text is null");
|
|
1305
1364
|
}
|
|
1365
|
+
const state = {
|
|
1366
|
+
selectors,
|
|
1367
|
+
_params,
|
|
1368
|
+
pattern,
|
|
1369
|
+
value: pattern,
|
|
1370
|
+
options,
|
|
1371
|
+
world,
|
|
1372
|
+
locate: false,
|
|
1373
|
+
scroll: false,
|
|
1374
|
+
screenshot: false,
|
|
1375
|
+
highlight: false,
|
|
1376
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1377
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1378
|
+
operation: "containsPattern",
|
|
1379
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1380
|
+
};
|
|
1306
1381
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1307
1382
|
if (newValue !== text) {
|
|
1308
1383
|
this.logger.info(text + "=" + newValue);
|
|
1309
1384
|
text = newValue;
|
|
1310
1385
|
}
|
|
1311
|
-
const startTime = Date.now();
|
|
1312
|
-
let error = null;
|
|
1313
|
-
let screenshotId = null;
|
|
1314
|
-
let screenshotPath = null;
|
|
1315
|
-
const info = {};
|
|
1316
|
-
info.log =
|
|
1317
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1318
|
-
info.operation = "containsPattern";
|
|
1319
|
-
info.selectors = selectors;
|
|
1320
|
-
info.value = text;
|
|
1321
|
-
info.pattern = pattern;
|
|
1322
1386
|
let foundObj = null;
|
|
1323
1387
|
try {
|
|
1324
|
-
|
|
1388
|
+
await _preCommand(state, this);
|
|
1389
|
+
state.info.pattern = pattern;
|
|
1390
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1325
1391
|
if (foundObj && foundObj.element) {
|
|
1326
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1392
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1327
1393
|
}
|
|
1328
|
-
|
|
1394
|
+
await _screenshot(state, this);
|
|
1329
1395
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1330
1396
|
pattern = pattern.replace("{text}", escapedText);
|
|
1331
1397
|
let regex = new RegExp(pattern, "im");
|
|
1332
|
-
if (!regex.test(foundObj
|
|
1333
|
-
info.foundText = foundObj
|
|
1398
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1399
|
+
state.info.foundText = foundObj?.text;
|
|
1334
1400
|
throw new Error("element doesn't contain text " + text);
|
|
1335
1401
|
}
|
|
1336
|
-
return info;
|
|
1402
|
+
return state.info;
|
|
1337
1403
|
}
|
|
1338
1404
|
catch (e) {
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1342
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1343
|
-
info.screenshotPath = screenshotPath;
|
|
1344
|
-
Object.assign(e, { info: info });
|
|
1345
|
-
error = e;
|
|
1346
|
-
throw e;
|
|
1405
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1406
|
+
await _commandError(state, e, this);
|
|
1347
1407
|
}
|
|
1348
1408
|
finally {
|
|
1349
|
-
|
|
1350
|
-
this._reportToWorld(world, {
|
|
1351
|
-
element_name: selectors.element_name,
|
|
1352
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1353
|
-
value: pattern,
|
|
1354
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1355
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1356
|
-
result: error
|
|
1357
|
-
? {
|
|
1358
|
-
status: "FAILED",
|
|
1359
|
-
startTime,
|
|
1360
|
-
endTime,
|
|
1361
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1362
|
-
}
|
|
1363
|
-
: {
|
|
1364
|
-
status: "PASSED",
|
|
1365
|
-
startTime,
|
|
1366
|
-
endTime,
|
|
1367
|
-
},
|
|
1368
|
-
info: info,
|
|
1369
|
-
});
|
|
1409
|
+
_commandFinally(state, this);
|
|
1370
1410
|
}
|
|
1371
1411
|
}
|
|
1372
1412
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1373
|
-
|
|
1374
|
-
|
|
1413
|
+
const state = {
|
|
1414
|
+
selectors,
|
|
1415
|
+
_params,
|
|
1416
|
+
value: text,
|
|
1417
|
+
options,
|
|
1418
|
+
world,
|
|
1419
|
+
locate: false,
|
|
1420
|
+
scroll: false,
|
|
1421
|
+
screenshot: false,
|
|
1422
|
+
highlight: false,
|
|
1423
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1424
|
+
text: `Verify element contains text: ${text}`,
|
|
1425
|
+
operation: "containsText",
|
|
1426
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1427
|
+
};
|
|
1375
1428
|
if (!text) {
|
|
1376
1429
|
throw new Error("text is null");
|
|
1377
1430
|
}
|
|
1378
|
-
|
|
1379
|
-
let error = null;
|
|
1380
|
-
let screenshotId = null;
|
|
1381
|
-
let screenshotPath = null;
|
|
1382
|
-
const info = {};
|
|
1383
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1384
|
-
info.operation = "containsText";
|
|
1385
|
-
info.selectors = selectors;
|
|
1431
|
+
text = unEscapeString(text);
|
|
1386
1432
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1387
1433
|
if (newValue !== text) {
|
|
1388
1434
|
this.logger.info(text + "=" + newValue);
|
|
1389
1435
|
text = newValue;
|
|
1390
1436
|
}
|
|
1391
|
-
info.value = text;
|
|
1392
1437
|
let foundObj = null;
|
|
1393
1438
|
try {
|
|
1394
|
-
|
|
1439
|
+
await _preCommand(state, this);
|
|
1440
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1395
1441
|
if (foundObj && foundObj.element) {
|
|
1396
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1442
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1397
1443
|
}
|
|
1398
|
-
|
|
1444
|
+
await _screenshot(state, this);
|
|
1399
1445
|
const dateAlternatives = findDateAlternatives(text);
|
|
1400
1446
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1401
1447
|
if (dateAlternatives.date) {
|
|
1402
1448
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1403
|
-
if (
|
|
1404
|
-
|
|
1405
|
-
return info;
|
|
1449
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1450
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1451
|
+
return state.info;
|
|
1406
1452
|
}
|
|
1407
1453
|
}
|
|
1408
1454
|
throw new Error("element doesn't contain text " + text);
|
|
1409
1455
|
}
|
|
1410
1456
|
else if (numberAlternatives.number) {
|
|
1411
1457
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1412
|
-
if (
|
|
1413
|
-
|
|
1414
|
-
return info;
|
|
1458
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1459
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1460
|
+
return state.info;
|
|
1415
1461
|
}
|
|
1416
1462
|
}
|
|
1417
1463
|
throw new Error("element doesn't contain text " + text);
|
|
1418
1464
|
}
|
|
1419
|
-
else if (!
|
|
1420
|
-
info.foundText = foundObj
|
|
1421
|
-
info.value = foundObj
|
|
1465
|
+
else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
|
|
1466
|
+
state.info.foundText = foundObj?.text;
|
|
1467
|
+
state.info.value = foundObj?.value;
|
|
1422
1468
|
throw new Error("element doesn't contain text " + text);
|
|
1423
1469
|
}
|
|
1424
|
-
return info;
|
|
1470
|
+
return state.info;
|
|
1425
1471
|
}
|
|
1426
1472
|
catch (e) {
|
|
1427
|
-
|
|
1428
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1429
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1430
|
-
info.screenshotPath = screenshotPath;
|
|
1431
|
-
Object.assign(e, { info: info });
|
|
1432
|
-
error = e;
|
|
1433
|
-
throw e;
|
|
1473
|
+
await _commandError(state, e, this);
|
|
1434
1474
|
}
|
|
1435
1475
|
finally {
|
|
1436
|
-
|
|
1437
|
-
this._reportToWorld(world, {
|
|
1438
|
-
element_name: selectors.element_name,
|
|
1439
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1440
|
-
text: `Verify element contains text: ${text}`,
|
|
1441
|
-
value: text,
|
|
1442
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1443
|
-
result: error
|
|
1444
|
-
? {
|
|
1445
|
-
status: "FAILED",
|
|
1446
|
-
startTime,
|
|
1447
|
-
endTime,
|
|
1448
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1449
|
-
}
|
|
1450
|
-
: {
|
|
1451
|
-
status: "PASSED",
|
|
1452
|
-
startTime,
|
|
1453
|
-
endTime,
|
|
1454
|
-
},
|
|
1455
|
-
info: info,
|
|
1456
|
-
});
|
|
1476
|
+
_commandFinally(state, this);
|
|
1457
1477
|
}
|
|
1458
1478
|
}
|
|
1459
1479
|
_getDataFile(world = null) {
|
|
@@ -1472,6 +1492,29 @@ class StableBrowser {
|
|
|
1472
1492
|
}
|
|
1473
1493
|
return dataFile;
|
|
1474
1494
|
}
|
|
1495
|
+
async waitForUserInput(message, world = null) {
|
|
1496
|
+
if (!message) {
|
|
1497
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1498
|
+
}
|
|
1499
|
+
else {
|
|
1500
|
+
message = "# Wait for user input. " + message;
|
|
1501
|
+
}
|
|
1502
|
+
message += "\n";
|
|
1503
|
+
const value = await new Promise((resolve) => {
|
|
1504
|
+
const rl = readline.createInterface({
|
|
1505
|
+
input: process.stdin,
|
|
1506
|
+
output: process.stdout,
|
|
1507
|
+
});
|
|
1508
|
+
rl.question(message, (answer) => {
|
|
1509
|
+
rl.close();
|
|
1510
|
+
resolve(answer);
|
|
1511
|
+
});
|
|
1512
|
+
});
|
|
1513
|
+
if (value) {
|
|
1514
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1515
|
+
}
|
|
1516
|
+
this.setTestData({ userInput: value }, world);
|
|
1517
|
+
}
|
|
1475
1518
|
setTestData(testData, world = null) {
|
|
1476
1519
|
if (!testData) {
|
|
1477
1520
|
return;
|
|
@@ -1499,7 +1542,7 @@ class StableBrowser {
|
|
|
1499
1542
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1500
1543
|
const results = [];
|
|
1501
1544
|
return new Promise((resolve, reject) => {
|
|
1502
|
-
const readableStream = new
|
|
1545
|
+
const readableStream = new Readable();
|
|
1503
1546
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1504
1547
|
readableStream.push(data);
|
|
1505
1548
|
readableStream.push(null);
|
|
@@ -1659,7 +1702,6 @@ class StableBrowser {
|
|
|
1659
1702
|
}
|
|
1660
1703
|
async takeScreenshot(screenshotPath) {
|
|
1661
1704
|
const playContext = this.context.playContext;
|
|
1662
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1663
1705
|
// Using CDP to capture the screenshot
|
|
1664
1706
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1665
1707
|
document.body.scrollWidth,
|
|
@@ -1669,97 +1711,67 @@ class StableBrowser {
|
|
|
1669
1711
|
document.body.clientWidth,
|
|
1670
1712
|
document.documentElement.clientWidth,
|
|
1671
1713
|
])));
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
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
|
-
await client.detach();
|
|
1714
|
+
let screenshotBuffer = null;
|
|
1715
|
+
if (this.context.browserName === "chromium") {
|
|
1716
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1717
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1718
|
+
format: "png",
|
|
1719
|
+
// clip: {
|
|
1720
|
+
// x: 0,
|
|
1721
|
+
// y: 0,
|
|
1722
|
+
// width: viewportWidth,
|
|
1723
|
+
// height: viewportHeight,
|
|
1724
|
+
// scale: 1,
|
|
1725
|
+
// },
|
|
1726
|
+
});
|
|
1727
|
+
await client.detach();
|
|
1728
|
+
if (!screenshotPath) {
|
|
1729
|
+
return data;
|
|
1730
|
+
}
|
|
1731
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1732
|
+
}
|
|
1733
|
+
else {
|
|
1734
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1735
|
+
}
|
|
1736
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1737
|
+
// Get the image dimensions
|
|
1738
|
+
const { width, height } = image.bitmap;
|
|
1739
|
+
const resizeRatio = viewportWidth / width;
|
|
1740
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1741
|
+
if (width > viewportWidth) {
|
|
1742
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1743
|
+
await image.write(screenshotPath);
|
|
1744
|
+
}
|
|
1745
|
+
else {
|
|
1746
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1747
|
+
}
|
|
1707
1748
|
}
|
|
1708
1749
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1750
|
+
const state = {
|
|
1751
|
+
selectors,
|
|
1752
|
+
_params,
|
|
1753
|
+
options,
|
|
1754
|
+
world,
|
|
1755
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1756
|
+
text: `Verify element exists in page`,
|
|
1757
|
+
operation: "verifyElementExistInPage",
|
|
1758
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1759
|
+
};
|
|
1714
1760
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1715
|
-
const info = {};
|
|
1716
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1717
|
-
info.operation = "verify";
|
|
1718
|
-
info.selectors = selectors;
|
|
1719
1761
|
try {
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
}
|
|
1724
|
-
await this._highlightElements(element);
|
|
1725
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1726
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1727
|
-
return info;
|
|
1762
|
+
await _preCommand(state, this);
|
|
1763
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1764
|
+
return state.info;
|
|
1728
1765
|
}
|
|
1729
1766
|
catch (e) {
|
|
1730
|
-
|
|
1731
|
-
this.logger.error("verify failed " + info.log);
|
|
1732
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1733
|
-
info.screenshotPath = screenshotPath;
|
|
1734
|
-
Object.assign(e, { info: info });
|
|
1735
|
-
error = e;
|
|
1736
|
-
throw e;
|
|
1767
|
+
await _commandError(state, e, this);
|
|
1737
1768
|
}
|
|
1738
1769
|
finally {
|
|
1739
|
-
|
|
1740
|
-
this._reportToWorld(world, {
|
|
1741
|
-
element_name: selectors.element_name,
|
|
1742
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1743
|
-
text: "Verify element exists in page",
|
|
1744
|
-
screenshotId,
|
|
1745
|
-
result: error
|
|
1746
|
-
? {
|
|
1747
|
-
status: "FAILED",
|
|
1748
|
-
startTime,
|
|
1749
|
-
endTime,
|
|
1750
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1751
|
-
}
|
|
1752
|
-
: {
|
|
1753
|
-
status: "PASSED",
|
|
1754
|
-
startTime,
|
|
1755
|
-
endTime,
|
|
1756
|
-
},
|
|
1757
|
-
info: info,
|
|
1758
|
-
});
|
|
1770
|
+
_commandFinally(state, this);
|
|
1759
1771
|
}
|
|
1760
1772
|
}
|
|
1761
1773
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1762
|
-
|
|
1774
|
+
_validateSelectors(selectors);
|
|
1763
1775
|
const startTime = Date.now();
|
|
1764
1776
|
let error = null;
|
|
1765
1777
|
let screenshotId = null;
|
|
@@ -1818,7 +1830,7 @@ class StableBrowser {
|
|
|
1818
1830
|
status: "FAILED",
|
|
1819
1831
|
startTime,
|
|
1820
1832
|
endTime,
|
|
1821
|
-
message: error
|
|
1833
|
+
message: error?.message,
|
|
1822
1834
|
}
|
|
1823
1835
|
: {
|
|
1824
1836
|
status: "PASSED",
|
|
@@ -2027,7 +2039,7 @@ class StableBrowser {
|
|
|
2027
2039
|
status: "FAILED",
|
|
2028
2040
|
startTime,
|
|
2029
2041
|
endTime,
|
|
2030
|
-
message: error
|
|
2042
|
+
message: error?.message,
|
|
2031
2043
|
}
|
|
2032
2044
|
: {
|
|
2033
2045
|
status: "PASSED",
|
|
@@ -2063,20 +2075,20 @@ class StableBrowser {
|
|
|
2063
2075
|
for (let i = 0; i < frames.length; i++) {
|
|
2064
2076
|
if (dateAlternatives.date) {
|
|
2065
2077
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2066
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
|
|
2078
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
|
|
2067
2079
|
result.frame = frames[i];
|
|
2068
2080
|
results.push(result);
|
|
2069
2081
|
}
|
|
2070
2082
|
}
|
|
2071
2083
|
else if (numberAlternatives.number) {
|
|
2072
2084
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2073
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
|
|
2085
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
|
|
2074
2086
|
result.frame = frames[i];
|
|
2075
2087
|
results.push(result);
|
|
2076
2088
|
}
|
|
2077
2089
|
}
|
|
2078
2090
|
else {
|
|
2079
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, {});
|
|
2091
|
+
const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
|
|
2080
2092
|
result.frame = frames[i];
|
|
2081
2093
|
results.push(result);
|
|
2082
2094
|
}
|
|
@@ -2125,7 +2137,7 @@ class StableBrowser {
|
|
|
2125
2137
|
status: "FAILED",
|
|
2126
2138
|
startTime,
|
|
2127
2139
|
endTime,
|
|
2128
|
-
message: error
|
|
2140
|
+
message: error?.message,
|
|
2129
2141
|
}
|
|
2130
2142
|
: {
|
|
2131
2143
|
status: "PASSED",
|
|
@@ -2209,7 +2221,7 @@ class StableBrowser {
|
|
|
2209
2221
|
status: "FAILED",
|
|
2210
2222
|
startTime,
|
|
2211
2223
|
endTime,
|
|
2212
|
-
message: error
|
|
2224
|
+
message: error?.message,
|
|
2213
2225
|
}
|
|
2214
2226
|
: {
|
|
2215
2227
|
status: "PASSED",
|
|
@@ -2241,7 +2253,7 @@ class StableBrowser {
|
|
|
2241
2253
|
this.logger.info("Table data verified");
|
|
2242
2254
|
}
|
|
2243
2255
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2244
|
-
|
|
2256
|
+
_validateSelectors(selectors);
|
|
2245
2257
|
const startTime = Date.now();
|
|
2246
2258
|
let error = null;
|
|
2247
2259
|
let screenshotId = null;
|
|
@@ -2277,7 +2289,7 @@ class StableBrowser {
|
|
|
2277
2289
|
status: "FAILED",
|
|
2278
2290
|
startTime,
|
|
2279
2291
|
endTime,
|
|
2280
|
-
message: error
|
|
2292
|
+
message: error?.message,
|
|
2281
2293
|
}
|
|
2282
2294
|
: {
|
|
2283
2295
|
status: "PASSED",
|
|
@@ -2289,7 +2301,7 @@ class StableBrowser {
|
|
|
2289
2301
|
}
|
|
2290
2302
|
}
|
|
2291
2303
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2292
|
-
|
|
2304
|
+
_validateSelectors(selectors);
|
|
2293
2305
|
if (!query) {
|
|
2294
2306
|
throw new Error("query is null");
|
|
2295
2307
|
}
|
|
@@ -2442,7 +2454,7 @@ class StableBrowser {
|
|
|
2442
2454
|
status: "FAILED",
|
|
2443
2455
|
startTime,
|
|
2444
2456
|
endTime,
|
|
2445
|
-
message: error
|
|
2457
|
+
message: error?.message,
|
|
2446
2458
|
}
|
|
2447
2459
|
: {
|
|
2448
2460
|
status: "PASSED",
|
|
@@ -2511,13 +2523,13 @@ class StableBrowser {
|
|
|
2511
2523
|
}
|
|
2512
2524
|
catch (e) {
|
|
2513
2525
|
if (e.label === "networkidle") {
|
|
2514
|
-
console.log("
|
|
2526
|
+
console.log("waited for the network to be idle timeout");
|
|
2515
2527
|
}
|
|
2516
2528
|
else if (e.label === "load") {
|
|
2517
|
-
console.log("
|
|
2529
|
+
console.log("waited for the load timeout");
|
|
2518
2530
|
}
|
|
2519
2531
|
else if (e.label === "domcontentloaded") {
|
|
2520
|
-
console.log("
|
|
2532
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2521
2533
|
}
|
|
2522
2534
|
console.log(".");
|
|
2523
2535
|
}
|
|
@@ -2534,7 +2546,7 @@ class StableBrowser {
|
|
|
2534
2546
|
status: "FAILED",
|
|
2535
2547
|
startTime,
|
|
2536
2548
|
endTime,
|
|
2537
|
-
message: error
|
|
2549
|
+
message: error?.message,
|
|
2538
2550
|
}
|
|
2539
2551
|
: {
|
|
2540
2552
|
status: "PASSED",
|
|
@@ -2552,13 +2564,6 @@ class StableBrowser {
|
|
|
2552
2564
|
const info = {};
|
|
2553
2565
|
try {
|
|
2554
2566
|
await this.page.close();
|
|
2555
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2556
|
-
this.context.pages.pop();
|
|
2557
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2558
|
-
this.context.page = this.page;
|
|
2559
|
-
let title = await this.page.title();
|
|
2560
|
-
console.log("Switched to page " + title);
|
|
2561
|
-
}
|
|
2562
2567
|
}
|
|
2563
2568
|
catch (e) {
|
|
2564
2569
|
console.log(".");
|
|
@@ -2576,7 +2581,7 @@ class StableBrowser {
|
|
|
2576
2581
|
status: "FAILED",
|
|
2577
2582
|
startTime,
|
|
2578
2583
|
endTime,
|
|
2579
|
-
message: error
|
|
2584
|
+
message: error?.message,
|
|
2580
2585
|
}
|
|
2581
2586
|
: {
|
|
2582
2587
|
status: "PASSED",
|
|
@@ -2618,7 +2623,7 @@ class StableBrowser {
|
|
|
2618
2623
|
status: "FAILED",
|
|
2619
2624
|
startTime,
|
|
2620
2625
|
endTime,
|
|
2621
|
-
message: error
|
|
2626
|
+
message: error?.message,
|
|
2622
2627
|
}
|
|
2623
2628
|
: {
|
|
2624
2629
|
status: "PASSED",
|
|
@@ -2654,7 +2659,7 @@ class StableBrowser {
|
|
|
2654
2659
|
status: "FAILED",
|
|
2655
2660
|
startTime,
|
|
2656
2661
|
endTime,
|
|
2657
|
-
message: error
|
|
2662
|
+
message: error?.message,
|
|
2658
2663
|
}
|
|
2659
2664
|
: {
|
|
2660
2665
|
status: "PASSED",
|
|
@@ -2667,33 +2672,18 @@ class StableBrowser {
|
|
|
2667
2672
|
}
|
|
2668
2673
|
async scrollIfNeeded(element, info) {
|
|
2669
2674
|
try {
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
if (rect &&
|
|
2673
|
-
rect.top >= 0 &&
|
|
2674
|
-
rect.left >= 0 &&
|
|
2675
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2676
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2677
|
-
return false;
|
|
2678
|
-
}
|
|
2679
|
-
else {
|
|
2680
|
-
node.scrollIntoView({
|
|
2681
|
-
behavior: "smooth",
|
|
2682
|
-
block: "center",
|
|
2683
|
-
inline: "center",
|
|
2684
|
-
});
|
|
2685
|
-
return true;
|
|
2686
|
-
}
|
|
2675
|
+
await element.scrollIntoViewIfNeeded({
|
|
2676
|
+
timeout: 2000,
|
|
2687
2677
|
});
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
}
|
|
2678
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2679
|
+
if (info) {
|
|
2680
|
+
info.box = await element.boundingBox({
|
|
2681
|
+
timeout: 1000,
|
|
2682
|
+
});
|
|
2693
2683
|
}
|
|
2694
2684
|
}
|
|
2695
2685
|
catch (e) {
|
|
2696
|
-
console.log("
|
|
2686
|
+
console.log("#-#");
|
|
2697
2687
|
}
|
|
2698
2688
|
}
|
|
2699
2689
|
_reportToWorld(world, properties) {
|
|
@@ -2854,5 +2844,10 @@ const KEYBOARD_EVENTS = [
|
|
|
2854
2844
|
"TVAntennaCable",
|
|
2855
2845
|
"TVAudioDescription",
|
|
2856
2846
|
];
|
|
2847
|
+
function unEscapeString(str) {
|
|
2848
|
+
const placeholder = "__NEWLINE__";
|
|
2849
|
+
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2850
|
+
return str;
|
|
2851
|
+
}
|
|
2857
2852
|
export { StableBrowser };
|
|
2858
2853
|
//# sourceMappingURL=stable_browser.js.map
|