automation_model 1.0.406-dev → 1.0.406
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 +932 -809
- 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,18 +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";
|
|
16
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";
|
|
17
22
|
const Types = {
|
|
18
23
|
CLICK: "click_element",
|
|
19
24
|
NAVIGATE: "navigate",
|
|
@@ -39,16 +44,28 @@ const Types = {
|
|
|
39
44
|
SET_VIEWPORT: "set_viewport",
|
|
40
45
|
VERIFY_VISUAL: "verify_visual",
|
|
41
46
|
LOAD_DATA: "load_data",
|
|
47
|
+
SET_INPUT: "set_input",
|
|
48
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
42
49
|
};
|
|
50
|
+
export const apps = {};
|
|
43
51
|
class StableBrowser {
|
|
44
|
-
|
|
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) {
|
|
45
64
|
this.browser = browser;
|
|
46
65
|
this.page = page;
|
|
47
66
|
this.logger = logger;
|
|
48
67
|
this.context = context;
|
|
49
|
-
this.
|
|
50
|
-
this.webLogFile = null;
|
|
51
|
-
this.configuration = null;
|
|
68
|
+
this.world = world;
|
|
52
69
|
if (!this.logger) {
|
|
53
70
|
this.logger = console;
|
|
54
71
|
}
|
|
@@ -74,23 +91,61 @@ class StableBrowser {
|
|
|
74
91
|
this.logger.error("unable to read ai_config.json");
|
|
75
92
|
}
|
|
76
93
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
77
|
-
this.
|
|
78
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
79
|
-
this.registerRequestListener();
|
|
94
|
+
this.world = world;
|
|
80
95
|
context.pages = [this.page];
|
|
81
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
|
+
}
|
|
82
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
|
+
}
|
|
83
131
|
context.pageLoading.status = true;
|
|
84
132
|
this.page = page;
|
|
85
133
|
context.page = page;
|
|
86
134
|
context.pages.push(page);
|
|
135
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
136
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
87
137
|
page.on("close", async () => {
|
|
88
|
-
if (this.context && this.context.pages && this.context.pages.length >
|
|
138
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
89
139
|
this.context.pages.pop();
|
|
90
140
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
91
141
|
this.context.page = this.page;
|
|
92
|
-
|
|
93
|
-
|
|
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
|
+
}
|
|
94
149
|
}
|
|
95
150
|
});
|
|
96
151
|
try {
|
|
@@ -103,6 +158,36 @@ class StableBrowser {
|
|
|
103
158
|
context.pageLoading.status = false;
|
|
104
159
|
}.bind(this));
|
|
105
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
|
+
}
|
|
106
191
|
getWebLogFile(logFolder) {
|
|
107
192
|
if (!fs.existsSync(logFolder)) {
|
|
108
193
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -114,37 +199,63 @@ class StableBrowser {
|
|
|
114
199
|
const fileName = nextIndex + ".json";
|
|
115
200
|
return path.join(logFolder, fileName);
|
|
116
201
|
}
|
|
117
|
-
registerConsoleLogListener(page, context
|
|
202
|
+
registerConsoleLogListener(page, context) {
|
|
118
203
|
if (!this.context.webLogger) {
|
|
119
204
|
this.context.webLogger = [];
|
|
120
205
|
}
|
|
121
206
|
page.on("console", async (msg) => {
|
|
122
|
-
|
|
207
|
+
const obj = {
|
|
123
208
|
type: msg.type(),
|
|
124
209
|
text: msg.text(),
|
|
125
210
|
location: msg.location(),
|
|
126
211
|
time: new Date().toISOString(),
|
|
127
|
-
}
|
|
128
|
-
|
|
212
|
+
};
|
|
213
|
+
this.context.webLogger.push(obj);
|
|
214
|
+
if (msg.type() === "error") {
|
|
215
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
216
|
+
}
|
|
129
217
|
});
|
|
130
218
|
}
|
|
131
|
-
registerRequestListener() {
|
|
132
|
-
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();
|
|
133
225
|
try {
|
|
134
|
-
const pageUrl = new URL(
|
|
226
|
+
const pageUrl = new URL(page.url());
|
|
135
227
|
const requestUrl = new URL(data.url());
|
|
136
228
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
137
229
|
const method = data.method();
|
|
138
|
-
if (
|
|
230
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
139
231
|
const token = await data.headerValue("Authorization");
|
|
140
232
|
if (token) {
|
|
141
|
-
|
|
233
|
+
context.authtoken = token;
|
|
142
234
|
}
|
|
143
235
|
}
|
|
144
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" });
|
|
145
249
|
}
|
|
146
250
|
catch (error) {
|
|
147
|
-
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));
|
|
148
259
|
}
|
|
149
260
|
});
|
|
150
261
|
}
|
|
@@ -159,20 +270,6 @@ class StableBrowser {
|
|
|
159
270
|
timeout: 60000,
|
|
160
271
|
});
|
|
161
272
|
}
|
|
162
|
-
_validateSelectors(selectors) {
|
|
163
|
-
if (!selectors) {
|
|
164
|
-
throw new Error("selectors is null");
|
|
165
|
-
}
|
|
166
|
-
if (!selectors.locators) {
|
|
167
|
-
throw new Error("selectors.locators is null");
|
|
168
|
-
}
|
|
169
|
-
if (!Array.isArray(selectors.locators)) {
|
|
170
|
-
throw new Error("selectors.locators expected to be array");
|
|
171
|
-
}
|
|
172
|
-
if (selectors.locators.length === 0) {
|
|
173
|
-
throw new Error("selectors.locators expected to be non empty array");
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
273
|
_fixUsingParams(text, _params) {
|
|
177
274
|
if (!_params || typeof text !== "string") {
|
|
178
275
|
return text;
|
|
@@ -214,9 +311,7 @@ class StableBrowser {
|
|
|
214
311
|
}
|
|
215
312
|
_getLocator(locator, scope, _params) {
|
|
216
313
|
locator = this._fixLocatorUsingParams(locator, _params);
|
|
217
|
-
|
|
218
|
-
return scope.locator(locator.selector);
|
|
219
|
-
}
|
|
314
|
+
let locatorReturn;
|
|
220
315
|
if (locator.role) {
|
|
221
316
|
if (locator.role[1].nameReg) {
|
|
222
317
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
@@ -225,23 +320,48 @@ class StableBrowser {
|
|
|
225
320
|
// if (locator.role[1].name) {
|
|
226
321
|
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
227
322
|
// }
|
|
228
|
-
|
|
323
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
229
324
|
}
|
|
230
325
|
if (locator.css) {
|
|
231
|
-
|
|
326
|
+
locatorReturn = scope.locator(locator.css);
|
|
232
327
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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" });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
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}`);
|
|
237
352
|
}
|
|
238
|
-
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
239
|
-
return locator;
|
|
240
353
|
}
|
|
241
|
-
|
|
354
|
+
if (!locatorReturn) {
|
|
355
|
+
console.error(locator);
|
|
356
|
+
throw new Error("Locator undefined");
|
|
357
|
+
}
|
|
358
|
+
return locatorReturn;
|
|
242
359
|
}
|
|
243
360
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
244
|
-
|
|
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);
|
|
245
365
|
if (result.elementCount === 0) {
|
|
246
366
|
return;
|
|
247
367
|
}
|
|
@@ -256,7 +376,7 @@ class StableBrowser {
|
|
|
256
376
|
}
|
|
257
377
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
258
378
|
//const stringifyText = JSON.stringify(text);
|
|
259
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
379
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
260
380
|
function isParent(parent, child) {
|
|
261
381
|
let currentNode = child.parentNode;
|
|
262
382
|
while (currentNode !== null) {
|
|
@@ -268,6 +388,15 @@ class StableBrowser {
|
|
|
268
388
|
return false;
|
|
269
389
|
}
|
|
270
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;
|
|
271
400
|
function collectAllShadowDomElements(element, result = []) {
|
|
272
401
|
// Check and add the element if it has a shadow root
|
|
273
402
|
if (element.shadowRoot) {
|
|
@@ -284,7 +413,11 @@ class StableBrowser {
|
|
|
284
413
|
}
|
|
285
414
|
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
286
415
|
if (!tag) {
|
|
287
|
-
tag = "
|
|
416
|
+
tag = "*:not(script, style, head)";
|
|
417
|
+
}
|
|
418
|
+
let regexpSearch = document.getRegex(text);
|
|
419
|
+
if (regexpSearch) {
|
|
420
|
+
regex = true;
|
|
288
421
|
}
|
|
289
422
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
290
423
|
let shadowHosts = [];
|
|
@@ -301,7 +434,9 @@ class StableBrowser {
|
|
|
301
434
|
let randomToken = null;
|
|
302
435
|
const foundElements = [];
|
|
303
436
|
if (regex) {
|
|
304
|
-
|
|
437
|
+
if (!regexpSearch) {
|
|
438
|
+
regexpSearch = new RegExp(text, "im");
|
|
439
|
+
}
|
|
305
440
|
for (let i = 0; i < elements.length; i++) {
|
|
306
441
|
const element = elements[i];
|
|
307
442
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -315,8 +450,8 @@ class StableBrowser {
|
|
|
315
450
|
for (let i = 0; i < elements.length; i++) {
|
|
316
451
|
const element = elements[i];
|
|
317
452
|
if (partial) {
|
|
318
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
319
|
-
(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()))) {
|
|
320
455
|
foundElements.push(element);
|
|
321
456
|
}
|
|
322
457
|
}
|
|
@@ -360,19 +495,39 @@ class StableBrowser {
|
|
|
360
495
|
}, [text1, tag1, regex1, partial1]);
|
|
361
496
|
}
|
|
362
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
|
+
}
|
|
363
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
|
+
}
|
|
364
514
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
365
515
|
let locator = null;
|
|
366
516
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
367
517
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
368
518
|
if (!locatorString) {
|
|
519
|
+
info.failCause.textNotFound = true;
|
|
520
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
369
521
|
return;
|
|
370
522
|
}
|
|
371
523
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
372
524
|
}
|
|
373
525
|
else if (locatorSearch.text) {
|
|
374
|
-
let
|
|
526
|
+
let text = this._fixUsingParams(locatorSearch.text, _params);
|
|
527
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
375
528
|
if (result.elementCount === 0) {
|
|
529
|
+
info.failCause.textNotFound = true;
|
|
530
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
376
531
|
return;
|
|
377
532
|
}
|
|
378
533
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -389,10 +544,13 @@ class StableBrowser {
|
|
|
389
544
|
// cssHref = true;
|
|
390
545
|
// }
|
|
391
546
|
let count = await locator.count();
|
|
547
|
+
if (count > 0 && !info.failCause.count) {
|
|
548
|
+
info.failCause.count = count;
|
|
549
|
+
}
|
|
392
550
|
//info.log += "total elements found " + count + "\n";
|
|
393
551
|
//let visibleCount = 0;
|
|
394
552
|
let visibleLocator = null;
|
|
395
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
553
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
396
554
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
397
555
|
return;
|
|
398
556
|
}
|
|
@@ -406,6 +564,8 @@ class StableBrowser {
|
|
|
406
564
|
foundLocators.push(locator.nth(j));
|
|
407
565
|
}
|
|
408
566
|
else {
|
|
567
|
+
info.failCause.visible = visible;
|
|
568
|
+
info.failCause.enabled = enabled;
|
|
409
569
|
if (!info.printMessages) {
|
|
410
570
|
info.printMessages = {};
|
|
411
571
|
}
|
|
@@ -417,6 +577,11 @@ class StableBrowser {
|
|
|
417
577
|
}
|
|
418
578
|
}
|
|
419
579
|
async closeUnexpectedPopups(info, _params) {
|
|
580
|
+
if (!info) {
|
|
581
|
+
info = {};
|
|
582
|
+
info.failCause = {};
|
|
583
|
+
info.log = "";
|
|
584
|
+
}
|
|
420
585
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
421
586
|
if (!info) {
|
|
422
587
|
info = {};
|
|
@@ -448,16 +613,33 @@ class StableBrowser {
|
|
|
448
613
|
}
|
|
449
614
|
if (result.foundElements.length > 0) {
|
|
450
615
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
451
|
-
|
|
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
|
+
}
|
|
452
631
|
return { rerun: true };
|
|
453
632
|
}
|
|
454
633
|
}
|
|
455
634
|
}
|
|
456
635
|
return { rerun: false };
|
|
457
636
|
}
|
|
458
|
-
async _locate(selectors, info, _params, timeout
|
|
637
|
+
async _locate(selectors, info, _params, timeout) {
|
|
638
|
+
if (!timeout) {
|
|
639
|
+
timeout = 30000;
|
|
640
|
+
}
|
|
459
641
|
for (let i = 0; i < 3; i++) {
|
|
460
|
-
info.log += "attempt " + i + ":
|
|
642
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
461
643
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
462
644
|
let selector = selectors.locators[j];
|
|
463
645
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -469,17 +651,49 @@ class StableBrowser {
|
|
|
469
651
|
}
|
|
470
652
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
471
653
|
}
|
|
472
|
-
async
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
654
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
655
|
+
if (!info) {
|
|
656
|
+
info = {};
|
|
657
|
+
info.failCause = {};
|
|
658
|
+
info.log = "";
|
|
659
|
+
}
|
|
478
660
|
let scope = this.page;
|
|
661
|
+
if (selectors.frame) {
|
|
662
|
+
return selectors.frame;
|
|
663
|
+
}
|
|
479
664
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
480
|
-
|
|
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
|
+
};
|
|
481
690
|
while (true) {
|
|
482
691
|
let frameFound = false;
|
|
692
|
+
if (selectors.nestFrmLoc) {
|
|
693
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
694
|
+
frameFound = true;
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
483
697
|
if (selectors.frameLocators) {
|
|
484
698
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
485
699
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -496,6 +710,8 @@ class StableBrowser {
|
|
|
496
710
|
if (!scope) {
|
|
497
711
|
info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
498
712
|
if (performance.now() - startTime > timeout) {
|
|
713
|
+
info.failCause.iframeNotFound = true;
|
|
714
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
499
715
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
500
716
|
}
|
|
501
717
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -505,6 +721,31 @@ class StableBrowser {
|
|
|
505
721
|
}
|
|
506
722
|
}
|
|
507
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);
|
|
508
749
|
let selectorsLocators = null;
|
|
509
750
|
selectorsLocators = selectors.locators;
|
|
510
751
|
// group selectors by priority
|
|
@@ -597,6 +838,10 @@ class StableBrowser {
|
|
|
597
838
|
if (performance.now() - startTime > highPriorityTimeout) {
|
|
598
839
|
info.log += "high priority timeout, will try all elements" + "\n";
|
|
599
840
|
highPriorityOnly = false;
|
|
841
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
842
|
+
lazy_scroll = true;
|
|
843
|
+
await this.scrollPageToLoadLazyElements();
|
|
844
|
+
}
|
|
600
845
|
}
|
|
601
846
|
if (performance.now() - startTime > visibleOnlyTimeout) {
|
|
602
847
|
info.log += "visible only timeout, will try all elements" + "\n";
|
|
@@ -606,6 +851,8 @@ class StableBrowser {
|
|
|
606
851
|
}
|
|
607
852
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
608
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";
|
|
609
856
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
610
857
|
}
|
|
611
858
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -637,86 +884,129 @@ class StableBrowser {
|
|
|
637
884
|
});
|
|
638
885
|
result.locatorIndex = i;
|
|
639
886
|
}
|
|
887
|
+
if (foundLocators.length > 1) {
|
|
888
|
+
info.failCause.foundMultiple = true;
|
|
889
|
+
}
|
|
640
890
|
}
|
|
641
891
|
return result;
|
|
642
892
|
}
|
|
643
|
-
async
|
|
644
|
-
this._validateSelectors(selectors);
|
|
893
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
645
894
|
const startTime = Date.now();
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
+
};
|
|
653
966
|
try {
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
967
|
+
await _preCommand(state, this);
|
|
968
|
+
if (state.options && state.options.context) {
|
|
969
|
+
state.selectors.locators[0].text = state.options.context;
|
|
970
|
+
}
|
|
657
971
|
try {
|
|
658
|
-
await
|
|
659
|
-
await
|
|
660
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
972
|
+
await state.element.click();
|
|
973
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
661
974
|
}
|
|
662
975
|
catch (e) {
|
|
663
976
|
// await this.closeUnexpectedPopups();
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
await
|
|
667
|
-
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));
|
|
668
980
|
}
|
|
669
981
|
await this.waitForPageLoad();
|
|
670
|
-
return info;
|
|
982
|
+
return state.info;
|
|
671
983
|
}
|
|
672
984
|
catch (e) {
|
|
673
|
-
|
|
674
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
675
|
-
info.screenshotPath = screenshotPath;
|
|
676
|
-
Object.assign(e, { info: info });
|
|
677
|
-
error = e;
|
|
678
|
-
throw e;
|
|
985
|
+
await _commandError(state, e, this);
|
|
679
986
|
}
|
|
680
987
|
finally {
|
|
681
|
-
|
|
682
|
-
this._reportToWorld(world, {
|
|
683
|
-
element_name: selectors.element_name,
|
|
684
|
-
type: Types.CLICK,
|
|
685
|
-
text: `Click element`,
|
|
686
|
-
screenshotId,
|
|
687
|
-
result: error
|
|
688
|
-
? {
|
|
689
|
-
status: "FAILED",
|
|
690
|
-
startTime,
|
|
691
|
-
endTime,
|
|
692
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
693
|
-
}
|
|
694
|
-
: {
|
|
695
|
-
status: "PASSED",
|
|
696
|
-
startTime,
|
|
697
|
-
endTime,
|
|
698
|
-
},
|
|
699
|
-
info: info,
|
|
700
|
-
});
|
|
988
|
+
_commandFinally(state, this);
|
|
701
989
|
}
|
|
702
990
|
}
|
|
703
991
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
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
|
+
};
|
|
714
1002
|
try {
|
|
715
|
-
|
|
716
|
-
|
|
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));
|
|
717
1007
|
try {
|
|
718
|
-
await this._highlightElements(element);
|
|
719
|
-
await element.setChecked(checked
|
|
1008
|
+
// await this._highlightElements(element);
|
|
1009
|
+
await state.element.setChecked(checked);
|
|
720
1010
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
721
1011
|
}
|
|
722
1012
|
catch (e) {
|
|
@@ -725,179 +1015,108 @@ class StableBrowser {
|
|
|
725
1015
|
}
|
|
726
1016
|
else {
|
|
727
1017
|
//await this.closeUnexpectedPopups();
|
|
728
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
729
|
-
element = await this._locate(selectors, info, _params);
|
|
730
|
-
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 });
|
|
731
1021
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
732
1022
|
}
|
|
733
1023
|
}
|
|
734
1024
|
await this.waitForPageLoad();
|
|
735
|
-
return info;
|
|
1025
|
+
return state.info;
|
|
736
1026
|
}
|
|
737
1027
|
catch (e) {
|
|
738
|
-
|
|
739
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
740
|
-
info.screenshotPath = screenshotPath;
|
|
741
|
-
Object.assign(e, { info: info });
|
|
742
|
-
error = e;
|
|
743
|
-
throw e;
|
|
1028
|
+
await _commandError(state, e, this);
|
|
744
1029
|
}
|
|
745
1030
|
finally {
|
|
746
|
-
|
|
747
|
-
this._reportToWorld(world, {
|
|
748
|
-
element_name: selectors.element_name,
|
|
749
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
750
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
751
|
-
screenshotId,
|
|
752
|
-
result: error
|
|
753
|
-
? {
|
|
754
|
-
status: "FAILED",
|
|
755
|
-
startTime,
|
|
756
|
-
endTime,
|
|
757
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
758
|
-
}
|
|
759
|
-
: {
|
|
760
|
-
status: "PASSED",
|
|
761
|
-
startTime,
|
|
762
|
-
endTime,
|
|
763
|
-
},
|
|
764
|
-
info: info,
|
|
765
|
-
});
|
|
1031
|
+
_commandFinally(state, this);
|
|
766
1032
|
}
|
|
767
1033
|
}
|
|
768
1034
|
async hover(selectors, _params, options = {}, world = null) {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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
|
+
};
|
|
778
1045
|
try {
|
|
779
|
-
|
|
780
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1046
|
+
await _preCommand(state, this);
|
|
781
1047
|
try {
|
|
782
|
-
await
|
|
783
|
-
await element.hover({ timeout: 10000 });
|
|
1048
|
+
await state.element.hover();
|
|
784
1049
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
785
1050
|
}
|
|
786
1051
|
catch (e) {
|
|
787
1052
|
//await this.closeUnexpectedPopups();
|
|
788
|
-
info.log += "hover failed, will try again" + "\n";
|
|
789
|
-
element = await this._locate(selectors, info, _params);
|
|
790
|
-
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 });
|
|
791
1056
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
792
1057
|
}
|
|
793
1058
|
await this.waitForPageLoad();
|
|
794
|
-
return info;
|
|
1059
|
+
return state.info;
|
|
795
1060
|
}
|
|
796
1061
|
catch (e) {
|
|
797
|
-
|
|
798
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
799
|
-
info.screenshotPath = screenshotPath;
|
|
800
|
-
Object.assign(e, { info: info });
|
|
801
|
-
error = e;
|
|
802
|
-
throw e;
|
|
1062
|
+
await _commandError(state, e, this);
|
|
803
1063
|
}
|
|
804
1064
|
finally {
|
|
805
|
-
|
|
806
|
-
this._reportToWorld(world, {
|
|
807
|
-
element_name: selectors.element_name,
|
|
808
|
-
type: Types.HOVER,
|
|
809
|
-
text: `Hover element`,
|
|
810
|
-
screenshotId,
|
|
811
|
-
result: error
|
|
812
|
-
? {
|
|
813
|
-
status: "FAILED",
|
|
814
|
-
startTime,
|
|
815
|
-
endTime,
|
|
816
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
817
|
-
}
|
|
818
|
-
: {
|
|
819
|
-
status: "PASSED",
|
|
820
|
-
startTime,
|
|
821
|
-
endTime,
|
|
822
|
-
},
|
|
823
|
-
info: info,
|
|
824
|
-
});
|
|
1065
|
+
_commandFinally(state, this);
|
|
825
1066
|
}
|
|
826
1067
|
}
|
|
827
1068
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
828
|
-
this._validateSelectors(selectors);
|
|
829
1069
|
if (!values) {
|
|
830
1070
|
throw new Error("values is null");
|
|
831
1071
|
}
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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
|
+
};
|
|
840
1083
|
try {
|
|
841
|
-
|
|
842
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1084
|
+
await _preCommand(state, this);
|
|
843
1085
|
try {
|
|
844
|
-
await
|
|
845
|
-
await element.selectOption(values, { timeout: 5000 });
|
|
1086
|
+
await state.element.selectOption(values);
|
|
846
1087
|
}
|
|
847
1088
|
catch (e) {
|
|
848
1089
|
//await this.closeUnexpectedPopups();
|
|
849
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
850
|
-
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 });
|
|
851
1092
|
}
|
|
852
1093
|
await this.waitForPageLoad();
|
|
853
|
-
return info;
|
|
1094
|
+
return state.info;
|
|
854
1095
|
}
|
|
855
1096
|
catch (e) {
|
|
856
|
-
|
|
857
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
858
|
-
info.screenshotPath = screenshotPath;
|
|
859
|
-
Object.assign(e, { info: info });
|
|
860
|
-
this.logger.info("click failed, will try next selector");
|
|
861
|
-
error = e;
|
|
862
|
-
throw e;
|
|
1097
|
+
await _commandError(state, e, this);
|
|
863
1098
|
}
|
|
864
1099
|
finally {
|
|
865
|
-
|
|
866
|
-
this._reportToWorld(world, {
|
|
867
|
-
element_name: selectors.element_name,
|
|
868
|
-
type: Types.SELECT,
|
|
869
|
-
text: `Select option: ${values}`,
|
|
870
|
-
value: values.toString(),
|
|
871
|
-
screenshotId,
|
|
872
|
-
result: error
|
|
873
|
-
? {
|
|
874
|
-
status: "FAILED",
|
|
875
|
-
startTime,
|
|
876
|
-
endTime,
|
|
877
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
878
|
-
}
|
|
879
|
-
: {
|
|
880
|
-
status: "PASSED",
|
|
881
|
-
startTime,
|
|
882
|
-
endTime,
|
|
883
|
-
},
|
|
884
|
-
info: info,
|
|
885
|
-
});
|
|
1100
|
+
_commandFinally(state, this);
|
|
886
1101
|
}
|
|
887
1102
|
}
|
|
888
1103
|
async type(_value, _params = null, options = {}, world = null) {
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
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
|
+
};
|
|
898
1117
|
try {
|
|
899
|
-
|
|
900
|
-
const valueSegment =
|
|
1118
|
+
await _preCommand(state, this);
|
|
1119
|
+
const valueSegment = state.value.split("&&");
|
|
901
1120
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
902
1121
|
if (i > 0) {
|
|
903
1122
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -917,68 +1136,76 @@ class StableBrowser {
|
|
|
917
1136
|
await this.page.keyboard.type(value);
|
|
918
1137
|
}
|
|
919
1138
|
}
|
|
920
|
-
return info;
|
|
1139
|
+
return state.info;
|
|
921
1140
|
}
|
|
922
1141
|
catch (e) {
|
|
923
|
-
|
|
924
|
-
this.logger.error("type failed " + info.log);
|
|
925
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
926
|
-
info.screenshotPath = screenshotPath;
|
|
927
|
-
Object.assign(e, { info: info });
|
|
928
|
-
error = e;
|
|
929
|
-
throw e;
|
|
1142
|
+
await _commandError(state, e, this);
|
|
930
1143
|
}
|
|
931
1144
|
finally {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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);
|
|
952
1182
|
}
|
|
953
1183
|
}
|
|
954
1184
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
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
|
+
};
|
|
965
1197
|
try {
|
|
966
|
-
|
|
967
|
-
let element = await this._locate(selectors, info, _params);
|
|
968
|
-
//insert red border around the element
|
|
969
|
-
await this.scrollIfNeeded(element, info);
|
|
970
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
971
|
-
await this._highlightElements(element);
|
|
1198
|
+
await _preCommand(state, this);
|
|
972
1199
|
try {
|
|
973
|
-
await element.click();
|
|
1200
|
+
await state.element.click();
|
|
974
1201
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
975
1202
|
if (format) {
|
|
976
|
-
value = dayjs(value).format(format);
|
|
977
|
-
await element.fill(value);
|
|
1203
|
+
state.value = dayjs(state.value).format(format);
|
|
1204
|
+
await state.element.fill(state.value);
|
|
978
1205
|
}
|
|
979
1206
|
else {
|
|
980
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
981
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1207
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1208
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
982
1209
|
el.value = ""; // clear input
|
|
983
1210
|
el.value = dateTimeValue;
|
|
984
1211
|
}, dateTimeValue);
|
|
@@ -991,19 +1218,19 @@ class StableBrowser {
|
|
|
991
1218
|
}
|
|
992
1219
|
catch (err) {
|
|
993
1220
|
//await this.closeUnexpectedPopups();
|
|
994
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
995
|
-
this.logger.info("Trying again")
|
|
996
|
-
|
|
997
|
-
Object.assign(err, { info: info });
|
|
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 });
|
|
998
1225
|
await element.click();
|
|
999
1226
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1000
1227
|
if (format) {
|
|
1001
|
-
value = dayjs(value).format(format);
|
|
1002
|
-
await element.fill(value);
|
|
1228
|
+
state.value = dayjs(state.value).format(format);
|
|
1229
|
+
await state.element.fill(state.value);
|
|
1003
1230
|
}
|
|
1004
1231
|
else {
|
|
1005
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1006
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1232
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1233
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1007
1234
|
el.value = ""; // clear input
|
|
1008
1235
|
el.value = dateTimeValue;
|
|
1009
1236
|
}, dateTimeValue);
|
|
@@ -1016,60 +1243,39 @@ class StableBrowser {
|
|
|
1016
1243
|
}
|
|
1017
1244
|
}
|
|
1018
1245
|
catch (e) {
|
|
1019
|
-
|
|
1020
|
-
throw e;
|
|
1246
|
+
await _commandError(state, e, this);
|
|
1021
1247
|
}
|
|
1022
1248
|
finally {
|
|
1023
|
-
|
|
1024
|
-
this._reportToWorld(world, {
|
|
1025
|
-
element_name: selectors.element_name,
|
|
1026
|
-
type: Types.SET_DATE_TIME,
|
|
1027
|
-
screenshotId,
|
|
1028
|
-
value: value,
|
|
1029
|
-
text: `setDateTime input with value: ${value}`,
|
|
1030
|
-
result: error
|
|
1031
|
-
? {
|
|
1032
|
-
status: "FAILED",
|
|
1033
|
-
startTime,
|
|
1034
|
-
endTime,
|
|
1035
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1036
|
-
}
|
|
1037
|
-
: {
|
|
1038
|
-
status: "PASSED",
|
|
1039
|
-
startTime,
|
|
1040
|
-
endTime,
|
|
1041
|
-
},
|
|
1042
|
-
info: info,
|
|
1043
|
-
});
|
|
1249
|
+
_commandFinally(state, this);
|
|
1044
1250
|
}
|
|
1045
1251
|
}
|
|
1046
1252
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1047
|
-
|
|
1048
|
-
const startTime = Date.now();
|
|
1049
|
-
let error = null;
|
|
1050
|
-
let screenshotId = null;
|
|
1051
|
-
let screenshotPath = null;
|
|
1052
|
-
const info = {};
|
|
1053
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1054
|
-
info.operation = "clickType";
|
|
1055
|
-
info.selectors = selectors;
|
|
1253
|
+
_value = unEscapeString(_value);
|
|
1056
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
|
+
};
|
|
1057
1267
|
if (newValue !== _value) {
|
|
1058
1268
|
//this.logger.info(_value + "=" + newValue);
|
|
1059
1269
|
_value = newValue;
|
|
1060
1270
|
}
|
|
1061
|
-
info.value = _value;
|
|
1062
1271
|
try {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
await this.scrollIfNeeded(element, info);
|
|
1066
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1067
|
-
await this._highlightElements(element);
|
|
1272
|
+
await _preCommand(state, this);
|
|
1273
|
+
state.info.value = _value;
|
|
1068
1274
|
if (options === null || options === undefined || !options.press) {
|
|
1069
1275
|
try {
|
|
1070
|
-
let currentValue = await element.inputValue();
|
|
1276
|
+
let currentValue = await state.element.inputValue();
|
|
1071
1277
|
if (currentValue) {
|
|
1072
|
-
await element.fill("");
|
|
1278
|
+
await state.element.fill("");
|
|
1073
1279
|
}
|
|
1074
1280
|
}
|
|
1075
1281
|
catch (e) {
|
|
@@ -1078,22 +1284,22 @@ class StableBrowser {
|
|
|
1078
1284
|
}
|
|
1079
1285
|
if (options === null || options === undefined || options.press) {
|
|
1080
1286
|
try {
|
|
1081
|
-
await element.click({ timeout: 5000 });
|
|
1287
|
+
await state.element.click({ timeout: 5000 });
|
|
1082
1288
|
}
|
|
1083
1289
|
catch (e) {
|
|
1084
|
-
await element.dispatchEvent("click");
|
|
1290
|
+
await state.element.dispatchEvent("click");
|
|
1085
1291
|
}
|
|
1086
1292
|
}
|
|
1087
1293
|
else {
|
|
1088
1294
|
try {
|
|
1089
|
-
await element.focus();
|
|
1295
|
+
await state.element.focus();
|
|
1090
1296
|
}
|
|
1091
1297
|
catch (e) {
|
|
1092
|
-
await element.dispatchEvent("focus");
|
|
1298
|
+
await state.element.dispatchEvent("focus");
|
|
1093
1299
|
}
|
|
1094
1300
|
}
|
|
1095
1301
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1096
|
-
const valueSegment =
|
|
1302
|
+
const valueSegment = state.value.split("&&");
|
|
1097
1303
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1098
1304
|
if (i > 0) {
|
|
1099
1305
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1113,13 +1319,14 @@ class StableBrowser {
|
|
|
1113
1319
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1114
1320
|
}
|
|
1115
1321
|
}
|
|
1322
|
+
await _screenshot(state, this);
|
|
1116
1323
|
if (enter === true) {
|
|
1117
1324
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1118
1325
|
await this.page.keyboard.press("Enter");
|
|
1119
1326
|
await this.waitForPageLoad();
|
|
1120
1327
|
}
|
|
1121
1328
|
else if (enter === false) {
|
|
1122
|
-
await element.dispatchEvent("change");
|
|
1329
|
+
await state.element.dispatchEvent("change");
|
|
1123
1330
|
//await this.page.keyboard.press("Tab");
|
|
1124
1331
|
}
|
|
1125
1332
|
else {
|
|
@@ -1128,103 +1335,50 @@ class StableBrowser {
|
|
|
1128
1335
|
await this.waitForPageLoad();
|
|
1129
1336
|
}
|
|
1130
1337
|
}
|
|
1131
|
-
return info;
|
|
1338
|
+
return state.info;
|
|
1132
1339
|
}
|
|
1133
1340
|
catch (e) {
|
|
1134
|
-
|
|
1135
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1136
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1137
|
-
info.screenshotPath = screenshotPath;
|
|
1138
|
-
Object.assign(e, { info: info });
|
|
1139
|
-
error = e;
|
|
1140
|
-
throw e;
|
|
1341
|
+
await _commandError(state, e, this);
|
|
1141
1342
|
}
|
|
1142
1343
|
finally {
|
|
1143
|
-
|
|
1144
|
-
this._reportToWorld(world, {
|
|
1145
|
-
element_name: selectors.element_name,
|
|
1146
|
-
type: Types.FILL,
|
|
1147
|
-
screenshotId,
|
|
1148
|
-
value: _value,
|
|
1149
|
-
text: `clickType input with value: ${_value}`,
|
|
1150
|
-
result: error
|
|
1151
|
-
? {
|
|
1152
|
-
status: "FAILED",
|
|
1153
|
-
startTime,
|
|
1154
|
-
endTime,
|
|
1155
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1156
|
-
}
|
|
1157
|
-
: {
|
|
1158
|
-
status: "PASSED",
|
|
1159
|
-
startTime,
|
|
1160
|
-
endTime,
|
|
1161
|
-
},
|
|
1162
|
-
info: info,
|
|
1163
|
-
});
|
|
1344
|
+
_commandFinally(state, this);
|
|
1164
1345
|
}
|
|
1165
1346
|
}
|
|
1166
1347
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
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
|
+
};
|
|
1177
1359
|
try {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
await
|
|
1181
|
-
await element.fill(value, { timeout: 10000 });
|
|
1182
|
-
await element.dispatchEvent("change");
|
|
1360
|
+
await _preCommand(state, this);
|
|
1361
|
+
await state.element.fill(value);
|
|
1362
|
+
await state.element.dispatchEvent("change");
|
|
1183
1363
|
if (enter) {
|
|
1184
1364
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1185
1365
|
await this.page.keyboard.press("Enter");
|
|
1186
1366
|
}
|
|
1187
1367
|
await this.waitForPageLoad();
|
|
1188
|
-
return info;
|
|
1368
|
+
return state.info;
|
|
1189
1369
|
}
|
|
1190
1370
|
catch (e) {
|
|
1191
|
-
|
|
1192
|
-
this.logger.error("fill failed " + info.log);
|
|
1193
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1194
|
-
info.screenshotPath = screenshotPath;
|
|
1195
|
-
Object.assign(e, { info: info });
|
|
1196
|
-
error = e;
|
|
1197
|
-
throw e;
|
|
1371
|
+
await _commandError(state, e, this);
|
|
1198
1372
|
}
|
|
1199
1373
|
finally {
|
|
1200
|
-
|
|
1201
|
-
this._reportToWorld(world, {
|
|
1202
|
-
element_name: selectors.element_name,
|
|
1203
|
-
type: Types.FILL,
|
|
1204
|
-
screenshotId,
|
|
1205
|
-
value,
|
|
1206
|
-
text: `Fill input with value: ${value}`,
|
|
1207
|
-
result: error
|
|
1208
|
-
? {
|
|
1209
|
-
status: "FAILED",
|
|
1210
|
-
startTime,
|
|
1211
|
-
endTime,
|
|
1212
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1213
|
-
}
|
|
1214
|
-
: {
|
|
1215
|
-
status: "PASSED",
|
|
1216
|
-
startTime,
|
|
1217
|
-
endTime,
|
|
1218
|
-
},
|
|
1219
|
-
info: info,
|
|
1220
|
-
});
|
|
1374
|
+
_commandFinally(state, this);
|
|
1221
1375
|
}
|
|
1222
1376
|
}
|
|
1223
1377
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1224
1378
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1225
1379
|
}
|
|
1226
1380
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1227
|
-
|
|
1381
|
+
_validateSelectors(selectors);
|
|
1228
1382
|
let screenshotId = null;
|
|
1229
1383
|
let screenshotPath = null;
|
|
1230
1384
|
if (!info.log) {
|
|
@@ -1268,165 +1422,124 @@ class StableBrowser {
|
|
|
1268
1422
|
}
|
|
1269
1423
|
}
|
|
1270
1424
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1271
|
-
var _a;
|
|
1272
|
-
this._validateSelectors(selectors);
|
|
1273
1425
|
if (!pattern) {
|
|
1274
1426
|
throw new Error("pattern is null");
|
|
1275
1427
|
}
|
|
1276
1428
|
if (!text) {
|
|
1277
1429
|
throw new Error("text is null");
|
|
1278
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
|
+
};
|
|
1279
1447
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1280
1448
|
if (newValue !== text) {
|
|
1281
1449
|
this.logger.info(text + "=" + newValue);
|
|
1282
1450
|
text = newValue;
|
|
1283
1451
|
}
|
|
1284
|
-
const startTime = Date.now();
|
|
1285
|
-
let error = null;
|
|
1286
|
-
let screenshotId = null;
|
|
1287
|
-
let screenshotPath = null;
|
|
1288
|
-
const info = {};
|
|
1289
|
-
info.log =
|
|
1290
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1291
|
-
info.operation = "containsPattern";
|
|
1292
|
-
info.selectors = selectors;
|
|
1293
|
-
info.value = text;
|
|
1294
|
-
info.pattern = pattern;
|
|
1295
1452
|
let foundObj = null;
|
|
1296
1453
|
try {
|
|
1297
|
-
|
|
1454
|
+
await _preCommand(state, this);
|
|
1455
|
+
state.info.pattern = pattern;
|
|
1456
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1298
1457
|
if (foundObj && foundObj.element) {
|
|
1299
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1458
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1300
1459
|
}
|
|
1301
|
-
|
|
1460
|
+
await _screenshot(state, this);
|
|
1302
1461
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1303
1462
|
pattern = pattern.replace("{text}", escapedText);
|
|
1304
1463
|
let regex = new RegExp(pattern, "im");
|
|
1305
|
-
if (!regex.test(foundObj
|
|
1306
|
-
info.foundText = foundObj
|
|
1464
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1465
|
+
state.info.foundText = foundObj?.text;
|
|
1307
1466
|
throw new Error("element doesn't contain text " + text);
|
|
1308
1467
|
}
|
|
1309
|
-
return info;
|
|
1468
|
+
return state.info;
|
|
1310
1469
|
}
|
|
1311
1470
|
catch (e) {
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1315
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1316
|
-
info.screenshotPath = screenshotPath;
|
|
1317
|
-
Object.assign(e, { info: info });
|
|
1318
|
-
error = e;
|
|
1319
|
-
throw e;
|
|
1471
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1472
|
+
await _commandError(state, e, this);
|
|
1320
1473
|
}
|
|
1321
1474
|
finally {
|
|
1322
|
-
|
|
1323
|
-
this._reportToWorld(world, {
|
|
1324
|
-
element_name: selectors.element_name,
|
|
1325
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1326
|
-
value: pattern,
|
|
1327
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1328
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1329
|
-
result: error
|
|
1330
|
-
? {
|
|
1331
|
-
status: "FAILED",
|
|
1332
|
-
startTime,
|
|
1333
|
-
endTime,
|
|
1334
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1335
|
-
}
|
|
1336
|
-
: {
|
|
1337
|
-
status: "PASSED",
|
|
1338
|
-
startTime,
|
|
1339
|
-
endTime,
|
|
1340
|
-
},
|
|
1341
|
-
info: info,
|
|
1342
|
-
});
|
|
1475
|
+
_commandFinally(state, this);
|
|
1343
1476
|
}
|
|
1344
1477
|
}
|
|
1345
1478
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1346
|
-
|
|
1347
|
-
|
|
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
|
+
};
|
|
1348
1494
|
if (!text) {
|
|
1349
1495
|
throw new Error("text is null");
|
|
1350
1496
|
}
|
|
1351
|
-
|
|
1352
|
-
let error = null;
|
|
1353
|
-
let screenshotId = null;
|
|
1354
|
-
let screenshotPath = null;
|
|
1355
|
-
const info = {};
|
|
1356
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1357
|
-
info.operation = "containsText";
|
|
1358
|
-
info.selectors = selectors;
|
|
1497
|
+
text = unEscapeString(text);
|
|
1359
1498
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1360
1499
|
if (newValue !== text) {
|
|
1361
1500
|
this.logger.info(text + "=" + newValue);
|
|
1362
1501
|
text = newValue;
|
|
1363
1502
|
}
|
|
1364
|
-
info.value = text;
|
|
1365
1503
|
let foundObj = null;
|
|
1366
1504
|
try {
|
|
1367
|
-
|
|
1505
|
+
await _preCommand(state, this);
|
|
1506
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1368
1507
|
if (foundObj && foundObj.element) {
|
|
1369
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1508
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1370
1509
|
}
|
|
1371
|
-
|
|
1510
|
+
await _screenshot(state, this);
|
|
1372
1511
|
const dateAlternatives = findDateAlternatives(text);
|
|
1373
1512
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1374
1513
|
if (dateAlternatives.date) {
|
|
1375
1514
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1376
|
-
if (
|
|
1377
|
-
|
|
1378
|
-
return info;
|
|
1515
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1516
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1517
|
+
return state.info;
|
|
1379
1518
|
}
|
|
1380
1519
|
}
|
|
1381
1520
|
throw new Error("element doesn't contain text " + text);
|
|
1382
1521
|
}
|
|
1383
1522
|
else if (numberAlternatives.number) {
|
|
1384
1523
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1385
|
-
if (
|
|
1386
|
-
|
|
1387
|
-
return info;
|
|
1524
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1525
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1526
|
+
return state.info;
|
|
1388
1527
|
}
|
|
1389
1528
|
}
|
|
1390
1529
|
throw new Error("element doesn't contain text " + text);
|
|
1391
1530
|
}
|
|
1392
|
-
else if (!
|
|
1393
|
-
info.foundText = foundObj
|
|
1394
|
-
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;
|
|
1395
1534
|
throw new Error("element doesn't contain text " + text);
|
|
1396
1535
|
}
|
|
1397
|
-
return info;
|
|
1536
|
+
return state.info;
|
|
1398
1537
|
}
|
|
1399
1538
|
catch (e) {
|
|
1400
|
-
|
|
1401
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1402
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1403
|
-
info.screenshotPath = screenshotPath;
|
|
1404
|
-
Object.assign(e, { info: info });
|
|
1405
|
-
error = e;
|
|
1406
|
-
throw e;
|
|
1539
|
+
await _commandError(state, e, this);
|
|
1407
1540
|
}
|
|
1408
1541
|
finally {
|
|
1409
|
-
|
|
1410
|
-
this._reportToWorld(world, {
|
|
1411
|
-
element_name: selectors.element_name,
|
|
1412
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1413
|
-
text: `Verify element contains text: ${text}`,
|
|
1414
|
-
value: text,
|
|
1415
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1416
|
-
result: error
|
|
1417
|
-
? {
|
|
1418
|
-
status: "FAILED",
|
|
1419
|
-
startTime,
|
|
1420
|
-
endTime,
|
|
1421
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1422
|
-
}
|
|
1423
|
-
: {
|
|
1424
|
-
status: "PASSED",
|
|
1425
|
-
startTime,
|
|
1426
|
-
endTime,
|
|
1427
|
-
},
|
|
1428
|
-
info: info,
|
|
1429
|
-
});
|
|
1542
|
+
_commandFinally(state, this);
|
|
1430
1543
|
}
|
|
1431
1544
|
}
|
|
1432
1545
|
_getDataFile(world = null) {
|
|
@@ -1445,6 +1558,29 @@ class StableBrowser {
|
|
|
1445
1558
|
}
|
|
1446
1559
|
return dataFile;
|
|
1447
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
|
+
}
|
|
1448
1584
|
setTestData(testData, world = null) {
|
|
1449
1585
|
if (!testData) {
|
|
1450
1586
|
return;
|
|
@@ -1585,11 +1721,9 @@ class StableBrowser {
|
|
|
1585
1721
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1586
1722
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1587
1723
|
}
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
}
|
|
1592
|
-
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");
|
|
1593
1727
|
try {
|
|
1594
1728
|
await this.takeScreenshot(screenshotPath);
|
|
1595
1729
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1603,7 +1737,7 @@ class StableBrowser {
|
|
|
1603
1737
|
catch (e) {
|
|
1604
1738
|
this.logger.info("unable to take screenshot, ignored");
|
|
1605
1739
|
}
|
|
1606
|
-
result.screenshotId =
|
|
1740
|
+
result.screenshotId = uuidStr;
|
|
1607
1741
|
result.screenshotPath = screenshotPath;
|
|
1608
1742
|
if (info && info.box) {
|
|
1609
1743
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1632,7 +1766,6 @@ class StableBrowser {
|
|
|
1632
1766
|
}
|
|
1633
1767
|
async takeScreenshot(screenshotPath) {
|
|
1634
1768
|
const playContext = this.context.playContext;
|
|
1635
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1636
1769
|
// Using CDP to capture the screenshot
|
|
1637
1770
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1638
1771
|
document.body.scrollWidth,
|
|
@@ -1642,164 +1775,105 @@ class StableBrowser {
|
|
|
1642
1775
|
document.body.clientWidth,
|
|
1643
1776
|
document.documentElement.clientWidth,
|
|
1644
1777
|
])));
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
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
|
+
}
|
|
1680
1812
|
}
|
|
1681
1813
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
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
|
+
};
|
|
1687
1824
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1688
|
-
const info = {};
|
|
1689
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1690
|
-
info.operation = "verify";
|
|
1691
|
-
info.selectors = selectors;
|
|
1692
1825
|
try {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
}
|
|
1697
|
-
await this._highlightElements(element);
|
|
1698
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1699
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1700
|
-
return info;
|
|
1826
|
+
await _preCommand(state, this);
|
|
1827
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1828
|
+
return state.info;
|
|
1701
1829
|
}
|
|
1702
1830
|
catch (e) {
|
|
1703
|
-
|
|
1704
|
-
this.logger.error("verify failed " + info.log);
|
|
1705
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1706
|
-
info.screenshotPath = screenshotPath;
|
|
1707
|
-
Object.assign(e, { info: info });
|
|
1708
|
-
error = e;
|
|
1709
|
-
throw e;
|
|
1831
|
+
await _commandError(state, e, this);
|
|
1710
1832
|
}
|
|
1711
1833
|
finally {
|
|
1712
|
-
|
|
1713
|
-
this._reportToWorld(world, {
|
|
1714
|
-
element_name: selectors.element_name,
|
|
1715
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1716
|
-
text: "Verify element exists in page",
|
|
1717
|
-
screenshotId,
|
|
1718
|
-
result: error
|
|
1719
|
-
? {
|
|
1720
|
-
status: "FAILED",
|
|
1721
|
-
startTime,
|
|
1722
|
-
endTime,
|
|
1723
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1724
|
-
}
|
|
1725
|
-
: {
|
|
1726
|
-
status: "PASSED",
|
|
1727
|
-
startTime,
|
|
1728
|
-
endTime,
|
|
1729
|
-
},
|
|
1730
|
-
info: info,
|
|
1731
|
-
});
|
|
1834
|
+
_commandFinally(state, this);
|
|
1732
1835
|
}
|
|
1733
1836
|
}
|
|
1734
1837
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
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
|
+
};
|
|
1740
1850
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1741
|
-
const info = {};
|
|
1742
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1743
|
-
info.operation = "extract";
|
|
1744
|
-
info.selectors = selectors;
|
|
1745
1851
|
try {
|
|
1746
|
-
|
|
1747
|
-
await this._highlightElements(element);
|
|
1748
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1852
|
+
await _preCommand(state, this);
|
|
1749
1853
|
switch (attribute) {
|
|
1750
1854
|
case "inner_text":
|
|
1751
|
-
|
|
1855
|
+
state.value = await state.element.innerText();
|
|
1752
1856
|
break;
|
|
1753
1857
|
case "href":
|
|
1754
|
-
|
|
1858
|
+
state.value = await state.element.getAttribute("href");
|
|
1755
1859
|
break;
|
|
1756
1860
|
case "value":
|
|
1757
|
-
|
|
1861
|
+
state.value = await state.element.inputValue();
|
|
1758
1862
|
break;
|
|
1759
1863
|
default:
|
|
1760
|
-
|
|
1864
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1761
1865
|
break;
|
|
1762
1866
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
this.setTestData({ [variable]: info.value }, world);
|
|
1768
|
-
this.logger.info("set test data: " + variable + "=" + info.value);
|
|
1769
|
-
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;
|
|
1770
1871
|
}
|
|
1771
1872
|
catch (e) {
|
|
1772
|
-
|
|
1773
|
-
this.logger.error("extract failed " + info.log);
|
|
1774
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1775
|
-
info.screenshotPath = screenshotPath;
|
|
1776
|
-
Object.assign(e, { info: info });
|
|
1777
|
-
error = e;
|
|
1778
|
-
throw e;
|
|
1873
|
+
await _commandError(state, e, this);
|
|
1779
1874
|
}
|
|
1780
1875
|
finally {
|
|
1781
|
-
|
|
1782
|
-
this._reportToWorld(world, {
|
|
1783
|
-
element_name: selectors.element_name,
|
|
1784
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1785
|
-
variable: variable,
|
|
1786
|
-
value: info.value,
|
|
1787
|
-
text: "Extract attribute from element",
|
|
1788
|
-
screenshotId,
|
|
1789
|
-
result: error
|
|
1790
|
-
? {
|
|
1791
|
-
status: "FAILED",
|
|
1792
|
-
startTime,
|
|
1793
|
-
endTime,
|
|
1794
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1795
|
-
}
|
|
1796
|
-
: {
|
|
1797
|
-
status: "PASSED",
|
|
1798
|
-
startTime,
|
|
1799
|
-
endTime,
|
|
1800
|
-
},
|
|
1801
|
-
info: info,
|
|
1802
|
-
});
|
|
1876
|
+
_commandFinally(state, this);
|
|
1803
1877
|
}
|
|
1804
1878
|
}
|
|
1805
1879
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1876,7 +1950,8 @@ class StableBrowser {
|
|
|
1876
1950
|
catch (e) {
|
|
1877
1951
|
errorCount++;
|
|
1878
1952
|
if (errorCount > 3) {
|
|
1879
|
-
throw e;
|
|
1953
|
+
// throw e;
|
|
1954
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1880
1955
|
}
|
|
1881
1956
|
// ignore
|
|
1882
1957
|
}
|
|
@@ -1987,11 +2062,12 @@ class StableBrowser {
|
|
|
1987
2062
|
info.screenshotPath = screenshotPath;
|
|
1988
2063
|
Object.assign(e, { info: info });
|
|
1989
2064
|
error = e;
|
|
1990
|
-
throw e;
|
|
2065
|
+
// throw e;
|
|
2066
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
1991
2067
|
}
|
|
1992
2068
|
finally {
|
|
1993
2069
|
const endTime = Date.now();
|
|
1994
|
-
|
|
2070
|
+
_reportToWorld(world, {
|
|
1995
2071
|
type: Types.VERIFY_PAGE_PATH,
|
|
1996
2072
|
text: "Verify page path",
|
|
1997
2073
|
screenshotId,
|
|
@@ -2000,7 +2076,7 @@ class StableBrowser {
|
|
|
2000
2076
|
status: "FAILED",
|
|
2001
2077
|
startTime,
|
|
2002
2078
|
endTime,
|
|
2003
|
-
message: error
|
|
2079
|
+
message: error?.message,
|
|
2004
2080
|
}
|
|
2005
2081
|
: {
|
|
2006
2082
|
status: "PASSED",
|
|
@@ -2012,52 +2088,59 @@ class StableBrowser {
|
|
|
2012
2088
|
}
|
|
2013
2089
|
}
|
|
2014
2090
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2015
|
-
|
|
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
|
+
};
|
|
2016
2104
|
const timeout = this._getLoadTimeout(options);
|
|
2017
|
-
let error = null;
|
|
2018
|
-
let screenshotId = null;
|
|
2019
|
-
let screenshotPath = null;
|
|
2020
2105
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2021
|
-
const info = {};
|
|
2022
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2023
|
-
info.operation = "verifyTextExistInPage";
|
|
2024
2106
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2025
2107
|
if (newValue !== text) {
|
|
2026
2108
|
this.logger.info(text + "=" + newValue);
|
|
2027
2109
|
text = newValue;
|
|
2028
2110
|
}
|
|
2029
|
-
info.text = text;
|
|
2030
2111
|
let dateAlternatives = findDateAlternatives(text);
|
|
2031
2112
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2032
2113
|
try {
|
|
2114
|
+
await _preCommand(state, this);
|
|
2115
|
+
state.info.text = text;
|
|
2033
2116
|
while (true) {
|
|
2034
2117
|
const frames = this.page.frames();
|
|
2035
2118
|
let results = [];
|
|
2036
2119
|
for (let i = 0; i < frames.length; i++) {
|
|
2037
2120
|
if (dateAlternatives.date) {
|
|
2038
2121
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2039
|
-
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, {});
|
|
2040
2123
|
result.frame = frames[i];
|
|
2041
2124
|
results.push(result);
|
|
2042
2125
|
}
|
|
2043
2126
|
}
|
|
2044
2127
|
else if (numberAlternatives.number) {
|
|
2045
2128
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2046
|
-
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, {});
|
|
2047
2130
|
result.frame = frames[i];
|
|
2048
2131
|
results.push(result);
|
|
2049
2132
|
}
|
|
2050
2133
|
}
|
|
2051
2134
|
else {
|
|
2052
|
-
const result = await this._locateElementByText(frames[i], text, "
|
|
2135
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2053
2136
|
result.frame = frames[i];
|
|
2054
2137
|
results.push(result);
|
|
2055
2138
|
}
|
|
2056
2139
|
}
|
|
2057
|
-
info.results = results;
|
|
2140
|
+
state.info.results = results;
|
|
2058
2141
|
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2059
2142
|
if (resultWithElementsFound.length === 0) {
|
|
2060
|
-
if (Date.now() - startTime > timeout) {
|
|
2143
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2061
2144
|
throw new Error(`Text ${text} not found in page`);
|
|
2062
2145
|
}
|
|
2063
2146
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2069,44 +2152,89 @@ class StableBrowser {
|
|
|
2069
2152
|
await this._highlightElements(frame, dataAttribute);
|
|
2070
2153
|
const element = await frame.$(dataAttribute);
|
|
2071
2154
|
if (element) {
|
|
2072
|
-
await this.scrollIfNeeded(element, info);
|
|
2155
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2073
2156
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2074
2157
|
}
|
|
2075
2158
|
}
|
|
2076
|
-
|
|
2077
|
-
return info;
|
|
2159
|
+
await _screenshot(state, this);
|
|
2160
|
+
return state.info;
|
|
2078
2161
|
}
|
|
2079
2162
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2080
2163
|
}
|
|
2081
2164
|
catch (e) {
|
|
2082
|
-
|
|
2083
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2084
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2085
|
-
info.screenshotPath = screenshotPath;
|
|
2086
|
-
Object.assign(e, { info: info });
|
|
2087
|
-
error = e;
|
|
2088
|
-
throw e;
|
|
2165
|
+
await _commandError(state, e, this);
|
|
2089
2166
|
}
|
|
2090
2167
|
finally {
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
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
|
+
}
|
|
2102
2207
|
}
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
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);
|
|
2110
2238
|
}
|
|
2111
2239
|
}
|
|
2112
2240
|
_getServerUrl() {
|
|
@@ -2169,11 +2297,12 @@ class StableBrowser {
|
|
|
2169
2297
|
info.screenshotPath = screenshotPath;
|
|
2170
2298
|
Object.assign(e, { info: info });
|
|
2171
2299
|
error = e;
|
|
2172
|
-
throw e;
|
|
2300
|
+
// throw e;
|
|
2301
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2173
2302
|
}
|
|
2174
2303
|
finally {
|
|
2175
2304
|
const endTime = Date.now();
|
|
2176
|
-
|
|
2305
|
+
_reportToWorld(world, {
|
|
2177
2306
|
type: Types.VERIFY_VISUAL,
|
|
2178
2307
|
text: "Visual verification",
|
|
2179
2308
|
screenshotId,
|
|
@@ -2182,7 +2311,7 @@ class StableBrowser {
|
|
|
2182
2311
|
status: "FAILED",
|
|
2183
2312
|
startTime,
|
|
2184
2313
|
endTime,
|
|
2185
|
-
message: error
|
|
2314
|
+
message: error?.message,
|
|
2186
2315
|
}
|
|
2187
2316
|
: {
|
|
2188
2317
|
status: "PASSED",
|
|
@@ -2214,7 +2343,7 @@ class StableBrowser {
|
|
|
2214
2343
|
this.logger.info("Table data verified");
|
|
2215
2344
|
}
|
|
2216
2345
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2217
|
-
|
|
2346
|
+
_validateSelectors(selectors);
|
|
2218
2347
|
const startTime = Date.now();
|
|
2219
2348
|
let error = null;
|
|
2220
2349
|
let screenshotId = null;
|
|
@@ -2236,11 +2365,12 @@ class StableBrowser {
|
|
|
2236
2365
|
info.screenshotPath = screenshotPath;
|
|
2237
2366
|
Object.assign(e, { info: info });
|
|
2238
2367
|
error = e;
|
|
2239
|
-
throw e;
|
|
2368
|
+
// throw e;
|
|
2369
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2240
2370
|
}
|
|
2241
2371
|
finally {
|
|
2242
2372
|
const endTime = Date.now();
|
|
2243
|
-
|
|
2373
|
+
_reportToWorld(world, {
|
|
2244
2374
|
element_name: selectors.element_name,
|
|
2245
2375
|
type: Types.GET_TABLE_DATA,
|
|
2246
2376
|
text: "Get table data",
|
|
@@ -2250,7 +2380,7 @@ class StableBrowser {
|
|
|
2250
2380
|
status: "FAILED",
|
|
2251
2381
|
startTime,
|
|
2252
2382
|
endTime,
|
|
2253
|
-
message: error
|
|
2383
|
+
message: error?.message,
|
|
2254
2384
|
}
|
|
2255
2385
|
: {
|
|
2256
2386
|
status: "PASSED",
|
|
@@ -2262,7 +2392,7 @@ class StableBrowser {
|
|
|
2262
2392
|
}
|
|
2263
2393
|
}
|
|
2264
2394
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2265
|
-
|
|
2395
|
+
_validateSelectors(selectors);
|
|
2266
2396
|
if (!query) {
|
|
2267
2397
|
throw new Error("query is null");
|
|
2268
2398
|
}
|
|
@@ -2401,11 +2531,12 @@ class StableBrowser {
|
|
|
2401
2531
|
info.screenshotPath = screenshotPath;
|
|
2402
2532
|
Object.assign(e, { info: info });
|
|
2403
2533
|
error = e;
|
|
2404
|
-
throw e;
|
|
2534
|
+
// throw e;
|
|
2535
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2405
2536
|
}
|
|
2406
2537
|
finally {
|
|
2407
2538
|
const endTime = Date.now();
|
|
2408
|
-
|
|
2539
|
+
_reportToWorld(world, {
|
|
2409
2540
|
element_name: selectors.element_name,
|
|
2410
2541
|
type: Types.ANALYZE_TABLE,
|
|
2411
2542
|
text: "Analyze table",
|
|
@@ -2415,7 +2546,7 @@ class StableBrowser {
|
|
|
2415
2546
|
status: "FAILED",
|
|
2416
2547
|
startTime,
|
|
2417
2548
|
endTime,
|
|
2418
|
-
message: error
|
|
2549
|
+
message: error?.message,
|
|
2419
2550
|
}
|
|
2420
2551
|
: {
|
|
2421
2552
|
status: "PASSED",
|
|
@@ -2427,27 +2558,7 @@ class StableBrowser {
|
|
|
2427
2558
|
}
|
|
2428
2559
|
}
|
|
2429
2560
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2430
|
-
|
|
2431
|
-
return value;
|
|
2432
|
-
}
|
|
2433
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2434
|
-
let regex = /{{(.*?)}}/g;
|
|
2435
|
-
let matches = value.match(regex);
|
|
2436
|
-
if (matches) {
|
|
2437
|
-
const testData = this.getTestData(world);
|
|
2438
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2439
|
-
let match = matches[i];
|
|
2440
|
-
let key = match.substring(2, match.length - 2);
|
|
2441
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2442
|
-
if (newValue !== null) {
|
|
2443
|
-
value = value.replace(match, newValue);
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2448
|
-
return await decrypt(value, null, totpWait);
|
|
2449
|
-
}
|
|
2450
|
-
return value;
|
|
2561
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2451
2562
|
}
|
|
2452
2563
|
_getLoadTimeout(options) {
|
|
2453
2564
|
let timeout = 15000;
|
|
@@ -2484,13 +2595,13 @@ class StableBrowser {
|
|
|
2484
2595
|
}
|
|
2485
2596
|
catch (e) {
|
|
2486
2597
|
if (e.label === "networkidle") {
|
|
2487
|
-
console.log("
|
|
2598
|
+
console.log("waited for the network to be idle timeout");
|
|
2488
2599
|
}
|
|
2489
2600
|
else if (e.label === "load") {
|
|
2490
|
-
console.log("
|
|
2601
|
+
console.log("waited for the load timeout");
|
|
2491
2602
|
}
|
|
2492
2603
|
else if (e.label === "domcontentloaded") {
|
|
2493
|
-
console.log("
|
|
2604
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2494
2605
|
}
|
|
2495
2606
|
console.log(".");
|
|
2496
2607
|
}
|
|
@@ -2498,7 +2609,7 @@ class StableBrowser {
|
|
|
2498
2609
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2499
2610
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2500
2611
|
const endTime = Date.now();
|
|
2501
|
-
|
|
2612
|
+
_reportToWorld(world, {
|
|
2502
2613
|
type: Types.GET_PAGE_STATUS,
|
|
2503
2614
|
text: "Wait for page load",
|
|
2504
2615
|
screenshotId,
|
|
@@ -2507,7 +2618,7 @@ class StableBrowser {
|
|
|
2507
2618
|
status: "FAILED",
|
|
2508
2619
|
startTime,
|
|
2509
2620
|
endTime,
|
|
2510
|
-
message: error
|
|
2621
|
+
message: error?.message,
|
|
2511
2622
|
}
|
|
2512
2623
|
: {
|
|
2513
2624
|
status: "PASSED",
|
|
@@ -2518,41 +2629,35 @@ class StableBrowser {
|
|
|
2518
2629
|
}
|
|
2519
2630
|
}
|
|
2520
2631
|
async closePage(options = {}, world = null) {
|
|
2521
|
-
const
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
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
|
+
};
|
|
2526
2644
|
try {
|
|
2645
|
+
await _preCommand(state, this);
|
|
2527
2646
|
await this.page.close();
|
|
2528
2647
|
}
|
|
2529
2648
|
catch (e) {
|
|
2530
2649
|
console.log(".");
|
|
2650
|
+
await _commandError(state, e, this);
|
|
2531
2651
|
}
|
|
2532
2652
|
finally {
|
|
2533
|
-
|
|
2534
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2535
|
-
const endTime = Date.now();
|
|
2536
|
-
this._reportToWorld(world, {
|
|
2537
|
-
type: Types.CLOSE_PAGE,
|
|
2538
|
-
text: "close page",
|
|
2539
|
-
screenshotId,
|
|
2540
|
-
result: error
|
|
2541
|
-
? {
|
|
2542
|
-
status: "FAILED",
|
|
2543
|
-
startTime,
|
|
2544
|
-
endTime,
|
|
2545
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2546
|
-
}
|
|
2547
|
-
: {
|
|
2548
|
-
status: "PASSED",
|
|
2549
|
-
startTime,
|
|
2550
|
-
endTime,
|
|
2551
|
-
},
|
|
2552
|
-
info: info,
|
|
2553
|
-
});
|
|
2653
|
+
_commandFinally(state, this);
|
|
2554
2654
|
}
|
|
2555
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
|
+
}
|
|
2556
2661
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2557
2662
|
const startTime = Date.now();
|
|
2558
2663
|
let error = null;
|
|
@@ -2570,12 +2675,13 @@ class StableBrowser {
|
|
|
2570
2675
|
}
|
|
2571
2676
|
catch (e) {
|
|
2572
2677
|
console.log(".");
|
|
2678
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2573
2679
|
}
|
|
2574
2680
|
finally {
|
|
2575
2681
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2576
2682
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2577
2683
|
const endTime = Date.now();
|
|
2578
|
-
|
|
2684
|
+
_reportToWorld(world, {
|
|
2579
2685
|
type: Types.SET_VIEWPORT,
|
|
2580
2686
|
text: "set viewport size to " + width + "x" + hight,
|
|
2581
2687
|
screenshotId,
|
|
@@ -2584,7 +2690,7 @@ class StableBrowser {
|
|
|
2584
2690
|
status: "FAILED",
|
|
2585
2691
|
startTime,
|
|
2586
2692
|
endTime,
|
|
2587
|
-
message: error
|
|
2693
|
+
message: error?.message,
|
|
2588
2694
|
}
|
|
2589
2695
|
: {
|
|
2590
2696
|
status: "PASSED",
|
|
@@ -2606,12 +2712,13 @@ class StableBrowser {
|
|
|
2606
2712
|
}
|
|
2607
2713
|
catch (e) {
|
|
2608
2714
|
console.log(".");
|
|
2715
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2609
2716
|
}
|
|
2610
2717
|
finally {
|
|
2611
2718
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2612
2719
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2613
2720
|
const endTime = Date.now();
|
|
2614
|
-
|
|
2721
|
+
_reportToWorld(world, {
|
|
2615
2722
|
type: Types.GET_PAGE_STATUS,
|
|
2616
2723
|
text: "page relaod",
|
|
2617
2724
|
screenshotId,
|
|
@@ -2620,7 +2727,7 @@ class StableBrowser {
|
|
|
2620
2727
|
status: "FAILED",
|
|
2621
2728
|
startTime,
|
|
2622
2729
|
endTime,
|
|
2623
|
-
message: error
|
|
2730
|
+
message: error?.message,
|
|
2624
2731
|
}
|
|
2625
2732
|
: {
|
|
2626
2733
|
status: "PASSED",
|
|
@@ -2633,40 +2740,51 @@ class StableBrowser {
|
|
|
2633
2740
|
}
|
|
2634
2741
|
async scrollIfNeeded(element, info) {
|
|
2635
2742
|
try {
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
if (rect &&
|
|
2639
|
-
rect.top >= 0 &&
|
|
2640
|
-
rect.left >= 0 &&
|
|
2641
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2642
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2643
|
-
return false;
|
|
2644
|
-
}
|
|
2645
|
-
else {
|
|
2646
|
-
node.scrollIntoView({
|
|
2647
|
-
behavior: "smooth",
|
|
2648
|
-
block: "center",
|
|
2649
|
-
inline: "center",
|
|
2650
|
-
});
|
|
2651
|
-
return true;
|
|
2652
|
-
}
|
|
2743
|
+
await element.scrollIntoViewIfNeeded({
|
|
2744
|
+
timeout: 2000,
|
|
2653
2745
|
});
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
}
|
|
2746
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2747
|
+
if (info) {
|
|
2748
|
+
info.box = await element.boundingBox({
|
|
2749
|
+
timeout: 1000,
|
|
2750
|
+
});
|
|
2659
2751
|
}
|
|
2660
2752
|
}
|
|
2661
2753
|
catch (e) {
|
|
2662
|
-
console.log("
|
|
2754
|
+
console.log("#-#");
|
|
2663
2755
|
}
|
|
2664
2756
|
}
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
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
|
+
}
|
|
2668
2787
|
}
|
|
2669
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2670
2788
|
}
|
|
2671
2789
|
}
|
|
2672
2790
|
function createTimedPromise(promise, label) {
|
|
@@ -2820,5 +2938,10 @@ const KEYBOARD_EVENTS = [
|
|
|
2820
2938
|
"TVAntennaCable",
|
|
2821
2939
|
"TVAudioDescription",
|
|
2822
2940
|
];
|
|
2941
|
+
function unEscapeString(str) {
|
|
2942
|
+
const placeholder = "__NEWLINE__";
|
|
2943
|
+
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2944
|
+
return str;
|
|
2945
|
+
}
|
|
2823
2946
|
export { StableBrowser };
|
|
2824
2947
|
//# sourceMappingURL=stable_browser.js.map
|