automation_model 1.0.400-dev → 1.0.400-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 +111 -0
- package/lib/command_common.js.map +1 -0
- package/lib/environment.d.ts +3 -0
- package/lib/environment.js +5 -2
- package/lib/environment.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +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 +32 -17
- package/lib/stable_browser.js +704 -719
- 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 +9 -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,27 +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
|
-
|
|
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" });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
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;
|
|
200
338
|
}
|
|
201
339
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
202
|
-
|
|
340
|
+
if (css && css.locator) {
|
|
341
|
+
css = css.locator;
|
|
342
|
+
}
|
|
343
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*", false, false, _params);
|
|
203
344
|
if (result.elementCount === 0) {
|
|
204
345
|
return;
|
|
205
346
|
}
|
|
@@ -214,7 +355,7 @@ class StableBrowser {
|
|
|
214
355
|
}
|
|
215
356
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
216
357
|
//const stringifyText = JSON.stringify(text);
|
|
217
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
358
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
218
359
|
function isParent(parent, child) {
|
|
219
360
|
let currentNode = child.parentNode;
|
|
220
361
|
while (currentNode !== null) {
|
|
@@ -226,6 +367,15 @@ class StableBrowser {
|
|
|
226
367
|
return false;
|
|
227
368
|
}
|
|
228
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;
|
|
229
379
|
function collectAllShadowDomElements(element, result = []) {
|
|
230
380
|
// Check and add the element if it has a shadow root
|
|
231
381
|
if (element.shadowRoot) {
|
|
@@ -244,6 +394,10 @@ class StableBrowser {
|
|
|
244
394
|
if (!tag) {
|
|
245
395
|
tag = "*";
|
|
246
396
|
}
|
|
397
|
+
let regexpSearch = document.getRegex(text);
|
|
398
|
+
if (regexpSearch) {
|
|
399
|
+
regex = true;
|
|
400
|
+
}
|
|
247
401
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
248
402
|
let shadowHosts = [];
|
|
249
403
|
document.collectAllShadowDomElements(document, shadowHosts);
|
|
@@ -259,7 +413,9 @@ class StableBrowser {
|
|
|
259
413
|
let randomToken = null;
|
|
260
414
|
const foundElements = [];
|
|
261
415
|
if (regex) {
|
|
262
|
-
|
|
416
|
+
if (!regexpSearch) {
|
|
417
|
+
regexpSearch = new RegExp(text, "im");
|
|
418
|
+
}
|
|
263
419
|
for (let i = 0; i < elements.length; i++) {
|
|
264
420
|
const element = elements[i];
|
|
265
421
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -273,8 +429,8 @@ class StableBrowser {
|
|
|
273
429
|
for (let i = 0; i < elements.length; i++) {
|
|
274
430
|
const element = elements[i];
|
|
275
431
|
if (partial) {
|
|
276
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
277
|
-
(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()))) {
|
|
278
434
|
foundElements.push(element);
|
|
279
435
|
}
|
|
280
436
|
}
|
|
@@ -319,6 +475,12 @@ class StableBrowser {
|
|
|
319
475
|
}
|
|
320
476
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
321
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
|
+
}
|
|
322
484
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
323
485
|
let locator = null;
|
|
324
486
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
@@ -407,15 +569,20 @@ class StableBrowser {
|
|
|
407
569
|
if (result.foundElements.length > 0) {
|
|
408
570
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
409
571
|
await dialogCloseLocator.click();
|
|
572
|
+
// wait for the dialog to close
|
|
573
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
410
574
|
return { rerun: true };
|
|
411
575
|
}
|
|
412
576
|
}
|
|
413
577
|
}
|
|
414
578
|
return { rerun: false };
|
|
415
579
|
}
|
|
416
|
-
async _locate(selectors, info, _params, timeout
|
|
580
|
+
async _locate(selectors, info, _params, timeout) {
|
|
581
|
+
if (!timeout) {
|
|
582
|
+
timeout = 30000;
|
|
583
|
+
}
|
|
417
584
|
for (let i = 0; i < 3; i++) {
|
|
418
|
-
info.log += "attempt " + i + ":
|
|
585
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
419
586
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
420
587
|
let selector = selectors.locators[j];
|
|
421
588
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -427,17 +594,44 @@ class StableBrowser {
|
|
|
427
594
|
}
|
|
428
595
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
429
596
|
}
|
|
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);
|
|
597
|
+
async _findFrameScope(selectors, timeout = 30000) {
|
|
436
598
|
let scope = this.page;
|
|
599
|
+
if (selectors.frame) {
|
|
600
|
+
return selectors.frame;
|
|
601
|
+
}
|
|
437
602
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
438
|
-
|
|
603
|
+
const findFrame = async (frame, framescope) => {
|
|
604
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
605
|
+
let frameLocator = frame.selectors[i];
|
|
606
|
+
if (frameLocator.css) {
|
|
607
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
608
|
+
if (frameLocator.index) {
|
|
609
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
610
|
+
}
|
|
611
|
+
try {
|
|
612
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
613
|
+
timeout: 5000,
|
|
614
|
+
});
|
|
615
|
+
framescope = testframescope;
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
catch (error) {
|
|
619
|
+
console.error("frame not found " + frameLocator.css);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
if (frame.children) {
|
|
624
|
+
return await findFrame(frame.children, framescope);
|
|
625
|
+
}
|
|
626
|
+
return framescope;
|
|
627
|
+
};
|
|
439
628
|
while (true) {
|
|
440
629
|
let frameFound = false;
|
|
630
|
+
if (selectors.nestFrmLoc) {
|
|
631
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
632
|
+
frameFound = true;
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
441
635
|
if (selectors.frameLocators) {
|
|
442
636
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
443
637
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -463,6 +657,25 @@ class StableBrowser {
|
|
|
463
657
|
}
|
|
464
658
|
}
|
|
465
659
|
}
|
|
660
|
+
if (!scope) {
|
|
661
|
+
scope = this.page;
|
|
662
|
+
}
|
|
663
|
+
return scope;
|
|
664
|
+
}
|
|
665
|
+
async _getDocumentBody(selectors, timeout = 30000) {
|
|
666
|
+
let scope = await this._findFrameScope(selectors, timeout);
|
|
667
|
+
return scope.evaluate(() => {
|
|
668
|
+
var bodyContent = document.body.innerHTML;
|
|
669
|
+
return bodyContent;
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
673
|
+
let highPriorityTimeout = 5000;
|
|
674
|
+
let visibleOnlyTimeout = 6000;
|
|
675
|
+
let startTime = performance.now();
|
|
676
|
+
let locatorsCount = 0;
|
|
677
|
+
//let arrayMode = Array.isArray(selectors);
|
|
678
|
+
let scope = await this._findFrameScope(selectors, timeout);
|
|
466
679
|
let selectorsLocators = null;
|
|
467
680
|
selectorsLocators = selectors.locators;
|
|
468
681
|
// group selectors by priority
|
|
@@ -598,83 +811,121 @@ class StableBrowser {
|
|
|
598
811
|
}
|
|
599
812
|
return result;
|
|
600
813
|
}
|
|
601
|
-
async
|
|
602
|
-
this._validateSelectors(selectors);
|
|
814
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
603
815
|
const startTime = Date.now();
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
816
|
+
let timeout = 30000;
|
|
817
|
+
if (options && options.timeout) {
|
|
818
|
+
timeout = options.timeout;
|
|
819
|
+
}
|
|
820
|
+
while (true) {
|
|
821
|
+
try {
|
|
822
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
823
|
+
if (result?.elementNumber >= 0) {
|
|
824
|
+
const selectors = {
|
|
825
|
+
frame: result?.frame,
|
|
826
|
+
locators: [
|
|
827
|
+
{
|
|
828
|
+
css: result?.css,
|
|
829
|
+
},
|
|
830
|
+
],
|
|
831
|
+
};
|
|
832
|
+
await this.click(selectors, _params, options, world);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
catch (e) {
|
|
837
|
+
if (performance.now() - startTime > timeout) {
|
|
838
|
+
throw e;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
845
|
+
const startTime = Date.now();
|
|
846
|
+
let timeout = 30000;
|
|
847
|
+
if (options && options.timeout) {
|
|
848
|
+
timeout = options.timeout;
|
|
849
|
+
}
|
|
850
|
+
while (true) {
|
|
851
|
+
try {
|
|
852
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
853
|
+
if (result?.elementNumber >= 0) {
|
|
854
|
+
const selectors = {
|
|
855
|
+
frame: result?.frame,
|
|
856
|
+
locators: [
|
|
857
|
+
{
|
|
858
|
+
css: result?.css,
|
|
859
|
+
},
|
|
860
|
+
],
|
|
861
|
+
};
|
|
862
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
catch (e) {
|
|
867
|
+
if (performance.now() - startTime > timeout) {
|
|
868
|
+
throw e;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
async click(selectors, _params, options = {}, world = null) {
|
|
875
|
+
const state = {
|
|
876
|
+
selectors,
|
|
877
|
+
_params,
|
|
878
|
+
options,
|
|
879
|
+
world,
|
|
880
|
+
text: "Click element",
|
|
881
|
+
type: Types.CLICK,
|
|
882
|
+
operation: "click",
|
|
883
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
884
|
+
};
|
|
611
885
|
try {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
886
|
+
await _preCommand(state, this);
|
|
887
|
+
if (state.options && state.options.context) {
|
|
888
|
+
state.selectors.locators[0].text = state.options.context;
|
|
889
|
+
}
|
|
615
890
|
try {
|
|
616
|
-
await
|
|
617
|
-
await element.click({ timeout: 5000 });
|
|
891
|
+
await state.element.click();
|
|
618
892
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
619
893
|
}
|
|
620
894
|
catch (e) {
|
|
621
895
|
// await this.closeUnexpectedPopups();
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
await element.click({ timeout: 10000, force: true });
|
|
896
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
897
|
+
await state.element.dispatchEvent("click");
|
|
625
898
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
626
899
|
}
|
|
627
900
|
await this.waitForPageLoad();
|
|
628
|
-
return info;
|
|
901
|
+
return state.info;
|
|
629
902
|
}
|
|
630
903
|
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;
|
|
904
|
+
await _commandError(state, e, this);
|
|
637
905
|
}
|
|
638
906
|
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
|
-
});
|
|
907
|
+
_commandFinally(state, this);
|
|
659
908
|
}
|
|
660
909
|
}
|
|
661
910
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
911
|
+
const state = {
|
|
912
|
+
selectors,
|
|
913
|
+
_params,
|
|
914
|
+
options,
|
|
915
|
+
world,
|
|
916
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
917
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
918
|
+
operation: "setCheck",
|
|
919
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
920
|
+
};
|
|
672
921
|
try {
|
|
673
|
-
|
|
674
|
-
|
|
922
|
+
await _preCommand(state, this);
|
|
923
|
+
state.info.checked = checked;
|
|
924
|
+
// let element = await this._locate(selectors, info, _params);
|
|
925
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
675
926
|
try {
|
|
676
|
-
await this._highlightElements(element);
|
|
677
|
-
await element.setChecked(checked
|
|
927
|
+
// await this._highlightElements(element);
|
|
928
|
+
await state.element.setChecked(checked);
|
|
678
929
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
679
930
|
}
|
|
680
931
|
catch (e) {
|
|
@@ -683,179 +934,108 @@ class StableBrowser {
|
|
|
683
934
|
}
|
|
684
935
|
else {
|
|
685
936
|
//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 });
|
|
937
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
938
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
939
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
689
940
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
690
941
|
}
|
|
691
942
|
}
|
|
692
943
|
await this.waitForPageLoad();
|
|
693
|
-
return info;
|
|
944
|
+
return state.info;
|
|
694
945
|
}
|
|
695
946
|
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;
|
|
947
|
+
await _commandError(state, e, this);
|
|
702
948
|
}
|
|
703
949
|
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
|
-
});
|
|
950
|
+
_commandFinally(state, this);
|
|
724
951
|
}
|
|
725
952
|
}
|
|
726
953
|
async hover(selectors, _params, options = {}, world = null) {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
954
|
+
const state = {
|
|
955
|
+
selectors,
|
|
956
|
+
_params,
|
|
957
|
+
options,
|
|
958
|
+
world,
|
|
959
|
+
type: Types.HOVER,
|
|
960
|
+
text: `Hover element`,
|
|
961
|
+
operation: "hover",
|
|
962
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
963
|
+
};
|
|
736
964
|
try {
|
|
737
|
-
|
|
738
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
965
|
+
await _preCommand(state, this);
|
|
739
966
|
try {
|
|
740
|
-
await
|
|
741
|
-
await element.hover({ timeout: 10000 });
|
|
967
|
+
await state.element.hover();
|
|
742
968
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
743
969
|
}
|
|
744
970
|
catch (e) {
|
|
745
971
|
//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 });
|
|
972
|
+
state.info.log += "hover failed, will try again" + "\n";
|
|
973
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
974
|
+
await state.element.hover({ timeout: 10000 });
|
|
749
975
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
750
976
|
}
|
|
751
977
|
await this.waitForPageLoad();
|
|
752
|
-
return info;
|
|
978
|
+
return state.info;
|
|
753
979
|
}
|
|
754
980
|
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;
|
|
981
|
+
await _commandError(state, e, this);
|
|
761
982
|
}
|
|
762
983
|
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
|
-
});
|
|
984
|
+
_commandFinally(state, this);
|
|
783
985
|
}
|
|
784
986
|
}
|
|
785
987
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
786
|
-
this._validateSelectors(selectors);
|
|
787
988
|
if (!values) {
|
|
788
989
|
throw new Error("values is null");
|
|
789
990
|
}
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
991
|
+
const state = {
|
|
992
|
+
selectors,
|
|
993
|
+
_params,
|
|
994
|
+
options,
|
|
995
|
+
world,
|
|
996
|
+
value: values.toString(),
|
|
997
|
+
type: Types.SELECT,
|
|
998
|
+
text: `Select option: ${values}`,
|
|
999
|
+
operation: "selectOption",
|
|
1000
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1001
|
+
};
|
|
798
1002
|
try {
|
|
799
|
-
|
|
800
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1003
|
+
await _preCommand(state, this);
|
|
801
1004
|
try {
|
|
802
|
-
await
|
|
803
|
-
await element.selectOption(values, { timeout: 5000 });
|
|
1005
|
+
await state.element.selectOption(values);
|
|
804
1006
|
}
|
|
805
1007
|
catch (e) {
|
|
806
1008
|
//await this.closeUnexpectedPopups();
|
|
807
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
808
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1009
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1010
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
809
1011
|
}
|
|
810
1012
|
await this.waitForPageLoad();
|
|
811
|
-
return info;
|
|
1013
|
+
return state.info;
|
|
812
1014
|
}
|
|
813
1015
|
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;
|
|
1016
|
+
await _commandError(state, e, this);
|
|
821
1017
|
}
|
|
822
1018
|
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
|
-
});
|
|
1019
|
+
_commandFinally(state, this);
|
|
844
1020
|
}
|
|
845
1021
|
}
|
|
846
1022
|
async type(_value, _params = null, options = {}, world = null) {
|
|
847
|
-
const
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
1023
|
+
const state = {
|
|
1024
|
+
value: _value,
|
|
1025
|
+
_params,
|
|
1026
|
+
options,
|
|
1027
|
+
world,
|
|
1028
|
+
locate: false,
|
|
1029
|
+
scroll: false,
|
|
1030
|
+
highlight: false,
|
|
1031
|
+
type: Types.TYPE_PRESS,
|
|
1032
|
+
text: `Type value: ${_value}`,
|
|
1033
|
+
operation: "type",
|
|
1034
|
+
log: "",
|
|
1035
|
+
};
|
|
856
1036
|
try {
|
|
857
|
-
|
|
858
|
-
const valueSegment =
|
|
1037
|
+
await _preCommand(state, this);
|
|
1038
|
+
const valueSegment = state.value.split("&&");
|
|
859
1039
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
860
1040
|
if (i > 0) {
|
|
861
1041
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -875,134 +1055,53 @@ class StableBrowser {
|
|
|
875
1055
|
await this.page.keyboard.type(value);
|
|
876
1056
|
}
|
|
877
1057
|
}
|
|
878
|
-
return info;
|
|
1058
|
+
return state.info;
|
|
879
1059
|
}
|
|
880
1060
|
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;
|
|
1061
|
+
await _commandError(state, e, this);
|
|
888
1062
|
}
|
|
889
1063
|
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
|
-
});
|
|
1064
|
+
_commandFinally(state, this);
|
|
910
1065
|
}
|
|
911
1066
|
}
|
|
912
|
-
async
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1067
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1068
|
+
const state = {
|
|
1069
|
+
selectors,
|
|
1070
|
+
_params,
|
|
1071
|
+
value,
|
|
1072
|
+
options,
|
|
1073
|
+
world,
|
|
1074
|
+
type: Types.SET_INPUT,
|
|
1075
|
+
text: `Set input value`,
|
|
1076
|
+
operation: "setInputValue",
|
|
1077
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1078
|
+
};
|
|
923
1079
|
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);
|
|
1080
|
+
await _preCommand(state, this);
|
|
1081
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
930
1082
|
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
|
-
}
|
|
1083
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1084
|
+
el.value = value;
|
|
1085
|
+
}, value);
|
|
949
1086
|
}
|
|
950
1087
|
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
|
-
}
|
|
1088
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1089
|
+
await _screenshot(state, this);
|
|
1090
|
+
Object.assign(error, { info: state.info });
|
|
1091
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1092
|
+
el.value = value;
|
|
1093
|
+
});
|
|
974
1094
|
}
|
|
975
1095
|
}
|
|
976
|
-
catch (
|
|
977
|
-
|
|
978
|
-
throw e;
|
|
1096
|
+
catch (e) {
|
|
1097
|
+
await _commandError(state, e, this);
|
|
979
1098
|
}
|
|
980
1099
|
finally {
|
|
981
|
-
|
|
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
|
-
});
|
|
1100
|
+
_commandFinally(state, this);
|
|
1002
1101
|
}
|
|
1003
1102
|
}
|
|
1004
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1005
|
-
|
|
1103
|
+
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1104
|
+
_validateSelectors(selectors);
|
|
1006
1105
|
const startTime = Date.now();
|
|
1007
1106
|
let error = null;
|
|
1008
1107
|
let screenshotId = null;
|
|
@@ -1013,6 +1112,7 @@ class StableBrowser {
|
|
|
1013
1112
|
info.selectors = selectors;
|
|
1014
1113
|
info.value = value;
|
|
1015
1114
|
try {
|
|
1115
|
+
value = await this._replaceWithLocalData(value, this);
|
|
1016
1116
|
let element = await this._locate(selectors, info, _params);
|
|
1017
1117
|
//insert red border around the element
|
|
1018
1118
|
await this.scrollIfNeeded(element, info);
|
|
@@ -1021,28 +1121,51 @@ class StableBrowser {
|
|
|
1021
1121
|
try {
|
|
1022
1122
|
await element.click();
|
|
1023
1123
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1124
|
+
if (format) {
|
|
1125
|
+
value = dayjs(value).format(format);
|
|
1126
|
+
await element.fill(value);
|
|
1127
|
+
}
|
|
1128
|
+
else {
|
|
1129
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1130
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1131
|
+
el.value = ""; // clear input
|
|
1132
|
+
el.value = dateTimeValue;
|
|
1133
|
+
}, dateTimeValue);
|
|
1134
|
+
}
|
|
1135
|
+
if (enter) {
|
|
1136
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1137
|
+
await this.page.keyboard.press("Enter");
|
|
1138
|
+
await this.waitForPageLoad();
|
|
1139
|
+
}
|
|
1029
1140
|
}
|
|
1030
|
-
catch (
|
|
1141
|
+
catch (err) {
|
|
1031
1142
|
//await this.closeUnexpectedPopups();
|
|
1032
1143
|
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1033
|
-
this.logger.info("Trying again")
|
|
1144
|
+
this.logger.info("Trying again");
|
|
1145
|
+
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1034
1146
|
info.screenshotPath = screenshotPath;
|
|
1035
|
-
Object.assign(
|
|
1147
|
+
Object.assign(err, { info: info });
|
|
1036
1148
|
await element.click();
|
|
1037
1149
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1150
|
+
if (format) {
|
|
1151
|
+
value = dayjs(value).format(format);
|
|
1152
|
+
await element.fill(value);
|
|
1153
|
+
}
|
|
1154
|
+
else {
|
|
1155
|
+
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1156
|
+
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1157
|
+
el.value = ""; // clear input
|
|
1158
|
+
el.value = dateTimeValue;
|
|
1159
|
+
}, dateTimeValue);
|
|
1160
|
+
}
|
|
1161
|
+
if (enter) {
|
|
1162
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1163
|
+
await this.page.keyboard.press("Enter");
|
|
1164
|
+
await this.waitForPageLoad();
|
|
1165
|
+
}
|
|
1043
1166
|
}
|
|
1044
1167
|
}
|
|
1045
|
-
catch (
|
|
1168
|
+
catch (e) {
|
|
1046
1169
|
error = e;
|
|
1047
1170
|
throw e;
|
|
1048
1171
|
}
|
|
@@ -1071,32 +1194,32 @@ class StableBrowser {
|
|
|
1071
1194
|
}
|
|
1072
1195
|
}
|
|
1073
1196
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1074
|
-
|
|
1075
|
-
const startTime = Date.now();
|
|
1076
|
-
let error = null;
|
|
1077
|
-
let screenshotId = null;
|
|
1078
|
-
let screenshotPath = null;
|
|
1079
|
-
const info = {};
|
|
1080
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1081
|
-
info.operation = "clickType";
|
|
1082
|
-
info.selectors = selectors;
|
|
1197
|
+
_value = unEscapeString(_value);
|
|
1083
1198
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1199
|
+
const state = {
|
|
1200
|
+
selectors,
|
|
1201
|
+
_params,
|
|
1202
|
+
value: newValue,
|
|
1203
|
+
originalValue: _value,
|
|
1204
|
+
options,
|
|
1205
|
+
world,
|
|
1206
|
+
type: Types.FILL,
|
|
1207
|
+
text: `Click type input with value: ${_value}`,
|
|
1208
|
+
operation: "clickType",
|
|
1209
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1210
|
+
};
|
|
1084
1211
|
if (newValue !== _value) {
|
|
1085
1212
|
//this.logger.info(_value + "=" + newValue);
|
|
1086
1213
|
_value = newValue;
|
|
1087
1214
|
}
|
|
1088
|
-
info.value = _value;
|
|
1089
1215
|
try {
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
await this.scrollIfNeeded(element, info);
|
|
1093
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1094
|
-
await this._highlightElements(element);
|
|
1216
|
+
await _preCommand(state, this);
|
|
1217
|
+
state.info.value = _value;
|
|
1095
1218
|
if (options === null || options === undefined || !options.press) {
|
|
1096
1219
|
try {
|
|
1097
|
-
let currentValue = await element.inputValue();
|
|
1220
|
+
let currentValue = await state.element.inputValue();
|
|
1098
1221
|
if (currentValue) {
|
|
1099
|
-
await element.fill("");
|
|
1222
|
+
await state.element.fill("");
|
|
1100
1223
|
}
|
|
1101
1224
|
}
|
|
1102
1225
|
catch (e) {
|
|
@@ -1105,22 +1228,22 @@ class StableBrowser {
|
|
|
1105
1228
|
}
|
|
1106
1229
|
if (options === null || options === undefined || options.press) {
|
|
1107
1230
|
try {
|
|
1108
|
-
await element.click({ timeout: 5000 });
|
|
1231
|
+
await state.element.click({ timeout: 5000 });
|
|
1109
1232
|
}
|
|
1110
1233
|
catch (e) {
|
|
1111
|
-
await element.dispatchEvent("click");
|
|
1234
|
+
await state.element.dispatchEvent("click");
|
|
1112
1235
|
}
|
|
1113
1236
|
}
|
|
1114
1237
|
else {
|
|
1115
1238
|
try {
|
|
1116
|
-
await element.focus();
|
|
1239
|
+
await state.element.focus();
|
|
1117
1240
|
}
|
|
1118
1241
|
catch (e) {
|
|
1119
|
-
await element.dispatchEvent("focus");
|
|
1242
|
+
await state.element.dispatchEvent("focus");
|
|
1120
1243
|
}
|
|
1121
1244
|
}
|
|
1122
1245
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1123
|
-
const valueSegment =
|
|
1246
|
+
const valueSegment = state.value.split("&&");
|
|
1124
1247
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1125
1248
|
if (i > 0) {
|
|
1126
1249
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1140,13 +1263,14 @@ class StableBrowser {
|
|
|
1140
1263
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1141
1264
|
}
|
|
1142
1265
|
}
|
|
1266
|
+
await _screenshot(state, this);
|
|
1143
1267
|
if (enter === true) {
|
|
1144
1268
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1145
1269
|
await this.page.keyboard.press("Enter");
|
|
1146
1270
|
await this.waitForPageLoad();
|
|
1147
1271
|
}
|
|
1148
1272
|
else if (enter === false) {
|
|
1149
|
-
await element.dispatchEvent("change");
|
|
1273
|
+
await state.element.dispatchEvent("change");
|
|
1150
1274
|
//await this.page.keyboard.press("Tab");
|
|
1151
1275
|
}
|
|
1152
1276
|
else {
|
|
@@ -1155,103 +1279,50 @@ class StableBrowser {
|
|
|
1155
1279
|
await this.waitForPageLoad();
|
|
1156
1280
|
}
|
|
1157
1281
|
}
|
|
1158
|
-
return info;
|
|
1282
|
+
return state.info;
|
|
1159
1283
|
}
|
|
1160
1284
|
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;
|
|
1285
|
+
await _commandError(state, e, this);
|
|
1168
1286
|
}
|
|
1169
1287
|
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
|
-
});
|
|
1288
|
+
_commandFinally(state, this);
|
|
1191
1289
|
}
|
|
1192
1290
|
}
|
|
1193
1291
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1292
|
+
const state = {
|
|
1293
|
+
selectors,
|
|
1294
|
+
_params,
|
|
1295
|
+
value: unEscapeString(value),
|
|
1296
|
+
options,
|
|
1297
|
+
world,
|
|
1298
|
+
type: Types.FILL,
|
|
1299
|
+
text: `Fill input with value: ${value}`,
|
|
1300
|
+
operation: "fill",
|
|
1301
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1302
|
+
};
|
|
1204
1303
|
try {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
await
|
|
1208
|
-
await element.fill(value, { timeout: 10000 });
|
|
1209
|
-
await element.dispatchEvent("change");
|
|
1304
|
+
await _preCommand(state, this);
|
|
1305
|
+
await state.element.fill(value);
|
|
1306
|
+
await state.element.dispatchEvent("change");
|
|
1210
1307
|
if (enter) {
|
|
1211
1308
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1212
1309
|
await this.page.keyboard.press("Enter");
|
|
1213
1310
|
}
|
|
1214
1311
|
await this.waitForPageLoad();
|
|
1215
|
-
return info;
|
|
1312
|
+
return state.info;
|
|
1216
1313
|
}
|
|
1217
1314
|
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;
|
|
1315
|
+
await _commandError(state, e, this);
|
|
1225
1316
|
}
|
|
1226
1317
|
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
|
-
});
|
|
1318
|
+
_commandFinally(state, this);
|
|
1248
1319
|
}
|
|
1249
1320
|
}
|
|
1250
1321
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1251
1322
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1252
1323
|
}
|
|
1253
1324
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1254
|
-
|
|
1325
|
+
_validateSelectors(selectors);
|
|
1255
1326
|
let screenshotId = null;
|
|
1256
1327
|
let screenshotPath = null;
|
|
1257
1328
|
if (!info.log) {
|
|
@@ -1295,165 +1366,124 @@ class StableBrowser {
|
|
|
1295
1366
|
}
|
|
1296
1367
|
}
|
|
1297
1368
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1298
|
-
var _a;
|
|
1299
|
-
this._validateSelectors(selectors);
|
|
1300
1369
|
if (!pattern) {
|
|
1301
1370
|
throw new Error("pattern is null");
|
|
1302
1371
|
}
|
|
1303
1372
|
if (!text) {
|
|
1304
1373
|
throw new Error("text is null");
|
|
1305
1374
|
}
|
|
1375
|
+
const state = {
|
|
1376
|
+
selectors,
|
|
1377
|
+
_params,
|
|
1378
|
+
pattern,
|
|
1379
|
+
value: pattern,
|
|
1380
|
+
options,
|
|
1381
|
+
world,
|
|
1382
|
+
locate: false,
|
|
1383
|
+
scroll: false,
|
|
1384
|
+
screenshot: false,
|
|
1385
|
+
highlight: false,
|
|
1386
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1387
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1388
|
+
operation: "containsPattern",
|
|
1389
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1390
|
+
};
|
|
1306
1391
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1307
1392
|
if (newValue !== text) {
|
|
1308
1393
|
this.logger.info(text + "=" + newValue);
|
|
1309
1394
|
text = newValue;
|
|
1310
1395
|
}
|
|
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
1396
|
let foundObj = null;
|
|
1323
1397
|
try {
|
|
1324
|
-
|
|
1398
|
+
await _preCommand(state, this);
|
|
1399
|
+
state.info.pattern = pattern;
|
|
1400
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1325
1401
|
if (foundObj && foundObj.element) {
|
|
1326
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1402
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1327
1403
|
}
|
|
1328
|
-
|
|
1404
|
+
await _screenshot(state, this);
|
|
1329
1405
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1330
1406
|
pattern = pattern.replace("{text}", escapedText);
|
|
1331
1407
|
let regex = new RegExp(pattern, "im");
|
|
1332
|
-
if (!regex.test(foundObj
|
|
1333
|
-
info.foundText = foundObj
|
|
1408
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1409
|
+
state.info.foundText = foundObj?.text;
|
|
1334
1410
|
throw new Error("element doesn't contain text " + text);
|
|
1335
1411
|
}
|
|
1336
|
-
return info;
|
|
1412
|
+
return state.info;
|
|
1337
1413
|
}
|
|
1338
1414
|
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;
|
|
1415
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1416
|
+
await _commandError(state, e, this);
|
|
1347
1417
|
}
|
|
1348
1418
|
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
|
-
});
|
|
1419
|
+
_commandFinally(state, this);
|
|
1370
1420
|
}
|
|
1371
1421
|
}
|
|
1372
1422
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1373
|
-
|
|
1374
|
-
|
|
1423
|
+
const state = {
|
|
1424
|
+
selectors,
|
|
1425
|
+
_params,
|
|
1426
|
+
value: text,
|
|
1427
|
+
options,
|
|
1428
|
+
world,
|
|
1429
|
+
locate: false,
|
|
1430
|
+
scroll: false,
|
|
1431
|
+
screenshot: false,
|
|
1432
|
+
highlight: false,
|
|
1433
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1434
|
+
text: `Verify element contains text: ${text}`,
|
|
1435
|
+
operation: "containsText",
|
|
1436
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1437
|
+
};
|
|
1375
1438
|
if (!text) {
|
|
1376
1439
|
throw new Error("text is null");
|
|
1377
1440
|
}
|
|
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;
|
|
1441
|
+
text = unEscapeString(text);
|
|
1386
1442
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1387
1443
|
if (newValue !== text) {
|
|
1388
1444
|
this.logger.info(text + "=" + newValue);
|
|
1389
1445
|
text = newValue;
|
|
1390
1446
|
}
|
|
1391
|
-
info.value = text;
|
|
1392
1447
|
let foundObj = null;
|
|
1393
1448
|
try {
|
|
1394
|
-
|
|
1449
|
+
await _preCommand(state, this);
|
|
1450
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1395
1451
|
if (foundObj && foundObj.element) {
|
|
1396
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1452
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1397
1453
|
}
|
|
1398
|
-
|
|
1454
|
+
await _screenshot(state, this);
|
|
1399
1455
|
const dateAlternatives = findDateAlternatives(text);
|
|
1400
1456
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1401
1457
|
if (dateAlternatives.date) {
|
|
1402
1458
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1403
|
-
if (
|
|
1404
|
-
|
|
1405
|
-
return info;
|
|
1459
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1460
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1461
|
+
return state.info;
|
|
1406
1462
|
}
|
|
1407
1463
|
}
|
|
1408
1464
|
throw new Error("element doesn't contain text " + text);
|
|
1409
1465
|
}
|
|
1410
1466
|
else if (numberAlternatives.number) {
|
|
1411
1467
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1412
|
-
if (
|
|
1413
|
-
|
|
1414
|
-
return info;
|
|
1468
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1469
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1470
|
+
return state.info;
|
|
1415
1471
|
}
|
|
1416
1472
|
}
|
|
1417
1473
|
throw new Error("element doesn't contain text " + text);
|
|
1418
1474
|
}
|
|
1419
|
-
else if (!
|
|
1420
|
-
info.foundText = foundObj
|
|
1421
|
-
info.value = foundObj
|
|
1475
|
+
else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
|
|
1476
|
+
state.info.foundText = foundObj?.text;
|
|
1477
|
+
state.info.value = foundObj?.value;
|
|
1422
1478
|
throw new Error("element doesn't contain text " + text);
|
|
1423
1479
|
}
|
|
1424
|
-
return info;
|
|
1480
|
+
return state.info;
|
|
1425
1481
|
}
|
|
1426
1482
|
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;
|
|
1483
|
+
await _commandError(state, e, this);
|
|
1434
1484
|
}
|
|
1435
1485
|
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
|
-
});
|
|
1486
|
+
_commandFinally(state, this);
|
|
1457
1487
|
}
|
|
1458
1488
|
}
|
|
1459
1489
|
_getDataFile(world = null) {
|
|
@@ -1472,6 +1502,29 @@ class StableBrowser {
|
|
|
1472
1502
|
}
|
|
1473
1503
|
return dataFile;
|
|
1474
1504
|
}
|
|
1505
|
+
async waitForUserInput(message, world = null) {
|
|
1506
|
+
if (!message) {
|
|
1507
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1508
|
+
}
|
|
1509
|
+
else {
|
|
1510
|
+
message = "# Wait for user input. " + message;
|
|
1511
|
+
}
|
|
1512
|
+
message += "\n";
|
|
1513
|
+
const value = await new Promise((resolve) => {
|
|
1514
|
+
const rl = readline.createInterface({
|
|
1515
|
+
input: process.stdin,
|
|
1516
|
+
output: process.stdout,
|
|
1517
|
+
});
|
|
1518
|
+
rl.question(message, (answer) => {
|
|
1519
|
+
rl.close();
|
|
1520
|
+
resolve(answer);
|
|
1521
|
+
});
|
|
1522
|
+
});
|
|
1523
|
+
if (value) {
|
|
1524
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1525
|
+
}
|
|
1526
|
+
this.setTestData({ userInput: value }, world);
|
|
1527
|
+
}
|
|
1475
1528
|
setTestData(testData, world = null) {
|
|
1476
1529
|
if (!testData) {
|
|
1477
1530
|
return;
|
|
@@ -1499,7 +1552,7 @@ class StableBrowser {
|
|
|
1499
1552
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1500
1553
|
const results = [];
|
|
1501
1554
|
return new Promise((resolve, reject) => {
|
|
1502
|
-
const readableStream = new
|
|
1555
|
+
const readableStream = new Readable();
|
|
1503
1556
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1504
1557
|
readableStream.push(data);
|
|
1505
1558
|
readableStream.push(null);
|
|
@@ -1659,7 +1712,6 @@ class StableBrowser {
|
|
|
1659
1712
|
}
|
|
1660
1713
|
async takeScreenshot(screenshotPath) {
|
|
1661
1714
|
const playContext = this.context.playContext;
|
|
1662
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1663
1715
|
// Using CDP to capture the screenshot
|
|
1664
1716
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1665
1717
|
document.body.scrollWidth,
|
|
@@ -1669,97 +1721,67 @@ class StableBrowser {
|
|
|
1669
1721
|
document.body.clientWidth,
|
|
1670
1722
|
document.documentElement.clientWidth,
|
|
1671
1723
|
])));
|
|
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();
|
|
1724
|
+
let screenshotBuffer = null;
|
|
1725
|
+
if (this.context.browserName === "chromium") {
|
|
1726
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1727
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1728
|
+
format: "png",
|
|
1729
|
+
// clip: {
|
|
1730
|
+
// x: 0,
|
|
1731
|
+
// y: 0,
|
|
1732
|
+
// width: viewportWidth,
|
|
1733
|
+
// height: viewportHeight,
|
|
1734
|
+
// scale: 1,
|
|
1735
|
+
// },
|
|
1736
|
+
});
|
|
1737
|
+
await client.detach();
|
|
1738
|
+
if (!screenshotPath) {
|
|
1739
|
+
return data;
|
|
1740
|
+
}
|
|
1741
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1742
|
+
}
|
|
1743
|
+
else {
|
|
1744
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1745
|
+
}
|
|
1746
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1747
|
+
// Get the image dimensions
|
|
1748
|
+
const { width, height } = image.bitmap;
|
|
1749
|
+
const resizeRatio = viewportWidth / width;
|
|
1750
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1751
|
+
if (width > viewportWidth) {
|
|
1752
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1753
|
+
await image.write(screenshotPath);
|
|
1754
|
+
}
|
|
1755
|
+
else {
|
|
1756
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1757
|
+
}
|
|
1707
1758
|
}
|
|
1708
1759
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1760
|
+
const state = {
|
|
1761
|
+
selectors,
|
|
1762
|
+
_params,
|
|
1763
|
+
options,
|
|
1764
|
+
world,
|
|
1765
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1766
|
+
text: `Verify element exists in page`,
|
|
1767
|
+
operation: "verifyElementExistInPage",
|
|
1768
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1769
|
+
};
|
|
1714
1770
|
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
1771
|
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;
|
|
1772
|
+
await _preCommand(state, this);
|
|
1773
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1774
|
+
return state.info;
|
|
1728
1775
|
}
|
|
1729
1776
|
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;
|
|
1777
|
+
await _commandError(state, e, this);
|
|
1737
1778
|
}
|
|
1738
1779
|
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
|
-
});
|
|
1780
|
+
_commandFinally(state, this);
|
|
1759
1781
|
}
|
|
1760
1782
|
}
|
|
1761
1783
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1762
|
-
|
|
1784
|
+
_validateSelectors(selectors);
|
|
1763
1785
|
const startTime = Date.now();
|
|
1764
1786
|
let error = null;
|
|
1765
1787
|
let screenshotId = null;
|
|
@@ -1818,7 +1840,7 @@ class StableBrowser {
|
|
|
1818
1840
|
status: "FAILED",
|
|
1819
1841
|
startTime,
|
|
1820
1842
|
endTime,
|
|
1821
|
-
message: error
|
|
1843
|
+
message: error?.message,
|
|
1822
1844
|
}
|
|
1823
1845
|
: {
|
|
1824
1846
|
status: "PASSED",
|
|
@@ -2027,7 +2049,7 @@ class StableBrowser {
|
|
|
2027
2049
|
status: "FAILED",
|
|
2028
2050
|
startTime,
|
|
2029
2051
|
endTime,
|
|
2030
|
-
message: error
|
|
2052
|
+
message: error?.message,
|
|
2031
2053
|
}
|
|
2032
2054
|
: {
|
|
2033
2055
|
status: "PASSED",
|
|
@@ -2063,20 +2085,20 @@ class StableBrowser {
|
|
|
2063
2085
|
for (let i = 0; i < frames.length; i++) {
|
|
2064
2086
|
if (dateAlternatives.date) {
|
|
2065
2087
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2066
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
|
|
2088
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, true, {});
|
|
2067
2089
|
result.frame = frames[i];
|
|
2068
2090
|
results.push(result);
|
|
2069
2091
|
}
|
|
2070
2092
|
}
|
|
2071
2093
|
else if (numberAlternatives.number) {
|
|
2072
2094
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2073
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
|
|
2095
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, true, {});
|
|
2074
2096
|
result.frame = frames[i];
|
|
2075
2097
|
results.push(result);
|
|
2076
2098
|
}
|
|
2077
2099
|
}
|
|
2078
2100
|
else {
|
|
2079
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, {});
|
|
2101
|
+
const result = await this._locateElementByText(frames[i], text, "*", true, true, {});
|
|
2080
2102
|
result.frame = frames[i];
|
|
2081
2103
|
results.push(result);
|
|
2082
2104
|
}
|
|
@@ -2125,7 +2147,7 @@ class StableBrowser {
|
|
|
2125
2147
|
status: "FAILED",
|
|
2126
2148
|
startTime,
|
|
2127
2149
|
endTime,
|
|
2128
|
-
message: error
|
|
2150
|
+
message: error?.message,
|
|
2129
2151
|
}
|
|
2130
2152
|
: {
|
|
2131
2153
|
status: "PASSED",
|
|
@@ -2209,7 +2231,7 @@ class StableBrowser {
|
|
|
2209
2231
|
status: "FAILED",
|
|
2210
2232
|
startTime,
|
|
2211
2233
|
endTime,
|
|
2212
|
-
message: error
|
|
2234
|
+
message: error?.message,
|
|
2213
2235
|
}
|
|
2214
2236
|
: {
|
|
2215
2237
|
status: "PASSED",
|
|
@@ -2241,7 +2263,7 @@ class StableBrowser {
|
|
|
2241
2263
|
this.logger.info("Table data verified");
|
|
2242
2264
|
}
|
|
2243
2265
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2244
|
-
|
|
2266
|
+
_validateSelectors(selectors);
|
|
2245
2267
|
const startTime = Date.now();
|
|
2246
2268
|
let error = null;
|
|
2247
2269
|
let screenshotId = null;
|
|
@@ -2277,7 +2299,7 @@ class StableBrowser {
|
|
|
2277
2299
|
status: "FAILED",
|
|
2278
2300
|
startTime,
|
|
2279
2301
|
endTime,
|
|
2280
|
-
message: error
|
|
2302
|
+
message: error?.message,
|
|
2281
2303
|
}
|
|
2282
2304
|
: {
|
|
2283
2305
|
status: "PASSED",
|
|
@@ -2289,7 +2311,7 @@ class StableBrowser {
|
|
|
2289
2311
|
}
|
|
2290
2312
|
}
|
|
2291
2313
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2292
|
-
|
|
2314
|
+
_validateSelectors(selectors);
|
|
2293
2315
|
if (!query) {
|
|
2294
2316
|
throw new Error("query is null");
|
|
2295
2317
|
}
|
|
@@ -2442,7 +2464,7 @@ class StableBrowser {
|
|
|
2442
2464
|
status: "FAILED",
|
|
2443
2465
|
startTime,
|
|
2444
2466
|
endTime,
|
|
2445
|
-
message: error
|
|
2467
|
+
message: error?.message,
|
|
2446
2468
|
}
|
|
2447
2469
|
: {
|
|
2448
2470
|
status: "PASSED",
|
|
@@ -2454,27 +2476,7 @@ class StableBrowser {
|
|
|
2454
2476
|
}
|
|
2455
2477
|
}
|
|
2456
2478
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2457
|
-
|
|
2458
|
-
return value;
|
|
2459
|
-
}
|
|
2460
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2461
|
-
let regex = /{{(.*?)}}/g;
|
|
2462
|
-
let matches = value.match(regex);
|
|
2463
|
-
if (matches) {
|
|
2464
|
-
const testData = this.getTestData(world);
|
|
2465
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2466
|
-
let match = matches[i];
|
|
2467
|
-
let key = match.substring(2, match.length - 2);
|
|
2468
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2469
|
-
if (newValue !== null) {
|
|
2470
|
-
value = value.replace(match, newValue);
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2475
|
-
return await decrypt(value, null, totpWait);
|
|
2476
|
-
}
|
|
2477
|
-
return value;
|
|
2479
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2478
2480
|
}
|
|
2479
2481
|
_getLoadTimeout(options) {
|
|
2480
2482
|
let timeout = 15000;
|
|
@@ -2511,13 +2513,13 @@ class StableBrowser {
|
|
|
2511
2513
|
}
|
|
2512
2514
|
catch (e) {
|
|
2513
2515
|
if (e.label === "networkidle") {
|
|
2514
|
-
console.log("
|
|
2516
|
+
console.log("waited for the network to be idle timeout");
|
|
2515
2517
|
}
|
|
2516
2518
|
else if (e.label === "load") {
|
|
2517
|
-
console.log("
|
|
2519
|
+
console.log("waited for the load timeout");
|
|
2518
2520
|
}
|
|
2519
2521
|
else if (e.label === "domcontentloaded") {
|
|
2520
|
-
console.log("
|
|
2522
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2521
2523
|
}
|
|
2522
2524
|
console.log(".");
|
|
2523
2525
|
}
|
|
@@ -2534,7 +2536,7 @@ class StableBrowser {
|
|
|
2534
2536
|
status: "FAILED",
|
|
2535
2537
|
startTime,
|
|
2536
2538
|
endTime,
|
|
2537
|
-
message: error
|
|
2539
|
+
message: error?.message,
|
|
2538
2540
|
}
|
|
2539
2541
|
: {
|
|
2540
2542
|
status: "PASSED",
|
|
@@ -2552,13 +2554,6 @@ class StableBrowser {
|
|
|
2552
2554
|
const info = {};
|
|
2553
2555
|
try {
|
|
2554
2556
|
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
2557
|
}
|
|
2563
2558
|
catch (e) {
|
|
2564
2559
|
console.log(".");
|
|
@@ -2576,7 +2571,7 @@ class StableBrowser {
|
|
|
2576
2571
|
status: "FAILED",
|
|
2577
2572
|
startTime,
|
|
2578
2573
|
endTime,
|
|
2579
|
-
message: error
|
|
2574
|
+
message: error?.message,
|
|
2580
2575
|
}
|
|
2581
2576
|
: {
|
|
2582
2577
|
status: "PASSED",
|
|
@@ -2618,7 +2613,7 @@ class StableBrowser {
|
|
|
2618
2613
|
status: "FAILED",
|
|
2619
2614
|
startTime,
|
|
2620
2615
|
endTime,
|
|
2621
|
-
message: error
|
|
2616
|
+
message: error?.message,
|
|
2622
2617
|
}
|
|
2623
2618
|
: {
|
|
2624
2619
|
status: "PASSED",
|
|
@@ -2654,7 +2649,7 @@ class StableBrowser {
|
|
|
2654
2649
|
status: "FAILED",
|
|
2655
2650
|
startTime,
|
|
2656
2651
|
endTime,
|
|
2657
|
-
message: error
|
|
2652
|
+
message: error?.message,
|
|
2658
2653
|
}
|
|
2659
2654
|
: {
|
|
2660
2655
|
status: "PASSED",
|
|
@@ -2667,33 +2662,18 @@ class StableBrowser {
|
|
|
2667
2662
|
}
|
|
2668
2663
|
async scrollIfNeeded(element, info) {
|
|
2669
2664
|
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
|
-
}
|
|
2665
|
+
await element.scrollIntoViewIfNeeded({
|
|
2666
|
+
timeout: 2000,
|
|
2687
2667
|
});
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
}
|
|
2668
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2669
|
+
if (info) {
|
|
2670
|
+
info.box = await element.boundingBox({
|
|
2671
|
+
timeout: 1000,
|
|
2672
|
+
});
|
|
2693
2673
|
}
|
|
2694
2674
|
}
|
|
2695
2675
|
catch (e) {
|
|
2696
|
-
console.log("
|
|
2676
|
+
console.log("#-#");
|
|
2697
2677
|
}
|
|
2698
2678
|
}
|
|
2699
2679
|
_reportToWorld(world, properties) {
|
|
@@ -2854,5 +2834,10 @@ const KEYBOARD_EVENTS = [
|
|
|
2854
2834
|
"TVAntennaCable",
|
|
2855
2835
|
"TVAudioDescription",
|
|
2856
2836
|
];
|
|
2837
|
+
function unEscapeString(str) {
|
|
2838
|
+
const placeholder = "__NEWLINE__";
|
|
2839
|
+
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2840
|
+
return str;
|
|
2841
|
+
}
|
|
2857
2842
|
export { StableBrowser };
|
|
2858
2843
|
//# sourceMappingURL=stable_browser.js.map
|