automation_model 1.0.404-dev → 1.0.404-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 +227 -41
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +1 -1
- package/lib/auto_page.js +43 -17
- package/lib/auto_page.js.map +1 -1
- package/lib/axe/axe.mini.js +12 -0
- package/lib/browser_manager.d.ts +6 -3
- package/lib/browser_manager.js +48 -20
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +6 -0
- package/lib/command_common.js +138 -0
- package/lib/command_common.js.map +1 -0
- package/lib/environment.d.ts +3 -0
- package/lib/environment.js +5 -2
- package/lib/environment.js.map +1 -1
- package/lib/error-messages.d.ts +6 -0
- package/lib/error-messages.js +188 -0
- package/lib/error-messages.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +54 -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/locator.d.ts +36 -0
- package/lib/locator.js +165 -0
- package/lib/locator.js.map +1 -1
- 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 +40 -23
- package/lib/stable_browser.js +942 -891
- package/lib/stable_browser.js.map +1 -1
- package/lib/table.d.ts +13 -0
- package/lib/table.js +187 -0
- package/lib/table.js.map +1 -0
- package/lib/test_context.d.ts +4 -0
- package/lib/test_context.js +12 -9
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +3 -1
- package/lib/utils.js +70 -3
- package/lib/utils.js.map +1 -1
- package/package.json +10 -7
package/lib/stable_browser.js
CHANGED
|
@@ -2,17 +2,23 @@
|
|
|
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 { randomUUID } from "crypto";
|
|
20
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
|
+
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
16
22
|
const Types = {
|
|
17
23
|
CLICK: "click_element",
|
|
18
24
|
NAVIGATE: "navigate",
|
|
@@ -37,16 +43,29 @@ const Types = {
|
|
|
37
43
|
SET_DATE_TIME: "set_date_time",
|
|
38
44
|
SET_VIEWPORT: "set_viewport",
|
|
39
45
|
VERIFY_VISUAL: "verify_visual",
|
|
46
|
+
LOAD_DATA: "load_data",
|
|
47
|
+
SET_INPUT: "set_input",
|
|
48
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
40
49
|
};
|
|
50
|
+
export const apps = {};
|
|
41
51
|
class StableBrowser {
|
|
42
|
-
|
|
52
|
+
browser;
|
|
53
|
+
page;
|
|
54
|
+
logger;
|
|
55
|
+
context;
|
|
56
|
+
world;
|
|
57
|
+
project_path = null;
|
|
58
|
+
webLogFile = null;
|
|
59
|
+
networkLogger = null;
|
|
60
|
+
configuration = null;
|
|
61
|
+
appName = "main";
|
|
62
|
+
tags = null;
|
|
63
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
43
64
|
this.browser = browser;
|
|
44
65
|
this.page = page;
|
|
45
66
|
this.logger = logger;
|
|
46
67
|
this.context = context;
|
|
47
|
-
this.
|
|
48
|
-
this.webLogFile = null;
|
|
49
|
-
this.configuration = null;
|
|
68
|
+
this.world = world;
|
|
50
69
|
if (!this.logger) {
|
|
51
70
|
this.logger = console;
|
|
52
71
|
}
|
|
@@ -72,22 +91,62 @@ class StableBrowser {
|
|
|
72
91
|
this.logger.error("unable to read ai_config.json");
|
|
73
92
|
}
|
|
74
93
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
75
|
-
this.
|
|
76
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
77
|
-
this.registerRequestListener();
|
|
94
|
+
this.world = world;
|
|
78
95
|
context.pages = [this.page];
|
|
79
96
|
context.pageLoading = { status: false };
|
|
97
|
+
this.registerEventListeners(this.context);
|
|
98
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
99
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
100
|
+
}
|
|
101
|
+
async scrollPageToLoadLazyElements() {
|
|
102
|
+
let lastHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
103
|
+
let retry = 0;
|
|
104
|
+
while (true) {
|
|
105
|
+
await this.page.evaluate(() => window.scrollBy(0, window.innerHeight));
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
107
|
+
let newHeight = await this.page.evaluate(() => document.body.scrollHeight);
|
|
108
|
+
if (newHeight === lastHeight) {
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
lastHeight = newHeight;
|
|
112
|
+
retry++;
|
|
113
|
+
if (retry > 10) {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
await this.page.evaluate(() => window.scrollTo(0, 0));
|
|
118
|
+
}
|
|
119
|
+
registerEventListeners(context) {
|
|
120
|
+
this.registerConsoleLogListener(this.page, context);
|
|
121
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
122
|
+
if (!context.pageLoading) {
|
|
123
|
+
context.pageLoading = { status: false };
|
|
124
|
+
}
|
|
80
125
|
context.playContext.on("page", async function (page) {
|
|
126
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
127
|
+
console.log("close unexpected popups");
|
|
128
|
+
await page.close();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
81
131
|
context.pageLoading.status = true;
|
|
82
132
|
this.page = page;
|
|
83
133
|
context.page = page;
|
|
84
134
|
context.pages.push(page);
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
135
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
136
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
137
|
+
page.on("close", async () => {
|
|
138
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
139
|
+
this.context.pages.pop();
|
|
140
|
+
this.page = this.context.pages[this.context.pages.length - 1];
|
|
141
|
+
this.context.page = this.page;
|
|
142
|
+
try {
|
|
143
|
+
let title = await this.page.title();
|
|
144
|
+
console.log("Switched to page " + title);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
console.error("Error on page close", error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
91
150
|
});
|
|
92
151
|
try {
|
|
93
152
|
await this.waitForPageLoad();
|
|
@@ -99,6 +158,36 @@ class StableBrowser {
|
|
|
99
158
|
context.pageLoading.status = false;
|
|
100
159
|
}.bind(this));
|
|
101
160
|
}
|
|
161
|
+
async switchApp(appName) {
|
|
162
|
+
// check if the current app (this.appName) is the same as the new app
|
|
163
|
+
if (this.appName === appName) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
let navigate = false;
|
|
167
|
+
if (!apps[appName]) {
|
|
168
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
169
|
+
newContextCreated = true;
|
|
170
|
+
apps[appName] = {
|
|
171
|
+
context: newContext,
|
|
172
|
+
browser: newContext.browser,
|
|
173
|
+
page: newContext.page,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
const tempContext = {};
|
|
177
|
+
this._copyContext(this, tempContext);
|
|
178
|
+
this._copyContext(apps[appName], this);
|
|
179
|
+
apps[this.appName] = tempContext;
|
|
180
|
+
this.appName = appName;
|
|
181
|
+
if (navigate) {
|
|
182
|
+
await this.goto(this.context.environment.baseUrl);
|
|
183
|
+
await this.waitForPageLoad();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
_copyContext(from, to) {
|
|
187
|
+
to.browser = from.browser;
|
|
188
|
+
to.page = from.page;
|
|
189
|
+
to.context = from.context;
|
|
190
|
+
}
|
|
102
191
|
getWebLogFile(logFolder) {
|
|
103
192
|
if (!fs.existsSync(logFolder)) {
|
|
104
193
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -110,37 +199,63 @@ class StableBrowser {
|
|
|
110
199
|
const fileName = nextIndex + ".json";
|
|
111
200
|
return path.join(logFolder, fileName);
|
|
112
201
|
}
|
|
113
|
-
registerConsoleLogListener(page, context
|
|
202
|
+
registerConsoleLogListener(page, context) {
|
|
114
203
|
if (!this.context.webLogger) {
|
|
115
204
|
this.context.webLogger = [];
|
|
116
205
|
}
|
|
117
206
|
page.on("console", async (msg) => {
|
|
118
|
-
|
|
207
|
+
const obj = {
|
|
119
208
|
type: msg.type(),
|
|
120
209
|
text: msg.text(),
|
|
121
210
|
location: msg.location(),
|
|
122
211
|
time: new Date().toISOString(),
|
|
123
|
-
}
|
|
124
|
-
|
|
212
|
+
};
|
|
213
|
+
this.context.webLogger.push(obj);
|
|
214
|
+
if (msg.type() === "error") {
|
|
215
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
216
|
+
}
|
|
125
217
|
});
|
|
126
218
|
}
|
|
127
|
-
registerRequestListener() {
|
|
128
|
-
this.
|
|
219
|
+
registerRequestListener(page, context, logFile) {
|
|
220
|
+
if (!this.context.networkLogger) {
|
|
221
|
+
this.context.networkLogger = [];
|
|
222
|
+
}
|
|
223
|
+
page.on("request", async (data) => {
|
|
224
|
+
const startTime = new Date().getTime();
|
|
129
225
|
try {
|
|
130
|
-
const pageUrl = new URL(
|
|
226
|
+
const pageUrl = new URL(page.url());
|
|
131
227
|
const requestUrl = new URL(data.url());
|
|
132
228
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
133
229
|
const method = data.method();
|
|
134
|
-
if (
|
|
230
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
135
231
|
const token = await data.headerValue("Authorization");
|
|
136
232
|
if (token) {
|
|
137
|
-
|
|
233
|
+
context.authtoken = token;
|
|
138
234
|
}
|
|
139
235
|
}
|
|
140
236
|
}
|
|
237
|
+
const response = await data.response();
|
|
238
|
+
const endTime = new Date().getTime();
|
|
239
|
+
const obj = {
|
|
240
|
+
url: data.url(),
|
|
241
|
+
method: data.method(),
|
|
242
|
+
postData: data.postData(),
|
|
243
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
244
|
+
duration: endTime - startTime,
|
|
245
|
+
startTime,
|
|
246
|
+
};
|
|
247
|
+
context.networkLogger.push(obj);
|
|
248
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
141
249
|
}
|
|
142
250
|
catch (error) {
|
|
143
|
-
console.error("Error in request listener", error);
|
|
251
|
+
// console.error("Error in request listener", error);
|
|
252
|
+
context.networkLogger.push({
|
|
253
|
+
error: "not able to listen",
|
|
254
|
+
message: error.message,
|
|
255
|
+
stack: error.stack,
|
|
256
|
+
time: new Date().toISOString(),
|
|
257
|
+
});
|
|
258
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
144
259
|
}
|
|
145
260
|
});
|
|
146
261
|
}
|
|
@@ -155,20 +270,6 @@ class StableBrowser {
|
|
|
155
270
|
timeout: 60000,
|
|
156
271
|
});
|
|
157
272
|
}
|
|
158
|
-
_validateSelectors(selectors) {
|
|
159
|
-
if (!selectors) {
|
|
160
|
-
throw new Error("selectors is null");
|
|
161
|
-
}
|
|
162
|
-
if (!selectors.locators) {
|
|
163
|
-
throw new Error("selectors.locators is null");
|
|
164
|
-
}
|
|
165
|
-
if (!Array.isArray(selectors.locators)) {
|
|
166
|
-
throw new Error("selectors.locators expected to be array");
|
|
167
|
-
}
|
|
168
|
-
if (selectors.locators.length === 0) {
|
|
169
|
-
throw new Error("selectors.locators expected to be non empty array");
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
273
|
_fixUsingParams(text, _params) {
|
|
173
274
|
if (!_params || typeof text !== "string") {
|
|
174
275
|
return text;
|
|
@@ -210,9 +311,7 @@ class StableBrowser {
|
|
|
210
311
|
}
|
|
211
312
|
_getLocator(locator, scope, _params) {
|
|
212
313
|
locator = this._fixLocatorUsingParams(locator, _params);
|
|
213
|
-
|
|
214
|
-
return scope.locator(locator.selector);
|
|
215
|
-
}
|
|
314
|
+
let locatorReturn;
|
|
216
315
|
if (locator.role) {
|
|
217
316
|
if (locator.role[1].nameReg) {
|
|
218
317
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
@@ -221,23 +320,48 @@ class StableBrowser {
|
|
|
221
320
|
// if (locator.role[1].name) {
|
|
222
321
|
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
223
322
|
// }
|
|
224
|
-
|
|
323
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
225
324
|
}
|
|
226
325
|
if (locator.css) {
|
|
227
|
-
|
|
326
|
+
locatorReturn = scope.locator(locator.css);
|
|
228
327
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
328
|
+
// handle role/name locators
|
|
329
|
+
// locator.selector will be something like: textbox[name="Username"i]
|
|
330
|
+
if (locator.engine === "internal:role") {
|
|
331
|
+
// extract the role, name and the i/s flags using regex
|
|
332
|
+
const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
|
|
333
|
+
if (match) {
|
|
334
|
+
const role = match[1];
|
|
335
|
+
const name = match[3];
|
|
336
|
+
const flags = match[4];
|
|
337
|
+
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
233
338
|
}
|
|
234
|
-
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
235
|
-
return locator;
|
|
236
339
|
}
|
|
237
|
-
|
|
340
|
+
if (locator?.engine) {
|
|
341
|
+
if (locator.engine === "css") {
|
|
342
|
+
locatorReturn = scope.locator(locator.selector);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
let selector = locator.selector;
|
|
346
|
+
if (locator.engine === "internal:attr") {
|
|
347
|
+
if (!selector.startsWith("[")) {
|
|
348
|
+
selector = `[${selector}]`;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (!locatorReturn) {
|
|
355
|
+
console.error(locator);
|
|
356
|
+
throw new Error("Locator undefined");
|
|
357
|
+
}
|
|
358
|
+
return locatorReturn;
|
|
238
359
|
}
|
|
239
360
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
240
|
-
|
|
361
|
+
if (css && css.locator) {
|
|
362
|
+
css = css.locator;
|
|
363
|
+
}
|
|
364
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
241
365
|
if (result.elementCount === 0) {
|
|
242
366
|
return;
|
|
243
367
|
}
|
|
@@ -252,7 +376,7 @@ class StableBrowser {
|
|
|
252
376
|
}
|
|
253
377
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
254
378
|
//const stringifyText = JSON.stringify(text);
|
|
255
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
379
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
256
380
|
function isParent(parent, child) {
|
|
257
381
|
let currentNode = child.parentNode;
|
|
258
382
|
while (currentNode !== null) {
|
|
@@ -264,6 +388,15 @@ class StableBrowser {
|
|
|
264
388
|
return false;
|
|
265
389
|
}
|
|
266
390
|
document.isParent = isParent;
|
|
391
|
+
function getRegex(str) {
|
|
392
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
393
|
+
if (!match) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
let [_, pattern, flags] = match;
|
|
397
|
+
return new RegExp(pattern, flags);
|
|
398
|
+
}
|
|
399
|
+
document.getRegex = getRegex;
|
|
267
400
|
function collectAllShadowDomElements(element, result = []) {
|
|
268
401
|
// Check and add the element if it has a shadow root
|
|
269
402
|
if (element.shadowRoot) {
|
|
@@ -280,7 +413,11 @@ class StableBrowser {
|
|
|
280
413
|
}
|
|
281
414
|
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
282
415
|
if (!tag) {
|
|
283
|
-
tag = "
|
|
416
|
+
tag = "*:not(script, style, head)";
|
|
417
|
+
}
|
|
418
|
+
let regexpSearch = document.getRegex(text);
|
|
419
|
+
if (regexpSearch) {
|
|
420
|
+
regex = true;
|
|
284
421
|
}
|
|
285
422
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
286
423
|
let shadowHosts = [];
|
|
@@ -297,7 +434,9 @@ class StableBrowser {
|
|
|
297
434
|
let randomToken = null;
|
|
298
435
|
const foundElements = [];
|
|
299
436
|
if (regex) {
|
|
300
|
-
|
|
437
|
+
if (!regexpSearch) {
|
|
438
|
+
regexpSearch = new RegExp(text, "im");
|
|
439
|
+
}
|
|
301
440
|
for (let i = 0; i < elements.length; i++) {
|
|
302
441
|
const element = elements[i];
|
|
303
442
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -311,8 +450,8 @@ class StableBrowser {
|
|
|
311
450
|
for (let i = 0; i < elements.length; i++) {
|
|
312
451
|
const element = elements[i];
|
|
313
452
|
if (partial) {
|
|
314
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
315
|
-
(element.value && element.value.includes(text))) {
|
|
453
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
454
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
316
455
|
foundElements.push(element);
|
|
317
456
|
}
|
|
318
457
|
}
|
|
@@ -356,19 +495,39 @@ class StableBrowser {
|
|
|
356
495
|
}, [text1, tag1, regex1, partial1]);
|
|
357
496
|
}
|
|
358
497
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
498
|
+
if (!info) {
|
|
499
|
+
info = {};
|
|
500
|
+
}
|
|
501
|
+
if (!info.failCause) {
|
|
502
|
+
info.failCause = {};
|
|
503
|
+
}
|
|
504
|
+
if (!info.log) {
|
|
505
|
+
info.log = "";
|
|
506
|
+
}
|
|
359
507
|
let locatorSearch = selectorHierarchy[index];
|
|
508
|
+
try {
|
|
509
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
510
|
+
}
|
|
511
|
+
catch (e) {
|
|
512
|
+
console.error(e);
|
|
513
|
+
}
|
|
360
514
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
361
515
|
let locator = null;
|
|
362
516
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
363
517
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
364
518
|
if (!locatorString) {
|
|
519
|
+
info.failCause.textNotFound = true;
|
|
520
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
365
521
|
return;
|
|
366
522
|
}
|
|
367
523
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
368
524
|
}
|
|
369
525
|
else if (locatorSearch.text) {
|
|
370
|
-
let
|
|
526
|
+
let text = this._fixUsingParams(locatorSearch.text, _params);
|
|
527
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
371
528
|
if (result.elementCount === 0) {
|
|
529
|
+
info.failCause.textNotFound = true;
|
|
530
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
372
531
|
return;
|
|
373
532
|
}
|
|
374
533
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -385,10 +544,13 @@ class StableBrowser {
|
|
|
385
544
|
// cssHref = true;
|
|
386
545
|
// }
|
|
387
546
|
let count = await locator.count();
|
|
547
|
+
if (count > 0 && !info.failCause.count) {
|
|
548
|
+
info.failCause.count = count;
|
|
549
|
+
}
|
|
388
550
|
//info.log += "total elements found " + count + "\n";
|
|
389
551
|
//let visibleCount = 0;
|
|
390
552
|
let visibleLocator = null;
|
|
391
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
553
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
392
554
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
393
555
|
return;
|
|
394
556
|
}
|
|
@@ -402,6 +564,8 @@ class StableBrowser {
|
|
|
402
564
|
foundLocators.push(locator.nth(j));
|
|
403
565
|
}
|
|
404
566
|
else {
|
|
567
|
+
info.failCause.visible = visible;
|
|
568
|
+
info.failCause.enabled = enabled;
|
|
405
569
|
if (!info.printMessages) {
|
|
406
570
|
info.printMessages = {};
|
|
407
571
|
}
|
|
@@ -413,6 +577,11 @@ class StableBrowser {
|
|
|
413
577
|
}
|
|
414
578
|
}
|
|
415
579
|
async closeUnexpectedPopups(info, _params) {
|
|
580
|
+
if (!info) {
|
|
581
|
+
info = {};
|
|
582
|
+
info.failCause = {};
|
|
583
|
+
info.log = "";
|
|
584
|
+
}
|
|
416
585
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
417
586
|
if (!info) {
|
|
418
587
|
info = {};
|
|
@@ -444,16 +613,33 @@ class StableBrowser {
|
|
|
444
613
|
}
|
|
445
614
|
if (result.foundElements.length > 0) {
|
|
446
615
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
447
|
-
|
|
616
|
+
try {
|
|
617
|
+
await scope?.evaluate(() => {
|
|
618
|
+
window.__isClosingPopups = true;
|
|
619
|
+
});
|
|
620
|
+
await dialogCloseLocator.click();
|
|
621
|
+
// wait for the dialog to close
|
|
622
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
623
|
+
}
|
|
624
|
+
catch (e) {
|
|
625
|
+
}
|
|
626
|
+
finally {
|
|
627
|
+
await scope?.evaluate(() => {
|
|
628
|
+
window.__isClosingPopups = false;
|
|
629
|
+
});
|
|
630
|
+
}
|
|
448
631
|
return { rerun: true };
|
|
449
632
|
}
|
|
450
633
|
}
|
|
451
634
|
}
|
|
452
635
|
return { rerun: false };
|
|
453
636
|
}
|
|
454
|
-
async _locate(selectors, info, _params, timeout
|
|
637
|
+
async _locate(selectors, info, _params, timeout) {
|
|
638
|
+
if (!timeout) {
|
|
639
|
+
timeout = 30000;
|
|
640
|
+
}
|
|
455
641
|
for (let i = 0; i < 3; i++) {
|
|
456
|
-
info.log += "attempt " + i + ":
|
|
642
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
457
643
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
458
644
|
let selector = selectors.locators[j];
|
|
459
645
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -465,17 +651,49 @@ class StableBrowser {
|
|
|
465
651
|
}
|
|
466
652
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
467
653
|
}
|
|
468
|
-
async
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
654
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
655
|
+
if (!info) {
|
|
656
|
+
info = {};
|
|
657
|
+
info.failCause = {};
|
|
658
|
+
info.log = "";
|
|
659
|
+
}
|
|
474
660
|
let scope = this.page;
|
|
661
|
+
if (selectors.frame) {
|
|
662
|
+
return selectors.frame;
|
|
663
|
+
}
|
|
475
664
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
476
|
-
|
|
665
|
+
const findFrame = async (frame, framescope) => {
|
|
666
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
667
|
+
let frameLocator = frame.selectors[i];
|
|
668
|
+
if (frameLocator.css) {
|
|
669
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
670
|
+
if (frameLocator.index) {
|
|
671
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
672
|
+
}
|
|
673
|
+
try {
|
|
674
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
675
|
+
timeout: 5000,
|
|
676
|
+
});
|
|
677
|
+
framescope = testframescope;
|
|
678
|
+
break;
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
console.error("frame not found " + frameLocator.css);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (frame.children) {
|
|
686
|
+
return await findFrame(frame.children, framescope);
|
|
687
|
+
}
|
|
688
|
+
return framescope;
|
|
689
|
+
};
|
|
477
690
|
while (true) {
|
|
478
691
|
let frameFound = false;
|
|
692
|
+
if (selectors.nestFrmLoc) {
|
|
693
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
694
|
+
frameFound = true;
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
479
697
|
if (selectors.frameLocators) {
|
|
480
698
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
481
699
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -492,6 +710,8 @@ class StableBrowser {
|
|
|
492
710
|
if (!scope) {
|
|
493
711
|
info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
494
712
|
if (performance.now() - startTime > timeout) {
|
|
713
|
+
info.failCause.iframeNotFound = true;
|
|
714
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
495
715
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
496
716
|
}
|
|
497
717
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -501,6 +721,31 @@ class StableBrowser {
|
|
|
501
721
|
}
|
|
502
722
|
}
|
|
503
723
|
}
|
|
724
|
+
if (!scope) {
|
|
725
|
+
scope = this.page;
|
|
726
|
+
}
|
|
727
|
+
return scope;
|
|
728
|
+
}
|
|
729
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
730
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
731
|
+
return scope.evaluate(() => {
|
|
732
|
+
var bodyContent = document.body.innerHTML;
|
|
733
|
+
return bodyContent;
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
737
|
+
if (!info) {
|
|
738
|
+
info = {};
|
|
739
|
+
info.failCause = {};
|
|
740
|
+
info.log = "";
|
|
741
|
+
}
|
|
742
|
+
let highPriorityTimeout = 5000;
|
|
743
|
+
let visibleOnlyTimeout = 6000;
|
|
744
|
+
let startTime = performance.now();
|
|
745
|
+
let locatorsCount = 0;
|
|
746
|
+
let lazy_scroll = false;
|
|
747
|
+
//let arrayMode = Array.isArray(selectors);
|
|
748
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
504
749
|
let selectorsLocators = null;
|
|
505
750
|
selectorsLocators = selectors.locators;
|
|
506
751
|
// group selectors by priority
|
|
@@ -593,6 +838,10 @@ class StableBrowser {
|
|
|
593
838
|
if (performance.now() - startTime > highPriorityTimeout) {
|
|
594
839
|
info.log += "high priority timeout, will try all elements" + "\n";
|
|
595
840
|
highPriorityOnly = false;
|
|
841
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
842
|
+
lazy_scroll = true;
|
|
843
|
+
await this.scrollPageToLoadLazyElements();
|
|
844
|
+
}
|
|
596
845
|
}
|
|
597
846
|
if (performance.now() - startTime > visibleOnlyTimeout) {
|
|
598
847
|
info.log += "visible only timeout, will try all elements" + "\n";
|
|
@@ -602,6 +851,8 @@ class StableBrowser {
|
|
|
602
851
|
}
|
|
603
852
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
604
853
|
info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
854
|
+
info.failCause.locatorNotFound = true;
|
|
855
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
605
856
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
606
857
|
}
|
|
607
858
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -633,86 +884,129 @@ class StableBrowser {
|
|
|
633
884
|
});
|
|
634
885
|
result.locatorIndex = i;
|
|
635
886
|
}
|
|
887
|
+
if (foundLocators.length > 1) {
|
|
888
|
+
info.failCause.foundMultiple = true;
|
|
889
|
+
}
|
|
636
890
|
}
|
|
637
891
|
return result;
|
|
638
892
|
}
|
|
639
|
-
async
|
|
640
|
-
this._validateSelectors(selectors);
|
|
893
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
641
894
|
const startTime = Date.now();
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
895
|
+
let timeout = 30000;
|
|
896
|
+
if (options && options.timeout) {
|
|
897
|
+
timeout = options.timeout;
|
|
898
|
+
}
|
|
899
|
+
while (true) {
|
|
900
|
+
try {
|
|
901
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
902
|
+
if (result?.elementNumber >= 0) {
|
|
903
|
+
const selectors = {
|
|
904
|
+
frame: result?.frame,
|
|
905
|
+
locators: [
|
|
906
|
+
{
|
|
907
|
+
css: result?.css,
|
|
908
|
+
},
|
|
909
|
+
],
|
|
910
|
+
};
|
|
911
|
+
await this.click(selectors, _params, options, world);
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
catch (e) {
|
|
916
|
+
if (performance.now() - startTime > timeout) {
|
|
917
|
+
// throw e;
|
|
918
|
+
await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
925
|
+
const startTime = Date.now();
|
|
926
|
+
let timeout = 30000;
|
|
927
|
+
if (options && options.timeout) {
|
|
928
|
+
timeout = options.timeout;
|
|
929
|
+
}
|
|
930
|
+
while (true) {
|
|
931
|
+
try {
|
|
932
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
933
|
+
if (result?.elementNumber >= 0) {
|
|
934
|
+
const selectors = {
|
|
935
|
+
frame: result?.frame,
|
|
936
|
+
locators: [
|
|
937
|
+
{
|
|
938
|
+
css: result?.css,
|
|
939
|
+
},
|
|
940
|
+
],
|
|
941
|
+
};
|
|
942
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
catch (e) {
|
|
947
|
+
if (performance.now() - startTime > timeout) {
|
|
948
|
+
// throw e;
|
|
949
|
+
await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
async click(selectors, _params, options = {}, world = null) {
|
|
956
|
+
const state = {
|
|
957
|
+
selectors,
|
|
958
|
+
_params,
|
|
959
|
+
options,
|
|
960
|
+
world,
|
|
961
|
+
text: "Click element",
|
|
962
|
+
type: Types.CLICK,
|
|
963
|
+
operation: "click",
|
|
964
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
965
|
+
};
|
|
649
966
|
try {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
967
|
+
await _preCommand(state, this);
|
|
968
|
+
if (state.options && state.options.context) {
|
|
969
|
+
state.selectors.locators[0].text = state.options.context;
|
|
970
|
+
}
|
|
653
971
|
try {
|
|
654
|
-
await
|
|
655
|
-
await
|
|
656
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
972
|
+
await state.element.click();
|
|
973
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
657
974
|
}
|
|
658
975
|
catch (e) {
|
|
659
976
|
// await this.closeUnexpectedPopups();
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
await
|
|
663
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
977
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
978
|
+
await state.element.dispatchEvent("click");
|
|
979
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
664
980
|
}
|
|
665
981
|
await this.waitForPageLoad();
|
|
666
|
-
return info;
|
|
982
|
+
return state.info;
|
|
667
983
|
}
|
|
668
984
|
catch (e) {
|
|
669
|
-
|
|
670
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
671
|
-
info.screenshotPath = screenshotPath;
|
|
672
|
-
Object.assign(e, { info: info });
|
|
673
|
-
error = e;
|
|
674
|
-
throw e;
|
|
985
|
+
await _commandError(state, e, this);
|
|
675
986
|
}
|
|
676
987
|
finally {
|
|
677
|
-
|
|
678
|
-
this._reportToWorld(world, {
|
|
679
|
-
element_name: selectors.element_name,
|
|
680
|
-
type: Types.CLICK,
|
|
681
|
-
text: `Click element`,
|
|
682
|
-
screenshotId,
|
|
683
|
-
result: error
|
|
684
|
-
? {
|
|
685
|
-
status: "FAILED",
|
|
686
|
-
startTime,
|
|
687
|
-
endTime,
|
|
688
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
689
|
-
}
|
|
690
|
-
: {
|
|
691
|
-
status: "PASSED",
|
|
692
|
-
startTime,
|
|
693
|
-
endTime,
|
|
694
|
-
},
|
|
695
|
-
info: info,
|
|
696
|
-
});
|
|
988
|
+
_commandFinally(state, this);
|
|
697
989
|
}
|
|
698
990
|
}
|
|
699
991
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
992
|
+
const state = {
|
|
993
|
+
selectors,
|
|
994
|
+
_params,
|
|
995
|
+
options,
|
|
996
|
+
world,
|
|
997
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
998
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
999
|
+
operation: "setCheck",
|
|
1000
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
1001
|
+
};
|
|
710
1002
|
try {
|
|
711
|
-
|
|
712
|
-
|
|
1003
|
+
await _preCommand(state, this);
|
|
1004
|
+
state.info.checked = checked;
|
|
1005
|
+
// let element = await this._locate(selectors, info, _params);
|
|
1006
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
713
1007
|
try {
|
|
714
|
-
await this._highlightElements(element);
|
|
715
|
-
await element.setChecked(checked
|
|
1008
|
+
// await this._highlightElements(element);
|
|
1009
|
+
await state.element.setChecked(checked);
|
|
716
1010
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
717
1011
|
}
|
|
718
1012
|
catch (e) {
|
|
@@ -721,179 +1015,108 @@ class StableBrowser {
|
|
|
721
1015
|
}
|
|
722
1016
|
else {
|
|
723
1017
|
//await this.closeUnexpectedPopups();
|
|
724
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
725
|
-
element = await this._locate(selectors, info, _params);
|
|
726
|
-
await element.setChecked(checked, { timeout: 5000, force: true });
|
|
1018
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
1019
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
1020
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
727
1021
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
728
1022
|
}
|
|
729
1023
|
}
|
|
730
1024
|
await this.waitForPageLoad();
|
|
731
|
-
return info;
|
|
1025
|
+
return state.info;
|
|
732
1026
|
}
|
|
733
1027
|
catch (e) {
|
|
734
|
-
|
|
735
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
736
|
-
info.screenshotPath = screenshotPath;
|
|
737
|
-
Object.assign(e, { info: info });
|
|
738
|
-
error = e;
|
|
739
|
-
throw e;
|
|
1028
|
+
await _commandError(state, e, this);
|
|
740
1029
|
}
|
|
741
1030
|
finally {
|
|
742
|
-
|
|
743
|
-
this._reportToWorld(world, {
|
|
744
|
-
element_name: selectors.element_name,
|
|
745
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
746
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
747
|
-
screenshotId,
|
|
748
|
-
result: error
|
|
749
|
-
? {
|
|
750
|
-
status: "FAILED",
|
|
751
|
-
startTime,
|
|
752
|
-
endTime,
|
|
753
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
754
|
-
}
|
|
755
|
-
: {
|
|
756
|
-
status: "PASSED",
|
|
757
|
-
startTime,
|
|
758
|
-
endTime,
|
|
759
|
-
},
|
|
760
|
-
info: info,
|
|
761
|
-
});
|
|
1031
|
+
_commandFinally(state, this);
|
|
762
1032
|
}
|
|
763
1033
|
}
|
|
764
1034
|
async hover(selectors, _params, options = {}, world = null) {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1035
|
+
const state = {
|
|
1036
|
+
selectors,
|
|
1037
|
+
_params,
|
|
1038
|
+
options,
|
|
1039
|
+
world,
|
|
1040
|
+
type: Types.HOVER,
|
|
1041
|
+
text: `Hover element`,
|
|
1042
|
+
operation: "hover",
|
|
1043
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1044
|
+
};
|
|
774
1045
|
try {
|
|
775
|
-
|
|
776
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1046
|
+
await _preCommand(state, this);
|
|
777
1047
|
try {
|
|
778
|
-
await
|
|
779
|
-
await element.hover({ timeout: 10000 });
|
|
1048
|
+
await state.element.hover();
|
|
780
1049
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
781
1050
|
}
|
|
782
1051
|
catch (e) {
|
|
783
1052
|
//await this.closeUnexpectedPopups();
|
|
784
|
-
info.log += "hover failed, will try again" + "\n";
|
|
785
|
-
element = await this._locate(selectors, info, _params);
|
|
786
|
-
await element.hover({ timeout: 10000 });
|
|
1053
|
+
state.info.log += "hover failed, will try again" + "\n";
|
|
1054
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
1055
|
+
await state.element.hover({ timeout: 10000 });
|
|
787
1056
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
788
1057
|
}
|
|
789
1058
|
await this.waitForPageLoad();
|
|
790
|
-
return info;
|
|
1059
|
+
return state.info;
|
|
791
1060
|
}
|
|
792
1061
|
catch (e) {
|
|
793
|
-
|
|
794
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
795
|
-
info.screenshotPath = screenshotPath;
|
|
796
|
-
Object.assign(e, { info: info });
|
|
797
|
-
error = e;
|
|
798
|
-
throw e;
|
|
1062
|
+
await _commandError(state, e, this);
|
|
799
1063
|
}
|
|
800
1064
|
finally {
|
|
801
|
-
|
|
802
|
-
this._reportToWorld(world, {
|
|
803
|
-
element_name: selectors.element_name,
|
|
804
|
-
type: Types.HOVER,
|
|
805
|
-
text: `Hover element`,
|
|
806
|
-
screenshotId,
|
|
807
|
-
result: error
|
|
808
|
-
? {
|
|
809
|
-
status: "FAILED",
|
|
810
|
-
startTime,
|
|
811
|
-
endTime,
|
|
812
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
813
|
-
}
|
|
814
|
-
: {
|
|
815
|
-
status: "PASSED",
|
|
816
|
-
startTime,
|
|
817
|
-
endTime,
|
|
818
|
-
},
|
|
819
|
-
info: info,
|
|
820
|
-
});
|
|
1065
|
+
_commandFinally(state, this);
|
|
821
1066
|
}
|
|
822
1067
|
}
|
|
823
1068
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
824
|
-
this._validateSelectors(selectors);
|
|
825
1069
|
if (!values) {
|
|
826
1070
|
throw new Error("values is null");
|
|
827
1071
|
}
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1072
|
+
const state = {
|
|
1073
|
+
selectors,
|
|
1074
|
+
_params,
|
|
1075
|
+
options,
|
|
1076
|
+
world,
|
|
1077
|
+
value: values.toString(),
|
|
1078
|
+
type: Types.SELECT,
|
|
1079
|
+
text: `Select option: ${values}`,
|
|
1080
|
+
operation: "selectOption",
|
|
1081
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1082
|
+
};
|
|
836
1083
|
try {
|
|
837
|
-
|
|
838
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1084
|
+
await _preCommand(state, this);
|
|
839
1085
|
try {
|
|
840
|
-
await
|
|
841
|
-
await element.selectOption(values, { timeout: 5000 });
|
|
1086
|
+
await state.element.selectOption(values);
|
|
842
1087
|
}
|
|
843
1088
|
catch (e) {
|
|
844
1089
|
//await this.closeUnexpectedPopups();
|
|
845
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
846
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1090
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1091
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
847
1092
|
}
|
|
848
1093
|
await this.waitForPageLoad();
|
|
849
|
-
return info;
|
|
1094
|
+
return state.info;
|
|
850
1095
|
}
|
|
851
1096
|
catch (e) {
|
|
852
|
-
|
|
853
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
854
|
-
info.screenshotPath = screenshotPath;
|
|
855
|
-
Object.assign(e, { info: info });
|
|
856
|
-
this.logger.info("click failed, will try next selector");
|
|
857
|
-
error = e;
|
|
858
|
-
throw e;
|
|
1097
|
+
await _commandError(state, e, this);
|
|
859
1098
|
}
|
|
860
1099
|
finally {
|
|
861
|
-
|
|
862
|
-
this._reportToWorld(world, {
|
|
863
|
-
element_name: selectors.element_name,
|
|
864
|
-
type: Types.SELECT,
|
|
865
|
-
text: `Select option: ${values}`,
|
|
866
|
-
value: values.toString(),
|
|
867
|
-
screenshotId,
|
|
868
|
-
result: error
|
|
869
|
-
? {
|
|
870
|
-
status: "FAILED",
|
|
871
|
-
startTime,
|
|
872
|
-
endTime,
|
|
873
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
874
|
-
}
|
|
875
|
-
: {
|
|
876
|
-
status: "PASSED",
|
|
877
|
-
startTime,
|
|
878
|
-
endTime,
|
|
879
|
-
},
|
|
880
|
-
info: info,
|
|
881
|
-
});
|
|
1100
|
+
_commandFinally(state, this);
|
|
882
1101
|
}
|
|
883
1102
|
}
|
|
884
1103
|
async type(_value, _params = null, options = {}, world = null) {
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1104
|
+
const state = {
|
|
1105
|
+
value: _value,
|
|
1106
|
+
_params,
|
|
1107
|
+
options,
|
|
1108
|
+
world,
|
|
1109
|
+
locate: false,
|
|
1110
|
+
scroll: false,
|
|
1111
|
+
highlight: false,
|
|
1112
|
+
type: Types.TYPE_PRESS,
|
|
1113
|
+
text: `Type value: ${_value}`,
|
|
1114
|
+
operation: "type",
|
|
1115
|
+
log: "",
|
|
1116
|
+
};
|
|
894
1117
|
try {
|
|
895
|
-
|
|
896
|
-
const valueSegment =
|
|
1118
|
+
await _preCommand(state, this);
|
|
1119
|
+
const valueSegment = state.value.split("&&");
|
|
897
1120
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
898
1121
|
if (i > 0) {
|
|
899
1122
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -913,68 +1136,76 @@ class StableBrowser {
|
|
|
913
1136
|
await this.page.keyboard.type(value);
|
|
914
1137
|
}
|
|
915
1138
|
}
|
|
916
|
-
return info;
|
|
1139
|
+
return state.info;
|
|
917
1140
|
}
|
|
918
1141
|
catch (e) {
|
|
919
|
-
|
|
920
|
-
this.logger.error("type failed " + info.log);
|
|
921
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
922
|
-
info.screenshotPath = screenshotPath;
|
|
923
|
-
Object.assign(e, { info: info });
|
|
924
|
-
error = e;
|
|
925
|
-
throw e;
|
|
1142
|
+
await _commandError(state, e, this);
|
|
926
1143
|
}
|
|
927
1144
|
finally {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1145
|
+
_commandFinally(state, this);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1149
|
+
const state = {
|
|
1150
|
+
selectors,
|
|
1151
|
+
_params,
|
|
1152
|
+
value,
|
|
1153
|
+
options,
|
|
1154
|
+
world,
|
|
1155
|
+
type: Types.SET_INPUT,
|
|
1156
|
+
text: `Set input value`,
|
|
1157
|
+
operation: "setInputValue",
|
|
1158
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1159
|
+
};
|
|
1160
|
+
try {
|
|
1161
|
+
await _preCommand(state, this);
|
|
1162
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1163
|
+
try {
|
|
1164
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1165
|
+
el.value = value;
|
|
1166
|
+
}, value);
|
|
1167
|
+
}
|
|
1168
|
+
catch (error) {
|
|
1169
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1170
|
+
await _screenshot(state, this);
|
|
1171
|
+
Object.assign(error, { info: state.info });
|
|
1172
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1173
|
+
el.value = value;
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
catch (e) {
|
|
1178
|
+
await _commandError(state, e, this);
|
|
1179
|
+
}
|
|
1180
|
+
finally {
|
|
1181
|
+
_commandFinally(state, this);
|
|
948
1182
|
}
|
|
949
1183
|
}
|
|
950
1184
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1185
|
+
const state = {
|
|
1186
|
+
selectors,
|
|
1187
|
+
_params,
|
|
1188
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1189
|
+
options,
|
|
1190
|
+
world,
|
|
1191
|
+
type: Types.SET_DATE_TIME,
|
|
1192
|
+
text: `Set date time value: ${value}`,
|
|
1193
|
+
operation: "setDateTime",
|
|
1194
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1195
|
+
throwError: false,
|
|
1196
|
+
};
|
|
961
1197
|
try {
|
|
962
|
-
|
|
963
|
-
let element = await this._locate(selectors, info, _params);
|
|
964
|
-
//insert red border around the element
|
|
965
|
-
await this.scrollIfNeeded(element, info);
|
|
966
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
967
|
-
await this._highlightElements(element);
|
|
1198
|
+
await _preCommand(state, this);
|
|
968
1199
|
try {
|
|
969
|
-
await element.click();
|
|
1200
|
+
await state.element.click();
|
|
970
1201
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
971
1202
|
if (format) {
|
|
972
|
-
value = dayjs(value).format(format);
|
|
973
|
-
await element.fill(value);
|
|
1203
|
+
state.value = dayjs(state.value).format(format);
|
|
1204
|
+
await state.element.fill(state.value);
|
|
974
1205
|
}
|
|
975
1206
|
else {
|
|
976
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
977
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1207
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1208
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
978
1209
|
el.value = ""; // clear input
|
|
979
1210
|
el.value = dateTimeValue;
|
|
980
1211
|
}, dateTimeValue);
|
|
@@ -985,21 +1216,21 @@ class StableBrowser {
|
|
|
985
1216
|
await this.waitForPageLoad();
|
|
986
1217
|
}
|
|
987
1218
|
}
|
|
988
|
-
catch (
|
|
1219
|
+
catch (err) {
|
|
989
1220
|
//await this.closeUnexpectedPopups();
|
|
990
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
991
|
-
this.logger.info("Trying again")
|
|
992
|
-
|
|
993
|
-
Object.assign(
|
|
1221
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1222
|
+
this.logger.info("Trying again");
|
|
1223
|
+
await _screenshot(state, this);
|
|
1224
|
+
Object.assign(err, { info: state.info });
|
|
994
1225
|
await element.click();
|
|
995
1226
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
996
1227
|
if (format) {
|
|
997
|
-
value = dayjs(value).format(format);
|
|
998
|
-
await element.fill(value);
|
|
1228
|
+
state.value = dayjs(state.value).format(format);
|
|
1229
|
+
await state.element.fill(state.value);
|
|
999
1230
|
}
|
|
1000
1231
|
else {
|
|
1001
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1002
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1232
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1233
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1003
1234
|
el.value = ""; // clear input
|
|
1004
1235
|
el.value = dateTimeValue;
|
|
1005
1236
|
}, dateTimeValue);
|
|
@@ -1011,130 +1242,40 @@ class StableBrowser {
|
|
|
1011
1242
|
}
|
|
1012
1243
|
}
|
|
1013
1244
|
}
|
|
1014
|
-
catch (
|
|
1015
|
-
|
|
1016
|
-
throw e;
|
|
1017
|
-
}
|
|
1018
|
-
finally {
|
|
1019
|
-
const endTime = Date.now();
|
|
1020
|
-
this._reportToWorld(world, {
|
|
1021
|
-
element_name: selectors.element_name,
|
|
1022
|
-
type: Types.SET_DATE_TIME,
|
|
1023
|
-
screenshotId,
|
|
1024
|
-
value: value,
|
|
1025
|
-
text: `setDateTime input with value: ${value}`,
|
|
1026
|
-
result: error
|
|
1027
|
-
? {
|
|
1028
|
-
status: "FAILED",
|
|
1029
|
-
startTime,
|
|
1030
|
-
endTime,
|
|
1031
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1032
|
-
}
|
|
1033
|
-
: {
|
|
1034
|
-
status: "PASSED",
|
|
1035
|
-
startTime,
|
|
1036
|
-
endTime,
|
|
1037
|
-
},
|
|
1038
|
-
info: info,
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1043
|
-
this._validateSelectors(selectors);
|
|
1044
|
-
const startTime = Date.now();
|
|
1045
|
-
let error = null;
|
|
1046
|
-
let screenshotId = null;
|
|
1047
|
-
let screenshotPath = null;
|
|
1048
|
-
const info = {};
|
|
1049
|
-
info.log = "";
|
|
1050
|
-
info.operation = Types.SET_DATE_TIME;
|
|
1051
|
-
info.selectors = selectors;
|
|
1052
|
-
info.value = value;
|
|
1053
|
-
try {
|
|
1054
|
-
let element = await this._locate(selectors, info, _params);
|
|
1055
|
-
//insert red border around the element
|
|
1056
|
-
await this.scrollIfNeeded(element, info);
|
|
1057
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1058
|
-
await this._highlightElements(element);
|
|
1059
|
-
try {
|
|
1060
|
-
await element.click();
|
|
1061
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1062
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1063
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1064
|
-
el.value = ""; // clear input
|
|
1065
|
-
el.value = dateTimeValue;
|
|
1066
|
-
}, dateTimeValue);
|
|
1067
|
-
}
|
|
1068
|
-
catch (error) {
|
|
1069
|
-
//await this.closeUnexpectedPopups();
|
|
1070
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1071
|
-
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
1072
|
-
info.screenshotPath = screenshotPath;
|
|
1073
|
-
Object.assign(error, { info: info });
|
|
1074
|
-
await element.click();
|
|
1075
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1076
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1077
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1078
|
-
el.value = ""; // clear input
|
|
1079
|
-
el.value = dateTimeValue;
|
|
1080
|
-
}, dateTimeValue);
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
catch (error) {
|
|
1084
|
-
error = e;
|
|
1085
|
-
throw e;
|
|
1245
|
+
catch (e) {
|
|
1246
|
+
await _commandError(state, e, this);
|
|
1086
1247
|
}
|
|
1087
1248
|
finally {
|
|
1088
|
-
|
|
1089
|
-
this._reportToWorld(world, {
|
|
1090
|
-
element_name: selectors.element_name,
|
|
1091
|
-
type: Types.SET_DATE_TIME,
|
|
1092
|
-
screenshotId,
|
|
1093
|
-
value: value,
|
|
1094
|
-
text: `setDateTime input with value: ${value}`,
|
|
1095
|
-
result: error
|
|
1096
|
-
? {
|
|
1097
|
-
status: "FAILED",
|
|
1098
|
-
startTime,
|
|
1099
|
-
endTime,
|
|
1100
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1101
|
-
}
|
|
1102
|
-
: {
|
|
1103
|
-
status: "PASSED",
|
|
1104
|
-
startTime,
|
|
1105
|
-
endTime,
|
|
1106
|
-
},
|
|
1107
|
-
info: info,
|
|
1108
|
-
});
|
|
1249
|
+
_commandFinally(state, this);
|
|
1109
1250
|
}
|
|
1110
1251
|
}
|
|
1111
1252
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1112
|
-
|
|
1113
|
-
const startTime = Date.now();
|
|
1114
|
-
let error = null;
|
|
1115
|
-
let screenshotId = null;
|
|
1116
|
-
let screenshotPath = null;
|
|
1117
|
-
const info = {};
|
|
1118
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1119
|
-
info.operation = "clickType";
|
|
1120
|
-
info.selectors = selectors;
|
|
1253
|
+
_value = unEscapeString(_value);
|
|
1121
1254
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1255
|
+
const state = {
|
|
1256
|
+
selectors,
|
|
1257
|
+
_params,
|
|
1258
|
+
value: newValue,
|
|
1259
|
+
originalValue: _value,
|
|
1260
|
+
options,
|
|
1261
|
+
world,
|
|
1262
|
+
type: Types.FILL,
|
|
1263
|
+
text: `Click type input with value: ${_value}`,
|
|
1264
|
+
operation: "clickType",
|
|
1265
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1266
|
+
};
|
|
1122
1267
|
if (newValue !== _value) {
|
|
1123
1268
|
//this.logger.info(_value + "=" + newValue);
|
|
1124
1269
|
_value = newValue;
|
|
1125
1270
|
}
|
|
1126
|
-
info.value = _value;
|
|
1127
1271
|
try {
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
await this.scrollIfNeeded(element, info);
|
|
1131
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1132
|
-
await this._highlightElements(element);
|
|
1272
|
+
await _preCommand(state, this);
|
|
1273
|
+
state.info.value = _value;
|
|
1133
1274
|
if (options === null || options === undefined || !options.press) {
|
|
1134
1275
|
try {
|
|
1135
|
-
let currentValue = await element.inputValue();
|
|
1276
|
+
let currentValue = await state.element.inputValue();
|
|
1136
1277
|
if (currentValue) {
|
|
1137
|
-
await element.fill("");
|
|
1278
|
+
await state.element.fill("");
|
|
1138
1279
|
}
|
|
1139
1280
|
}
|
|
1140
1281
|
catch (e) {
|
|
@@ -1143,22 +1284,22 @@ class StableBrowser {
|
|
|
1143
1284
|
}
|
|
1144
1285
|
if (options === null || options === undefined || options.press) {
|
|
1145
1286
|
try {
|
|
1146
|
-
await element.click({ timeout: 5000 });
|
|
1287
|
+
await state.element.click({ timeout: 5000 });
|
|
1147
1288
|
}
|
|
1148
1289
|
catch (e) {
|
|
1149
|
-
await element.dispatchEvent("click");
|
|
1290
|
+
await state.element.dispatchEvent("click");
|
|
1150
1291
|
}
|
|
1151
1292
|
}
|
|
1152
1293
|
else {
|
|
1153
1294
|
try {
|
|
1154
|
-
await element.focus();
|
|
1295
|
+
await state.element.focus();
|
|
1155
1296
|
}
|
|
1156
1297
|
catch (e) {
|
|
1157
|
-
await element.dispatchEvent("focus");
|
|
1298
|
+
await state.element.dispatchEvent("focus");
|
|
1158
1299
|
}
|
|
1159
1300
|
}
|
|
1160
1301
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1161
|
-
const valueSegment =
|
|
1302
|
+
const valueSegment = state.value.split("&&");
|
|
1162
1303
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1163
1304
|
if (i > 0) {
|
|
1164
1305
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1178,13 +1319,14 @@ class StableBrowser {
|
|
|
1178
1319
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1179
1320
|
}
|
|
1180
1321
|
}
|
|
1322
|
+
await _screenshot(state, this);
|
|
1181
1323
|
if (enter === true) {
|
|
1182
1324
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1183
1325
|
await this.page.keyboard.press("Enter");
|
|
1184
1326
|
await this.waitForPageLoad();
|
|
1185
1327
|
}
|
|
1186
1328
|
else if (enter === false) {
|
|
1187
|
-
await element.dispatchEvent("change");
|
|
1329
|
+
await state.element.dispatchEvent("change");
|
|
1188
1330
|
//await this.page.keyboard.press("Tab");
|
|
1189
1331
|
}
|
|
1190
1332
|
else {
|
|
@@ -1193,103 +1335,50 @@ class StableBrowser {
|
|
|
1193
1335
|
await this.waitForPageLoad();
|
|
1194
1336
|
}
|
|
1195
1337
|
}
|
|
1196
|
-
return info;
|
|
1338
|
+
return state.info;
|
|
1197
1339
|
}
|
|
1198
1340
|
catch (e) {
|
|
1199
|
-
|
|
1200
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1201
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1202
|
-
info.screenshotPath = screenshotPath;
|
|
1203
|
-
Object.assign(e, { info: info });
|
|
1204
|
-
error = e;
|
|
1205
|
-
throw e;
|
|
1341
|
+
await _commandError(state, e, this);
|
|
1206
1342
|
}
|
|
1207
1343
|
finally {
|
|
1208
|
-
|
|
1209
|
-
this._reportToWorld(world, {
|
|
1210
|
-
element_name: selectors.element_name,
|
|
1211
|
-
type: Types.FILL,
|
|
1212
|
-
screenshotId,
|
|
1213
|
-
value: _value,
|
|
1214
|
-
text: `clickType input with value: ${_value}`,
|
|
1215
|
-
result: error
|
|
1216
|
-
? {
|
|
1217
|
-
status: "FAILED",
|
|
1218
|
-
startTime,
|
|
1219
|
-
endTime,
|
|
1220
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1221
|
-
}
|
|
1222
|
-
: {
|
|
1223
|
-
status: "PASSED",
|
|
1224
|
-
startTime,
|
|
1225
|
-
endTime,
|
|
1226
|
-
},
|
|
1227
|
-
info: info,
|
|
1228
|
-
});
|
|
1344
|
+
_commandFinally(state, this);
|
|
1229
1345
|
}
|
|
1230
1346
|
}
|
|
1231
1347
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1348
|
+
const state = {
|
|
1349
|
+
selectors,
|
|
1350
|
+
_params,
|
|
1351
|
+
value: unEscapeString(value),
|
|
1352
|
+
options,
|
|
1353
|
+
world,
|
|
1354
|
+
type: Types.FILL,
|
|
1355
|
+
text: `Fill input with value: ${value}`,
|
|
1356
|
+
operation: "fill",
|
|
1357
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1358
|
+
};
|
|
1242
1359
|
try {
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
await
|
|
1246
|
-
await element.fill(value, { timeout: 10000 });
|
|
1247
|
-
await element.dispatchEvent("change");
|
|
1360
|
+
await _preCommand(state, this);
|
|
1361
|
+
await state.element.fill(value);
|
|
1362
|
+
await state.element.dispatchEvent("change");
|
|
1248
1363
|
if (enter) {
|
|
1249
1364
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1250
1365
|
await this.page.keyboard.press("Enter");
|
|
1251
1366
|
}
|
|
1252
1367
|
await this.waitForPageLoad();
|
|
1253
|
-
return info;
|
|
1368
|
+
return state.info;
|
|
1254
1369
|
}
|
|
1255
1370
|
catch (e) {
|
|
1256
|
-
|
|
1257
|
-
this.logger.error("fill failed " + info.log);
|
|
1258
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1259
|
-
info.screenshotPath = screenshotPath;
|
|
1260
|
-
Object.assign(e, { info: info });
|
|
1261
|
-
error = e;
|
|
1262
|
-
throw e;
|
|
1371
|
+
await _commandError(state, e, this);
|
|
1263
1372
|
}
|
|
1264
1373
|
finally {
|
|
1265
|
-
|
|
1266
|
-
this._reportToWorld(world, {
|
|
1267
|
-
element_name: selectors.element_name,
|
|
1268
|
-
type: Types.FILL,
|
|
1269
|
-
screenshotId,
|
|
1270
|
-
value,
|
|
1271
|
-
text: `Fill input with value: ${value}`,
|
|
1272
|
-
result: error
|
|
1273
|
-
? {
|
|
1274
|
-
status: "FAILED",
|
|
1275
|
-
startTime,
|
|
1276
|
-
endTime,
|
|
1277
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1278
|
-
}
|
|
1279
|
-
: {
|
|
1280
|
-
status: "PASSED",
|
|
1281
|
-
startTime,
|
|
1282
|
-
endTime,
|
|
1283
|
-
},
|
|
1284
|
-
info: info,
|
|
1285
|
-
});
|
|
1374
|
+
_commandFinally(state, this);
|
|
1286
1375
|
}
|
|
1287
1376
|
}
|
|
1288
1377
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1289
1378
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1290
1379
|
}
|
|
1291
1380
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1292
|
-
|
|
1381
|
+
_validateSelectors(selectors);
|
|
1293
1382
|
let screenshotId = null;
|
|
1294
1383
|
let screenshotPath = null;
|
|
1295
1384
|
if (!info.log) {
|
|
@@ -1333,165 +1422,124 @@ class StableBrowser {
|
|
|
1333
1422
|
}
|
|
1334
1423
|
}
|
|
1335
1424
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1336
|
-
var _a;
|
|
1337
|
-
this._validateSelectors(selectors);
|
|
1338
1425
|
if (!pattern) {
|
|
1339
1426
|
throw new Error("pattern is null");
|
|
1340
1427
|
}
|
|
1341
1428
|
if (!text) {
|
|
1342
1429
|
throw new Error("text is null");
|
|
1343
1430
|
}
|
|
1431
|
+
const state = {
|
|
1432
|
+
selectors,
|
|
1433
|
+
_params,
|
|
1434
|
+
pattern,
|
|
1435
|
+
value: pattern,
|
|
1436
|
+
options,
|
|
1437
|
+
world,
|
|
1438
|
+
locate: false,
|
|
1439
|
+
scroll: false,
|
|
1440
|
+
screenshot: false,
|
|
1441
|
+
highlight: false,
|
|
1442
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1443
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1444
|
+
operation: "containsPattern",
|
|
1445
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1446
|
+
};
|
|
1344
1447
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1345
1448
|
if (newValue !== text) {
|
|
1346
1449
|
this.logger.info(text + "=" + newValue);
|
|
1347
1450
|
text = newValue;
|
|
1348
1451
|
}
|
|
1349
|
-
const startTime = Date.now();
|
|
1350
|
-
let error = null;
|
|
1351
|
-
let screenshotId = null;
|
|
1352
|
-
let screenshotPath = null;
|
|
1353
|
-
const info = {};
|
|
1354
|
-
info.log =
|
|
1355
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1356
|
-
info.operation = "containsPattern";
|
|
1357
|
-
info.selectors = selectors;
|
|
1358
|
-
info.value = text;
|
|
1359
|
-
info.pattern = pattern;
|
|
1360
1452
|
let foundObj = null;
|
|
1361
1453
|
try {
|
|
1362
|
-
|
|
1454
|
+
await _preCommand(state, this);
|
|
1455
|
+
state.info.pattern = pattern;
|
|
1456
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1363
1457
|
if (foundObj && foundObj.element) {
|
|
1364
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1458
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1365
1459
|
}
|
|
1366
|
-
|
|
1460
|
+
await _screenshot(state, this);
|
|
1367
1461
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1368
1462
|
pattern = pattern.replace("{text}", escapedText);
|
|
1369
1463
|
let regex = new RegExp(pattern, "im");
|
|
1370
|
-
if (!regex.test(foundObj
|
|
1371
|
-
info.foundText = foundObj
|
|
1464
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1465
|
+
state.info.foundText = foundObj?.text;
|
|
1372
1466
|
throw new Error("element doesn't contain text " + text);
|
|
1373
1467
|
}
|
|
1374
|
-
return info;
|
|
1468
|
+
return state.info;
|
|
1375
1469
|
}
|
|
1376
1470
|
catch (e) {
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1380
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1381
|
-
info.screenshotPath = screenshotPath;
|
|
1382
|
-
Object.assign(e, { info: info });
|
|
1383
|
-
error = e;
|
|
1384
|
-
throw e;
|
|
1471
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1472
|
+
await _commandError(state, e, this);
|
|
1385
1473
|
}
|
|
1386
1474
|
finally {
|
|
1387
|
-
|
|
1388
|
-
this._reportToWorld(world, {
|
|
1389
|
-
element_name: selectors.element_name,
|
|
1390
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1391
|
-
value: pattern,
|
|
1392
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1393
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1394
|
-
result: error
|
|
1395
|
-
? {
|
|
1396
|
-
status: "FAILED",
|
|
1397
|
-
startTime,
|
|
1398
|
-
endTime,
|
|
1399
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1400
|
-
}
|
|
1401
|
-
: {
|
|
1402
|
-
status: "PASSED",
|
|
1403
|
-
startTime,
|
|
1404
|
-
endTime,
|
|
1405
|
-
},
|
|
1406
|
-
info: info,
|
|
1407
|
-
});
|
|
1475
|
+
_commandFinally(state, this);
|
|
1408
1476
|
}
|
|
1409
1477
|
}
|
|
1410
1478
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1411
|
-
|
|
1412
|
-
|
|
1479
|
+
const state = {
|
|
1480
|
+
selectors,
|
|
1481
|
+
_params,
|
|
1482
|
+
value: text,
|
|
1483
|
+
options,
|
|
1484
|
+
world,
|
|
1485
|
+
locate: false,
|
|
1486
|
+
scroll: false,
|
|
1487
|
+
screenshot: false,
|
|
1488
|
+
highlight: false,
|
|
1489
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1490
|
+
text: `Verify element contains text: ${text}`,
|
|
1491
|
+
operation: "containsText",
|
|
1492
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1493
|
+
};
|
|
1413
1494
|
if (!text) {
|
|
1414
1495
|
throw new Error("text is null");
|
|
1415
1496
|
}
|
|
1416
|
-
|
|
1417
|
-
let error = null;
|
|
1418
|
-
let screenshotId = null;
|
|
1419
|
-
let screenshotPath = null;
|
|
1420
|
-
const info = {};
|
|
1421
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1422
|
-
info.operation = "containsText";
|
|
1423
|
-
info.selectors = selectors;
|
|
1497
|
+
text = unEscapeString(text);
|
|
1424
1498
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1425
1499
|
if (newValue !== text) {
|
|
1426
1500
|
this.logger.info(text + "=" + newValue);
|
|
1427
1501
|
text = newValue;
|
|
1428
1502
|
}
|
|
1429
|
-
info.value = text;
|
|
1430
1503
|
let foundObj = null;
|
|
1431
1504
|
try {
|
|
1432
|
-
|
|
1505
|
+
await _preCommand(state, this);
|
|
1506
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1433
1507
|
if (foundObj && foundObj.element) {
|
|
1434
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1508
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1435
1509
|
}
|
|
1436
|
-
|
|
1510
|
+
await _screenshot(state, this);
|
|
1437
1511
|
const dateAlternatives = findDateAlternatives(text);
|
|
1438
1512
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1439
1513
|
if (dateAlternatives.date) {
|
|
1440
1514
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1441
|
-
if (
|
|
1442
|
-
|
|
1443
|
-
return info;
|
|
1515
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1516
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1517
|
+
return state.info;
|
|
1444
1518
|
}
|
|
1445
1519
|
}
|
|
1446
1520
|
throw new Error("element doesn't contain text " + text);
|
|
1447
1521
|
}
|
|
1448
1522
|
else if (numberAlternatives.number) {
|
|
1449
1523
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1450
|
-
if (
|
|
1451
|
-
|
|
1452
|
-
return info;
|
|
1524
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1525
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1526
|
+
return state.info;
|
|
1453
1527
|
}
|
|
1454
1528
|
}
|
|
1455
1529
|
throw new Error("element doesn't contain text " + text);
|
|
1456
1530
|
}
|
|
1457
|
-
else if (!
|
|
1458
|
-
info.foundText = foundObj
|
|
1459
|
-
info.value = foundObj
|
|
1531
|
+
else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
|
|
1532
|
+
state.info.foundText = foundObj?.text;
|
|
1533
|
+
state.info.value = foundObj?.value;
|
|
1460
1534
|
throw new Error("element doesn't contain text " + text);
|
|
1461
1535
|
}
|
|
1462
|
-
return info;
|
|
1536
|
+
return state.info;
|
|
1463
1537
|
}
|
|
1464
1538
|
catch (e) {
|
|
1465
|
-
|
|
1466
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1467
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1468
|
-
info.screenshotPath = screenshotPath;
|
|
1469
|
-
Object.assign(e, { info: info });
|
|
1470
|
-
error = e;
|
|
1471
|
-
throw e;
|
|
1539
|
+
await _commandError(state, e, this);
|
|
1472
1540
|
}
|
|
1473
1541
|
finally {
|
|
1474
|
-
|
|
1475
|
-
this._reportToWorld(world, {
|
|
1476
|
-
element_name: selectors.element_name,
|
|
1477
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1478
|
-
text: `Verify element contains text: ${text}`,
|
|
1479
|
-
value: text,
|
|
1480
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1481
|
-
result: error
|
|
1482
|
-
? {
|
|
1483
|
-
status: "FAILED",
|
|
1484
|
-
startTime,
|
|
1485
|
-
endTime,
|
|
1486
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1487
|
-
}
|
|
1488
|
-
: {
|
|
1489
|
-
status: "PASSED",
|
|
1490
|
-
startTime,
|
|
1491
|
-
endTime,
|
|
1492
|
-
},
|
|
1493
|
-
info: info,
|
|
1494
|
-
});
|
|
1542
|
+
_commandFinally(state, this);
|
|
1495
1543
|
}
|
|
1496
1544
|
}
|
|
1497
1545
|
_getDataFile(world = null) {
|
|
@@ -1510,6 +1558,29 @@ class StableBrowser {
|
|
|
1510
1558
|
}
|
|
1511
1559
|
return dataFile;
|
|
1512
1560
|
}
|
|
1561
|
+
async waitForUserInput(message, world = null) {
|
|
1562
|
+
if (!message) {
|
|
1563
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1564
|
+
}
|
|
1565
|
+
else {
|
|
1566
|
+
message = "# Wait for user input. " + message;
|
|
1567
|
+
}
|
|
1568
|
+
message += "\n";
|
|
1569
|
+
const value = await new Promise((resolve) => {
|
|
1570
|
+
const rl = readline.createInterface({
|
|
1571
|
+
input: process.stdin,
|
|
1572
|
+
output: process.stdout,
|
|
1573
|
+
});
|
|
1574
|
+
rl.question(message, (answer) => {
|
|
1575
|
+
rl.close();
|
|
1576
|
+
resolve(answer);
|
|
1577
|
+
});
|
|
1578
|
+
});
|
|
1579
|
+
if (value) {
|
|
1580
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1581
|
+
}
|
|
1582
|
+
this.setTestData({ userInput: value }, world);
|
|
1583
|
+
}
|
|
1513
1584
|
setTestData(testData, world = null) {
|
|
1514
1585
|
if (!testData) {
|
|
1515
1586
|
return;
|
|
@@ -1537,7 +1608,7 @@ class StableBrowser {
|
|
|
1537
1608
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1538
1609
|
const results = [];
|
|
1539
1610
|
return new Promise((resolve, reject) => {
|
|
1540
|
-
const readableStream = new
|
|
1611
|
+
const readableStream = new Readable();
|
|
1541
1612
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1542
1613
|
readableStream.push(data);
|
|
1543
1614
|
readableStream.push(null);
|
|
@@ -1650,11 +1721,9 @@ class StableBrowser {
|
|
|
1650
1721
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1651
1722
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1652
1723
|
}
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
}
|
|
1657
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1724
|
+
// to make sure the path doesn't start with -
|
|
1725
|
+
const uuidStr = "id_" + randomUUID();
|
|
1726
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1658
1727
|
try {
|
|
1659
1728
|
await this.takeScreenshot(screenshotPath);
|
|
1660
1729
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1668,7 +1737,7 @@ class StableBrowser {
|
|
|
1668
1737
|
catch (e) {
|
|
1669
1738
|
this.logger.info("unable to take screenshot, ignored");
|
|
1670
1739
|
}
|
|
1671
|
-
result.screenshotId =
|
|
1740
|
+
result.screenshotId = uuidStr;
|
|
1672
1741
|
result.screenshotPath = screenshotPath;
|
|
1673
1742
|
if (info && info.box) {
|
|
1674
1743
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1697,7 +1766,6 @@ class StableBrowser {
|
|
|
1697
1766
|
}
|
|
1698
1767
|
async takeScreenshot(screenshotPath) {
|
|
1699
1768
|
const playContext = this.context.playContext;
|
|
1700
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1701
1769
|
// Using CDP to capture the screenshot
|
|
1702
1770
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1703
1771
|
document.body.scrollWidth,
|
|
@@ -1707,164 +1775,105 @@ class StableBrowser {
|
|
|
1707
1775
|
document.body.clientWidth,
|
|
1708
1776
|
document.documentElement.clientWidth,
|
|
1709
1777
|
])));
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
await client.detach();
|
|
1778
|
+
let screenshotBuffer = null;
|
|
1779
|
+
if (this.context.browserName === "chromium") {
|
|
1780
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1781
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1782
|
+
format: "png",
|
|
1783
|
+
// clip: {
|
|
1784
|
+
// x: 0,
|
|
1785
|
+
// y: 0,
|
|
1786
|
+
// width: viewportWidth,
|
|
1787
|
+
// height: viewportHeight,
|
|
1788
|
+
// scale: 1,
|
|
1789
|
+
// },
|
|
1790
|
+
});
|
|
1791
|
+
await client.detach();
|
|
1792
|
+
if (!screenshotPath) {
|
|
1793
|
+
return data;
|
|
1794
|
+
}
|
|
1795
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1796
|
+
}
|
|
1797
|
+
else {
|
|
1798
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1799
|
+
}
|
|
1800
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1801
|
+
// Get the image dimensions
|
|
1802
|
+
const { width, height } = image.bitmap;
|
|
1803
|
+
const resizeRatio = viewportWidth / width;
|
|
1804
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1805
|
+
if (width > viewportWidth) {
|
|
1806
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1807
|
+
await image.write(screenshotPath);
|
|
1808
|
+
}
|
|
1809
|
+
else {
|
|
1810
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1811
|
+
}
|
|
1745
1812
|
}
|
|
1746
1813
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1814
|
+
const state = {
|
|
1815
|
+
selectors,
|
|
1816
|
+
_params,
|
|
1817
|
+
options,
|
|
1818
|
+
world,
|
|
1819
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1820
|
+
text: `Verify element exists in page`,
|
|
1821
|
+
operation: "verifyElementExistInPage",
|
|
1822
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1823
|
+
};
|
|
1752
1824
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1753
|
-
const info = {};
|
|
1754
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1755
|
-
info.operation = "verify";
|
|
1756
|
-
info.selectors = selectors;
|
|
1757
1825
|
try {
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
}
|
|
1762
|
-
await this._highlightElements(element);
|
|
1763
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1764
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1765
|
-
return info;
|
|
1826
|
+
await _preCommand(state, this);
|
|
1827
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1828
|
+
return state.info;
|
|
1766
1829
|
}
|
|
1767
1830
|
catch (e) {
|
|
1768
|
-
|
|
1769
|
-
this.logger.error("verify failed " + info.log);
|
|
1770
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1771
|
-
info.screenshotPath = screenshotPath;
|
|
1772
|
-
Object.assign(e, { info: info });
|
|
1773
|
-
error = e;
|
|
1774
|
-
throw e;
|
|
1831
|
+
await _commandError(state, e, this);
|
|
1775
1832
|
}
|
|
1776
1833
|
finally {
|
|
1777
|
-
|
|
1778
|
-
this._reportToWorld(world, {
|
|
1779
|
-
element_name: selectors.element_name,
|
|
1780
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1781
|
-
text: "Verify element exists in page",
|
|
1782
|
-
screenshotId,
|
|
1783
|
-
result: error
|
|
1784
|
-
? {
|
|
1785
|
-
status: "FAILED",
|
|
1786
|
-
startTime,
|
|
1787
|
-
endTime,
|
|
1788
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1789
|
-
}
|
|
1790
|
-
: {
|
|
1791
|
-
status: "PASSED",
|
|
1792
|
-
startTime,
|
|
1793
|
-
endTime,
|
|
1794
|
-
},
|
|
1795
|
-
info: info,
|
|
1796
|
-
});
|
|
1834
|
+
_commandFinally(state, this);
|
|
1797
1835
|
}
|
|
1798
1836
|
}
|
|
1799
1837
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1838
|
+
const state = {
|
|
1839
|
+
selectors,
|
|
1840
|
+
_params,
|
|
1841
|
+
attribute,
|
|
1842
|
+
variable,
|
|
1843
|
+
options,
|
|
1844
|
+
world,
|
|
1845
|
+
type: Types.EXTRACT,
|
|
1846
|
+
text: `Extract attribute from element`,
|
|
1847
|
+
operation: "extractAttribute",
|
|
1848
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1849
|
+
};
|
|
1805
1850
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1806
|
-
const info = {};
|
|
1807
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1808
|
-
info.operation = "extract";
|
|
1809
|
-
info.selectors = selectors;
|
|
1810
1851
|
try {
|
|
1811
|
-
|
|
1812
|
-
await this._highlightElements(element);
|
|
1813
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1852
|
+
await _preCommand(state, this);
|
|
1814
1853
|
switch (attribute) {
|
|
1815
1854
|
case "inner_text":
|
|
1816
|
-
|
|
1855
|
+
state.value = await state.element.innerText();
|
|
1817
1856
|
break;
|
|
1818
1857
|
case "href":
|
|
1819
|
-
|
|
1858
|
+
state.value = await state.element.getAttribute("href");
|
|
1820
1859
|
break;
|
|
1821
1860
|
case "value":
|
|
1822
|
-
|
|
1861
|
+
state.value = await state.element.inputValue();
|
|
1823
1862
|
break;
|
|
1824
1863
|
default:
|
|
1825
|
-
|
|
1864
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1826
1865
|
break;
|
|
1827
1866
|
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
this.setTestData({ [variable]: info.value }, world);
|
|
1833
|
-
this.logger.info("set test data: " + variable + "=" + info.value);
|
|
1834
|
-
return info;
|
|
1867
|
+
state.info.value = state.value;
|
|
1868
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1869
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1870
|
+
return state.info;
|
|
1835
1871
|
}
|
|
1836
1872
|
catch (e) {
|
|
1837
|
-
|
|
1838
|
-
this.logger.error("extract failed " + info.log);
|
|
1839
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1840
|
-
info.screenshotPath = screenshotPath;
|
|
1841
|
-
Object.assign(e, { info: info });
|
|
1842
|
-
error = e;
|
|
1843
|
-
throw e;
|
|
1873
|
+
await _commandError(state, e, this);
|
|
1844
1874
|
}
|
|
1845
1875
|
finally {
|
|
1846
|
-
|
|
1847
|
-
this._reportToWorld(world, {
|
|
1848
|
-
element_name: selectors.element_name,
|
|
1849
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1850
|
-
variable: variable,
|
|
1851
|
-
value: info.value,
|
|
1852
|
-
text: "Extract attribute from element",
|
|
1853
|
-
screenshotId,
|
|
1854
|
-
result: error
|
|
1855
|
-
? {
|
|
1856
|
-
status: "FAILED",
|
|
1857
|
-
startTime,
|
|
1858
|
-
endTime,
|
|
1859
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1860
|
-
}
|
|
1861
|
-
: {
|
|
1862
|
-
status: "PASSED",
|
|
1863
|
-
startTime,
|
|
1864
|
-
endTime,
|
|
1865
|
-
},
|
|
1866
|
-
info: info,
|
|
1867
|
-
});
|
|
1876
|
+
_commandFinally(state, this);
|
|
1868
1877
|
}
|
|
1869
1878
|
}
|
|
1870
1879
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1941,7 +1950,8 @@ class StableBrowser {
|
|
|
1941
1950
|
catch (e) {
|
|
1942
1951
|
errorCount++;
|
|
1943
1952
|
if (errorCount > 3) {
|
|
1944
|
-
throw e;
|
|
1953
|
+
// throw e;
|
|
1954
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1945
1955
|
}
|
|
1946
1956
|
// ignore
|
|
1947
1957
|
}
|
|
@@ -2052,11 +2062,12 @@ class StableBrowser {
|
|
|
2052
2062
|
info.screenshotPath = screenshotPath;
|
|
2053
2063
|
Object.assign(e, { info: info });
|
|
2054
2064
|
error = e;
|
|
2055
|
-
throw e;
|
|
2065
|
+
// throw e;
|
|
2066
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2056
2067
|
}
|
|
2057
2068
|
finally {
|
|
2058
2069
|
const endTime = Date.now();
|
|
2059
|
-
|
|
2070
|
+
_reportToWorld(world, {
|
|
2060
2071
|
type: Types.VERIFY_PAGE_PATH,
|
|
2061
2072
|
text: "Verify page path",
|
|
2062
2073
|
screenshotId,
|
|
@@ -2065,7 +2076,7 @@ class StableBrowser {
|
|
|
2065
2076
|
status: "FAILED",
|
|
2066
2077
|
startTime,
|
|
2067
2078
|
endTime,
|
|
2068
|
-
message: error
|
|
2079
|
+
message: error?.message,
|
|
2069
2080
|
}
|
|
2070
2081
|
: {
|
|
2071
2082
|
status: "PASSED",
|
|
@@ -2077,52 +2088,59 @@ class StableBrowser {
|
|
|
2077
2088
|
}
|
|
2078
2089
|
}
|
|
2079
2090
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2080
|
-
|
|
2091
|
+
text = unEscapeString(text);
|
|
2092
|
+
const state = {
|
|
2093
|
+
text_search: text,
|
|
2094
|
+
options,
|
|
2095
|
+
world,
|
|
2096
|
+
locate: false,
|
|
2097
|
+
scroll: false,
|
|
2098
|
+
highlight: false,
|
|
2099
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2100
|
+
text: `Verify text exists in page`,
|
|
2101
|
+
operation: "verifyTextExistInPage",
|
|
2102
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2103
|
+
};
|
|
2081
2104
|
const timeout = this._getLoadTimeout(options);
|
|
2082
|
-
let error = null;
|
|
2083
|
-
let screenshotId = null;
|
|
2084
|
-
let screenshotPath = null;
|
|
2085
2105
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2086
|
-
const info = {};
|
|
2087
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2088
|
-
info.operation = "verifyTextExistInPage";
|
|
2089
2106
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2090
2107
|
if (newValue !== text) {
|
|
2091
2108
|
this.logger.info(text + "=" + newValue);
|
|
2092
2109
|
text = newValue;
|
|
2093
2110
|
}
|
|
2094
|
-
info.text = text;
|
|
2095
2111
|
let dateAlternatives = findDateAlternatives(text);
|
|
2096
2112
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2097
2113
|
try {
|
|
2114
|
+
await _preCommand(state, this);
|
|
2115
|
+
state.info.text = text;
|
|
2098
2116
|
while (true) {
|
|
2099
2117
|
const frames = this.page.frames();
|
|
2100
2118
|
let results = [];
|
|
2101
2119
|
for (let i = 0; i < frames.length; i++) {
|
|
2102
2120
|
if (dateAlternatives.date) {
|
|
2103
2121
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2104
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "
|
|
2122
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2105
2123
|
result.frame = frames[i];
|
|
2106
2124
|
results.push(result);
|
|
2107
2125
|
}
|
|
2108
2126
|
}
|
|
2109
2127
|
else if (numberAlternatives.number) {
|
|
2110
2128
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2111
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "
|
|
2129
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2112
2130
|
result.frame = frames[i];
|
|
2113
2131
|
results.push(result);
|
|
2114
2132
|
}
|
|
2115
2133
|
}
|
|
2116
2134
|
else {
|
|
2117
|
-
const result = await this._locateElementByText(frames[i], text, "
|
|
2135
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2118
2136
|
result.frame = frames[i];
|
|
2119
2137
|
results.push(result);
|
|
2120
2138
|
}
|
|
2121
2139
|
}
|
|
2122
|
-
info.results = results;
|
|
2140
|
+
state.info.results = results;
|
|
2123
2141
|
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2124
2142
|
if (resultWithElementsFound.length === 0) {
|
|
2125
|
-
if (Date.now() - startTime > timeout) {
|
|
2143
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2126
2144
|
throw new Error(`Text ${text} not found in page`);
|
|
2127
2145
|
}
|
|
2128
2146
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2134,44 +2152,89 @@ class StableBrowser {
|
|
|
2134
2152
|
await this._highlightElements(frame, dataAttribute);
|
|
2135
2153
|
const element = await frame.$(dataAttribute);
|
|
2136
2154
|
if (element) {
|
|
2137
|
-
await this.scrollIfNeeded(element, info);
|
|
2155
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2138
2156
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2139
2157
|
}
|
|
2140
2158
|
}
|
|
2141
|
-
|
|
2142
|
-
return info;
|
|
2159
|
+
await _screenshot(state, this);
|
|
2160
|
+
return state.info;
|
|
2143
2161
|
}
|
|
2144
2162
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2145
2163
|
}
|
|
2146
2164
|
catch (e) {
|
|
2147
|
-
|
|
2148
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2149
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2150
|
-
info.screenshotPath = screenshotPath;
|
|
2151
|
-
Object.assign(e, { info: info });
|
|
2152
|
-
error = e;
|
|
2153
|
-
throw e;
|
|
2165
|
+
await _commandError(state, e, this);
|
|
2154
2166
|
}
|
|
2155
2167
|
finally {
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2168
|
+
_commandFinally(state, this);
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2172
|
+
text = unEscapeString(text);
|
|
2173
|
+
const state = {
|
|
2174
|
+
text_search: text,
|
|
2175
|
+
options,
|
|
2176
|
+
world,
|
|
2177
|
+
locate: false,
|
|
2178
|
+
scroll: false,
|
|
2179
|
+
highlight: false,
|
|
2180
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2181
|
+
text: `Verify text does not exist in page`,
|
|
2182
|
+
operation: "verifyTextNotExistInPage",
|
|
2183
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2184
|
+
};
|
|
2185
|
+
const timeout = this._getLoadTimeout(options);
|
|
2186
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2187
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2188
|
+
if (newValue !== text) {
|
|
2189
|
+
this.logger.info(text + "=" + newValue);
|
|
2190
|
+
text = newValue;
|
|
2191
|
+
}
|
|
2192
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2193
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2194
|
+
try {
|
|
2195
|
+
await _preCommand(state, this);
|
|
2196
|
+
state.info.text = text;
|
|
2197
|
+
while (true) {
|
|
2198
|
+
const frames = this.page.frames();
|
|
2199
|
+
let results = [];
|
|
2200
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2201
|
+
if (dateAlternatives.date) {
|
|
2202
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2203
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2204
|
+
result.frame = frames[i];
|
|
2205
|
+
results.push(result);
|
|
2206
|
+
}
|
|
2167
2207
|
}
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2208
|
+
else if (numberAlternatives.number) {
|
|
2209
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2210
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2211
|
+
result.frame = frames[i];
|
|
2212
|
+
results.push(result);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
else {
|
|
2216
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2217
|
+
result.frame = frames[i];
|
|
2218
|
+
results.push(result);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
state.info.results = results;
|
|
2222
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2223
|
+
if (resultWithElementsFound.length === 0) {
|
|
2224
|
+
await _screenshot(state, this);
|
|
2225
|
+
return state.info;
|
|
2226
|
+
}
|
|
2227
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2228
|
+
throw new Error(`Text ${text} found in page`);
|
|
2229
|
+
}
|
|
2230
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
catch (e) {
|
|
2234
|
+
await _commandError(state, e, this);
|
|
2235
|
+
}
|
|
2236
|
+
finally {
|
|
2237
|
+
_commandFinally(state, this);
|
|
2175
2238
|
}
|
|
2176
2239
|
}
|
|
2177
2240
|
_getServerUrl() {
|
|
@@ -2234,11 +2297,12 @@ class StableBrowser {
|
|
|
2234
2297
|
info.screenshotPath = screenshotPath;
|
|
2235
2298
|
Object.assign(e, { info: info });
|
|
2236
2299
|
error = e;
|
|
2237
|
-
throw e;
|
|
2300
|
+
// throw e;
|
|
2301
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2238
2302
|
}
|
|
2239
2303
|
finally {
|
|
2240
2304
|
const endTime = Date.now();
|
|
2241
|
-
|
|
2305
|
+
_reportToWorld(world, {
|
|
2242
2306
|
type: Types.VERIFY_VISUAL,
|
|
2243
2307
|
text: "Visual verification",
|
|
2244
2308
|
screenshotId,
|
|
@@ -2247,7 +2311,7 @@ class StableBrowser {
|
|
|
2247
2311
|
status: "FAILED",
|
|
2248
2312
|
startTime,
|
|
2249
2313
|
endTime,
|
|
2250
|
-
message: error
|
|
2314
|
+
message: error?.message,
|
|
2251
2315
|
}
|
|
2252
2316
|
: {
|
|
2253
2317
|
status: "PASSED",
|
|
@@ -2279,7 +2343,7 @@ class StableBrowser {
|
|
|
2279
2343
|
this.logger.info("Table data verified");
|
|
2280
2344
|
}
|
|
2281
2345
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2282
|
-
|
|
2346
|
+
_validateSelectors(selectors);
|
|
2283
2347
|
const startTime = Date.now();
|
|
2284
2348
|
let error = null;
|
|
2285
2349
|
let screenshotId = null;
|
|
@@ -2301,11 +2365,12 @@ class StableBrowser {
|
|
|
2301
2365
|
info.screenshotPath = screenshotPath;
|
|
2302
2366
|
Object.assign(e, { info: info });
|
|
2303
2367
|
error = e;
|
|
2304
|
-
throw e;
|
|
2368
|
+
// throw e;
|
|
2369
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2305
2370
|
}
|
|
2306
2371
|
finally {
|
|
2307
2372
|
const endTime = Date.now();
|
|
2308
|
-
|
|
2373
|
+
_reportToWorld(world, {
|
|
2309
2374
|
element_name: selectors.element_name,
|
|
2310
2375
|
type: Types.GET_TABLE_DATA,
|
|
2311
2376
|
text: "Get table data",
|
|
@@ -2315,7 +2380,7 @@ class StableBrowser {
|
|
|
2315
2380
|
status: "FAILED",
|
|
2316
2381
|
startTime,
|
|
2317
2382
|
endTime,
|
|
2318
|
-
message: error
|
|
2383
|
+
message: error?.message,
|
|
2319
2384
|
}
|
|
2320
2385
|
: {
|
|
2321
2386
|
status: "PASSED",
|
|
@@ -2327,7 +2392,7 @@ class StableBrowser {
|
|
|
2327
2392
|
}
|
|
2328
2393
|
}
|
|
2329
2394
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2330
|
-
|
|
2395
|
+
_validateSelectors(selectors);
|
|
2331
2396
|
if (!query) {
|
|
2332
2397
|
throw new Error("query is null");
|
|
2333
2398
|
}
|
|
@@ -2466,11 +2531,12 @@ class StableBrowser {
|
|
|
2466
2531
|
info.screenshotPath = screenshotPath;
|
|
2467
2532
|
Object.assign(e, { info: info });
|
|
2468
2533
|
error = e;
|
|
2469
|
-
throw e;
|
|
2534
|
+
// throw e;
|
|
2535
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2470
2536
|
}
|
|
2471
2537
|
finally {
|
|
2472
2538
|
const endTime = Date.now();
|
|
2473
|
-
|
|
2539
|
+
_reportToWorld(world, {
|
|
2474
2540
|
element_name: selectors.element_name,
|
|
2475
2541
|
type: Types.ANALYZE_TABLE,
|
|
2476
2542
|
text: "Analyze table",
|
|
@@ -2480,7 +2546,7 @@ class StableBrowser {
|
|
|
2480
2546
|
status: "FAILED",
|
|
2481
2547
|
startTime,
|
|
2482
2548
|
endTime,
|
|
2483
|
-
message: error
|
|
2549
|
+
message: error?.message,
|
|
2484
2550
|
}
|
|
2485
2551
|
: {
|
|
2486
2552
|
status: "PASSED",
|
|
@@ -2492,27 +2558,7 @@ class StableBrowser {
|
|
|
2492
2558
|
}
|
|
2493
2559
|
}
|
|
2494
2560
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2495
|
-
|
|
2496
|
-
return value;
|
|
2497
|
-
}
|
|
2498
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2499
|
-
let regex = /{{(.*?)}}/g;
|
|
2500
|
-
let matches = value.match(regex);
|
|
2501
|
-
if (matches) {
|
|
2502
|
-
const testData = this.getTestData(world);
|
|
2503
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2504
|
-
let match = matches[i];
|
|
2505
|
-
let key = match.substring(2, match.length - 2);
|
|
2506
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2507
|
-
if (newValue !== null) {
|
|
2508
|
-
value = value.replace(match, newValue);
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2513
|
-
return await decrypt(value, null, totpWait);
|
|
2514
|
-
}
|
|
2515
|
-
return value;
|
|
2561
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2516
2562
|
}
|
|
2517
2563
|
_getLoadTimeout(options) {
|
|
2518
2564
|
let timeout = 15000;
|
|
@@ -2549,13 +2595,13 @@ class StableBrowser {
|
|
|
2549
2595
|
}
|
|
2550
2596
|
catch (e) {
|
|
2551
2597
|
if (e.label === "networkidle") {
|
|
2552
|
-
console.log("
|
|
2598
|
+
console.log("waited for the network to be idle timeout");
|
|
2553
2599
|
}
|
|
2554
2600
|
else if (e.label === "load") {
|
|
2555
|
-
console.log("
|
|
2601
|
+
console.log("waited for the load timeout");
|
|
2556
2602
|
}
|
|
2557
2603
|
else if (e.label === "domcontentloaded") {
|
|
2558
|
-
console.log("
|
|
2604
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2559
2605
|
}
|
|
2560
2606
|
console.log(".");
|
|
2561
2607
|
}
|
|
@@ -2563,7 +2609,7 @@ class StableBrowser {
|
|
|
2563
2609
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2564
2610
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2565
2611
|
const endTime = Date.now();
|
|
2566
|
-
|
|
2612
|
+
_reportToWorld(world, {
|
|
2567
2613
|
type: Types.GET_PAGE_STATUS,
|
|
2568
2614
|
text: "Wait for page load",
|
|
2569
2615
|
screenshotId,
|
|
@@ -2572,7 +2618,7 @@ class StableBrowser {
|
|
|
2572
2618
|
status: "FAILED",
|
|
2573
2619
|
startTime,
|
|
2574
2620
|
endTime,
|
|
2575
|
-
message: error
|
|
2621
|
+
message: error?.message,
|
|
2576
2622
|
}
|
|
2577
2623
|
: {
|
|
2578
2624
|
status: "PASSED",
|
|
@@ -2583,48 +2629,35 @@ class StableBrowser {
|
|
|
2583
2629
|
}
|
|
2584
2630
|
}
|
|
2585
2631
|
async closePage(options = {}, world = null) {
|
|
2586
|
-
const
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2632
|
+
const state = {
|
|
2633
|
+
options,
|
|
2634
|
+
world,
|
|
2635
|
+
locate: false,
|
|
2636
|
+
scroll: false,
|
|
2637
|
+
highlight: false,
|
|
2638
|
+
type: Types.CLOSE_PAGE,
|
|
2639
|
+
text: `Close page`,
|
|
2640
|
+
operation: "closePage",
|
|
2641
|
+
log: "***** close page *****\n",
|
|
2642
|
+
throwError: false,
|
|
2643
|
+
};
|
|
2591
2644
|
try {
|
|
2645
|
+
await _preCommand(state, this);
|
|
2592
2646
|
await this.page.close();
|
|
2593
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2594
|
-
this.context.pages.pop();
|
|
2595
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2596
|
-
this.context.page = this.page;
|
|
2597
|
-
let title = await this.page.title();
|
|
2598
|
-
console.log("Switched to page " + title);
|
|
2599
|
-
}
|
|
2600
2647
|
}
|
|
2601
2648
|
catch (e) {
|
|
2602
2649
|
console.log(".");
|
|
2650
|
+
await _commandError(state, e, this);
|
|
2603
2651
|
}
|
|
2604
2652
|
finally {
|
|
2605
|
-
|
|
2606
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2607
|
-
const endTime = Date.now();
|
|
2608
|
-
this._reportToWorld(world, {
|
|
2609
|
-
type: Types.CLOSE_PAGE,
|
|
2610
|
-
text: "close page",
|
|
2611
|
-
screenshotId,
|
|
2612
|
-
result: error
|
|
2613
|
-
? {
|
|
2614
|
-
status: "FAILED",
|
|
2615
|
-
startTime,
|
|
2616
|
-
endTime,
|
|
2617
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2618
|
-
}
|
|
2619
|
-
: {
|
|
2620
|
-
status: "PASSED",
|
|
2621
|
-
startTime,
|
|
2622
|
-
endTime,
|
|
2623
|
-
},
|
|
2624
|
-
info: info,
|
|
2625
|
-
});
|
|
2653
|
+
_commandFinally(state, this);
|
|
2626
2654
|
}
|
|
2627
2655
|
}
|
|
2656
|
+
saveTestDataAsGlobal(options, world) {
|
|
2657
|
+
const dataFile = this._getDataFile(world);
|
|
2658
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2659
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2660
|
+
}
|
|
2628
2661
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2629
2662
|
const startTime = Date.now();
|
|
2630
2663
|
let error = null;
|
|
@@ -2642,12 +2675,13 @@ class StableBrowser {
|
|
|
2642
2675
|
}
|
|
2643
2676
|
catch (e) {
|
|
2644
2677
|
console.log(".");
|
|
2678
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2645
2679
|
}
|
|
2646
2680
|
finally {
|
|
2647
2681
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2648
2682
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2649
2683
|
const endTime = Date.now();
|
|
2650
|
-
|
|
2684
|
+
_reportToWorld(world, {
|
|
2651
2685
|
type: Types.SET_VIEWPORT,
|
|
2652
2686
|
text: "set viewport size to " + width + "x" + hight,
|
|
2653
2687
|
screenshotId,
|
|
@@ -2656,7 +2690,7 @@ class StableBrowser {
|
|
|
2656
2690
|
status: "FAILED",
|
|
2657
2691
|
startTime,
|
|
2658
2692
|
endTime,
|
|
2659
|
-
message: error
|
|
2693
|
+
message: error?.message,
|
|
2660
2694
|
}
|
|
2661
2695
|
: {
|
|
2662
2696
|
status: "PASSED",
|
|
@@ -2678,12 +2712,13 @@ class StableBrowser {
|
|
|
2678
2712
|
}
|
|
2679
2713
|
catch (e) {
|
|
2680
2714
|
console.log(".");
|
|
2715
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2681
2716
|
}
|
|
2682
2717
|
finally {
|
|
2683
2718
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2684
2719
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2685
2720
|
const endTime = Date.now();
|
|
2686
|
-
|
|
2721
|
+
_reportToWorld(world, {
|
|
2687
2722
|
type: Types.GET_PAGE_STATUS,
|
|
2688
2723
|
text: "page relaod",
|
|
2689
2724
|
screenshotId,
|
|
@@ -2692,7 +2727,7 @@ class StableBrowser {
|
|
|
2692
2727
|
status: "FAILED",
|
|
2693
2728
|
startTime,
|
|
2694
2729
|
endTime,
|
|
2695
|
-
message: error
|
|
2730
|
+
message: error?.message,
|
|
2696
2731
|
}
|
|
2697
2732
|
: {
|
|
2698
2733
|
status: "PASSED",
|
|
@@ -2705,40 +2740,51 @@ class StableBrowser {
|
|
|
2705
2740
|
}
|
|
2706
2741
|
async scrollIfNeeded(element, info) {
|
|
2707
2742
|
try {
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
if (rect &&
|
|
2711
|
-
rect.top >= 0 &&
|
|
2712
|
-
rect.left >= 0 &&
|
|
2713
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2714
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2715
|
-
return false;
|
|
2716
|
-
}
|
|
2717
|
-
else {
|
|
2718
|
-
node.scrollIntoView({
|
|
2719
|
-
behavior: "smooth",
|
|
2720
|
-
block: "center",
|
|
2721
|
-
inline: "center",
|
|
2722
|
-
});
|
|
2723
|
-
return true;
|
|
2724
|
-
}
|
|
2743
|
+
await element.scrollIntoViewIfNeeded({
|
|
2744
|
+
timeout: 2000,
|
|
2725
2745
|
});
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
}
|
|
2746
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2747
|
+
if (info) {
|
|
2748
|
+
info.box = await element.boundingBox({
|
|
2749
|
+
timeout: 1000,
|
|
2750
|
+
});
|
|
2731
2751
|
}
|
|
2732
2752
|
}
|
|
2733
2753
|
catch (e) {
|
|
2734
|
-
console.log("
|
|
2754
|
+
console.log("#-#");
|
|
2735
2755
|
}
|
|
2736
2756
|
}
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2757
|
+
async beforeStep(world, step) {
|
|
2758
|
+
this.stepName = step.pickleStep.text;
|
|
2759
|
+
this.logger.info("step: " + this.stepName);
|
|
2760
|
+
if (this.stepIndex === undefined) {
|
|
2761
|
+
this.stepIndex = 0;
|
|
2762
|
+
}
|
|
2763
|
+
else {
|
|
2764
|
+
this.stepIndex++;
|
|
2765
|
+
}
|
|
2766
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2767
|
+
if (this.context.browserObject.context) {
|
|
2768
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2772
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2773
|
+
// check if @global_test_data tag is present
|
|
2774
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2775
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2776
|
+
}
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
async afterStep(world, step) {
|
|
2780
|
+
this.stepName = null;
|
|
2781
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2782
|
+
if (this.context.browserObject.context) {
|
|
2783
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2784
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2740
2787
|
}
|
|
2741
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2742
2788
|
}
|
|
2743
2789
|
}
|
|
2744
2790
|
function createTimedPromise(promise, label) {
|
|
@@ -2892,5 +2938,10 @@ const KEYBOARD_EVENTS = [
|
|
|
2892
2938
|
"TVAntennaCable",
|
|
2893
2939
|
"TVAudioDescription",
|
|
2894
2940
|
];
|
|
2941
|
+
function unEscapeString(str) {
|
|
2942
|
+
const placeholder = "__NEWLINE__";
|
|
2943
|
+
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2944
|
+
return str;
|
|
2945
|
+
}
|
|
2895
2946
|
export { StableBrowser };
|
|
2896
2947
|
//# sourceMappingURL=stable_browser.js.map
|