automation_model 1.0.433-dev → 1.0.433
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/README.md +130 -0
- package/lib/api.d.ts +43 -1
- package/lib/api.js +241 -41
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +5 -2
- package/lib/auto_page.js +177 -46
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +7 -3
- package/lib/browser_manager.js +157 -49
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +2 -0
- package/lib/bruno.js +371 -0
- package/lib/bruno.js.map +1 -0
- package/lib/command_common.d.ts +6 -0
- package/lib/command_common.js +198 -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 +206 -0
- package/lib/error-messages.js.map +1 -0
- package/lib/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +5 -2
- package/lib/init_browser.js +124 -7
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.d.ts +7 -0
- package/lib/locate_element.js +215 -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/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.d.ts +3 -0
- package/lib/network.js +183 -0
- package/lib/network.js.map +1 -0
- package/lib/scripts/axe.mini.js +12 -0
- package/lib/stable_browser.d.ts +103 -36
- package/lib/stable_browser.js +1770 -1247
- 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/table_helper.d.ts +19 -0
- package/lib/table_helper.js +116 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +6 -0
- package/lib/test_context.js +14 -10
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +22 -2
- package/lib/utils.js +664 -11
- package/lib/utils.js.map +1 -1
- package/package.json +14 -8
package/lib/stable_browser.js
CHANGED
|
@@ -2,21 +2,31 @@
|
|
|
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 { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
|
|
15
14
|
import csv from "csv-parser";
|
|
16
15
|
import { Readable } from "node:stream";
|
|
17
16
|
import readline from "readline";
|
|
18
|
-
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
|
+
import { getTestData } from "./auto_page.js";
|
|
19
|
+
import { locate_element } from "./locate_element.js";
|
|
20
|
+
import { randomUUID } from "crypto";
|
|
21
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
22
|
+
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
23
|
+
import { LocatorLog } from "./locator_log.js";
|
|
24
|
+
import axios from "axios";
|
|
25
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
26
|
+
import { loadBrunoParams } from "./bruno.js";
|
|
27
|
+
export const Types = {
|
|
19
28
|
CLICK: "click_element",
|
|
29
|
+
WAIT_ELEMENT: "wait_element",
|
|
20
30
|
NAVIGATE: "navigate",
|
|
21
31
|
FILL: "fill_element",
|
|
22
32
|
EXECUTE: "execute_page_method",
|
|
@@ -26,6 +36,8 @@ const Types = {
|
|
|
26
36
|
GET_PAGE_STATUS: "get_page_status",
|
|
27
37
|
CLICK_ROW_ACTION: "click_row_action",
|
|
28
38
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
39
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
40
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
29
41
|
ANALYZE_TABLE: "analyze_table",
|
|
30
42
|
SELECT: "select_combobox",
|
|
31
43
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -36,21 +48,41 @@ const Types = {
|
|
|
36
48
|
UNCHECK: "uncheck_element",
|
|
37
49
|
EXTRACT: "extract_attribute",
|
|
38
50
|
CLOSE_PAGE: "close_page",
|
|
51
|
+
TABLE_OPERATION: "table_operation",
|
|
39
52
|
SET_DATE_TIME: "set_date_time",
|
|
40
53
|
SET_VIEWPORT: "set_viewport",
|
|
41
54
|
VERIFY_VISUAL: "verify_visual",
|
|
42
55
|
LOAD_DATA: "load_data",
|
|
43
56
|
SET_INPUT: "set_input",
|
|
57
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
58
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
59
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
60
|
+
BRUNO: "bruno",
|
|
61
|
+
};
|
|
62
|
+
export const apps = {};
|
|
63
|
+
const formatElementName = (elementName) => {
|
|
64
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
44
65
|
};
|
|
45
66
|
class StableBrowser {
|
|
46
|
-
|
|
67
|
+
browser;
|
|
68
|
+
page;
|
|
69
|
+
logger;
|
|
70
|
+
context;
|
|
71
|
+
world;
|
|
72
|
+
project_path = null;
|
|
73
|
+
webLogFile = null;
|
|
74
|
+
networkLogger = null;
|
|
75
|
+
configuration = null;
|
|
76
|
+
appName = "main";
|
|
77
|
+
tags = null;
|
|
78
|
+
isRecording = false;
|
|
79
|
+
initSnapshotTaken = false;
|
|
80
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
47
81
|
this.browser = browser;
|
|
48
82
|
this.page = page;
|
|
49
83
|
this.logger = logger;
|
|
50
84
|
this.context = context;
|
|
51
|
-
this.
|
|
52
|
-
this.webLogFile = null;
|
|
53
|
-
this.configuration = null;
|
|
85
|
+
this.world = world;
|
|
54
86
|
if (!this.logger) {
|
|
55
87
|
this.logger = console;
|
|
56
88
|
}
|
|
@@ -75,17 +107,32 @@ class StableBrowser {
|
|
|
75
107
|
catch (e) {
|
|
76
108
|
this.logger.error("unable to read ai_config.json");
|
|
77
109
|
}
|
|
78
|
-
const logFolder = path.join(this.project_path, "logs", "web");
|
|
79
|
-
this.webLogFile = this.getWebLogFile(logFolder);
|
|
80
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
81
|
-
this.registerRequestListener();
|
|
82
|
-
context.pages = [this.page];
|
|
83
110
|
context.pageLoading = { status: false };
|
|
111
|
+
context.pages = [this.page];
|
|
112
|
+
const logFolder = path.join(this.project_path, "logs", "web");
|
|
113
|
+
this.world = world;
|
|
114
|
+
this.registerEventListeners(this.context);
|
|
115
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
116
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
117
|
+
}
|
|
118
|
+
registerEventListeners(context) {
|
|
119
|
+
this.registerConsoleLogListener(this.page, context);
|
|
120
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
121
|
+
if (!context.pageLoading) {
|
|
122
|
+
context.pageLoading = { status: false };
|
|
123
|
+
}
|
|
84
124
|
context.playContext.on("page", async function (page) {
|
|
125
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
126
|
+
console.log("close unexpected popups");
|
|
127
|
+
await page.close();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
85
130
|
context.pageLoading.status = true;
|
|
86
131
|
this.page = page;
|
|
87
132
|
context.page = page;
|
|
88
133
|
context.pages.push(page);
|
|
134
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
135
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
89
136
|
page.on("close", async () => {
|
|
90
137
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
91
138
|
this.context.pages.pop();
|
|
@@ -110,117 +157,164 @@ class StableBrowser {
|
|
|
110
157
|
context.pageLoading.status = false;
|
|
111
158
|
}.bind(this));
|
|
112
159
|
}
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
160
|
+
async switchApp(appName) {
|
|
161
|
+
// check if the current app (this.appName) is the same as the new app
|
|
162
|
+
if (this.appName === appName) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
let newContextCreated = false;
|
|
166
|
+
if (!apps[appName]) {
|
|
167
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
168
|
+
newContextCreated = true;
|
|
169
|
+
apps[appName] = {
|
|
170
|
+
context: newContext,
|
|
171
|
+
browser: newContext.browser,
|
|
172
|
+
page: newContext.page,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const tempContext = {};
|
|
176
|
+
_copyContext(this, tempContext);
|
|
177
|
+
_copyContext(apps[appName], this);
|
|
178
|
+
apps[this.appName] = tempContext;
|
|
179
|
+
this.appName = appName;
|
|
180
|
+
if (newContextCreated) {
|
|
181
|
+
this.registerEventListeners(this.context);
|
|
182
|
+
await this.goto(this.context.environment.baseUrl);
|
|
183
|
+
await this.waitForPageLoad();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async switchTab(tabTitleOrIndex) {
|
|
187
|
+
// first check if the tabNameOrIndex is a number
|
|
188
|
+
let index = parseInt(tabTitleOrIndex);
|
|
189
|
+
if (!isNaN(index)) {
|
|
190
|
+
if (index >= 0 && index < this.context.pages.length) {
|
|
191
|
+
this.page = this.context.pages[index];
|
|
192
|
+
this.context.page = this.page;
|
|
193
|
+
await this.page.bringToFront();
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
116
196
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
197
|
+
// if the tabNameOrIndex is a string, find the tab by name
|
|
198
|
+
for (let i = 0; i < this.context.pages.length; i++) {
|
|
199
|
+
let page = this.context.pages[i];
|
|
200
|
+
let title = await page.title();
|
|
201
|
+
if (title.includes(tabTitleOrIndex)) {
|
|
202
|
+
this.page = page;
|
|
203
|
+
this.context.page = this.page;
|
|
204
|
+
await this.page.bringToFront();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
120
207
|
}
|
|
121
|
-
|
|
122
|
-
return path.join(logFolder, fileName);
|
|
208
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
123
209
|
}
|
|
124
|
-
registerConsoleLogListener(page, context
|
|
210
|
+
registerConsoleLogListener(page, context) {
|
|
125
211
|
if (!this.context.webLogger) {
|
|
126
212
|
this.context.webLogger = [];
|
|
127
213
|
}
|
|
128
214
|
page.on("console", async (msg) => {
|
|
129
|
-
|
|
215
|
+
const obj = {
|
|
130
216
|
type: msg.type(),
|
|
131
217
|
text: msg.text(),
|
|
132
218
|
location: msg.location(),
|
|
133
219
|
time: new Date().toISOString(),
|
|
134
|
-
}
|
|
135
|
-
|
|
220
|
+
};
|
|
221
|
+
this.context.webLogger.push(obj);
|
|
222
|
+
if (msg.type() === "error") {
|
|
223
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
224
|
+
}
|
|
136
225
|
});
|
|
137
226
|
}
|
|
138
|
-
registerRequestListener() {
|
|
139
|
-
this.
|
|
227
|
+
registerRequestListener(page, context, logFile) {
|
|
228
|
+
if (!this.context.networkLogger) {
|
|
229
|
+
this.context.networkLogger = [];
|
|
230
|
+
}
|
|
231
|
+
page.on("request", async (data) => {
|
|
232
|
+
const startTime = new Date().getTime();
|
|
140
233
|
try {
|
|
141
|
-
const pageUrl = new URL(
|
|
234
|
+
const pageUrl = new URL(page.url());
|
|
142
235
|
const requestUrl = new URL(data.url());
|
|
143
236
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
144
237
|
const method = data.method();
|
|
145
|
-
if (
|
|
238
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
146
239
|
const token = await data.headerValue("Authorization");
|
|
147
240
|
if (token) {
|
|
148
|
-
|
|
241
|
+
context.authtoken = token;
|
|
149
242
|
}
|
|
150
243
|
}
|
|
151
244
|
}
|
|
245
|
+
const response = await data.response();
|
|
246
|
+
const endTime = new Date().getTime();
|
|
247
|
+
const obj = {
|
|
248
|
+
url: data.url(),
|
|
249
|
+
method: data.method(),
|
|
250
|
+
postData: data.postData(),
|
|
251
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
252
|
+
duration: endTime - startTime,
|
|
253
|
+
startTime,
|
|
254
|
+
};
|
|
255
|
+
context.networkLogger.push(obj);
|
|
256
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
152
257
|
}
|
|
153
258
|
catch (error) {
|
|
154
|
-
console.error("Error in request listener", error);
|
|
259
|
+
// console.error("Error in request listener", error);
|
|
260
|
+
context.networkLogger.push({
|
|
261
|
+
error: "not able to listen",
|
|
262
|
+
message: error.message,
|
|
263
|
+
stack: error.stack,
|
|
264
|
+
time: new Date().toISOString(),
|
|
265
|
+
});
|
|
266
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
155
267
|
}
|
|
156
268
|
});
|
|
157
269
|
}
|
|
158
270
|
// async closeUnexpectedPopups() {
|
|
159
271
|
// await closeUnexpectedPopups(this.page);
|
|
160
272
|
// }
|
|
161
|
-
async goto(url) {
|
|
273
|
+
async goto(url, world = null) {
|
|
274
|
+
if (!url) {
|
|
275
|
+
throw new Error("url is null, verify that the environment file is correct");
|
|
276
|
+
}
|
|
162
277
|
if (!url.startsWith("http")) {
|
|
163
278
|
url = "https://" + url;
|
|
164
279
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (!_params || typeof text !== "string") {
|
|
185
|
-
return text;
|
|
280
|
+
const state = {
|
|
281
|
+
value: url,
|
|
282
|
+
world: world,
|
|
283
|
+
type: Types.NAVIGATE,
|
|
284
|
+
text: `Navigate Page to: ${url}`,
|
|
285
|
+
operation: "goto",
|
|
286
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
287
|
+
info: {},
|
|
288
|
+
locate: false,
|
|
289
|
+
scroll: false,
|
|
290
|
+
screenshot: false,
|
|
291
|
+
highlight: false,
|
|
292
|
+
};
|
|
293
|
+
try {
|
|
294
|
+
await _preCommand(state, this);
|
|
295
|
+
await this.page.goto(url, {
|
|
296
|
+
timeout: 60000,
|
|
297
|
+
});
|
|
298
|
+
await _screenshot(state, this);
|
|
186
299
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// remove the _ prefix
|
|
191
|
-
regValue = key.substring(1);
|
|
192
|
-
}
|
|
193
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
300
|
+
catch (error) {
|
|
301
|
+
console.error("Error on goto", error);
|
|
302
|
+
_commandError(state, error, this);
|
|
194
303
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
198
|
-
// check if not null
|
|
199
|
-
if (!locator) {
|
|
200
|
-
return locator;
|
|
304
|
+
finally {
|
|
305
|
+
await _commandFinally(state, this);
|
|
201
306
|
}
|
|
202
|
-
// clone the locator
|
|
203
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
204
|
-
this.scanAndManipulate(locator, _params);
|
|
205
|
-
return locator;
|
|
206
307
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
else if (this._isObject(currentObj[key])) {
|
|
217
|
-
// Recursively scan nested objects
|
|
218
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
308
|
+
async _getLocator(locator, scope, _params) {
|
|
309
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
310
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
311
|
+
for (let key in locator) {
|
|
312
|
+
if (typeof locator[key] !== "string")
|
|
313
|
+
continue;
|
|
314
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
315
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
219
316
|
}
|
|
220
317
|
}
|
|
221
|
-
}
|
|
222
|
-
_getLocator(locator, scope, _params) {
|
|
223
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
224
318
|
let locatorReturn;
|
|
225
319
|
if (locator.role) {
|
|
226
320
|
if (locator.role[1].nameReg) {
|
|
@@ -228,7 +322,7 @@ class StableBrowser {
|
|
|
228
322
|
delete locator.role[1].nameReg;
|
|
229
323
|
}
|
|
230
324
|
// if (locator.role[1].name) {
|
|
231
|
-
// locator.role[1].name =
|
|
325
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
232
326
|
// }
|
|
233
327
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
234
328
|
}
|
|
@@ -247,7 +341,7 @@ class StableBrowser {
|
|
|
247
341
|
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
248
342
|
}
|
|
249
343
|
}
|
|
250
|
-
if (locator
|
|
344
|
+
if (locator?.engine) {
|
|
251
345
|
if (locator.engine === "css") {
|
|
252
346
|
locatorReturn = scope.locator(locator.selector);
|
|
253
347
|
}
|
|
@@ -268,192 +362,181 @@ class StableBrowser {
|
|
|
268
362
|
return locatorReturn;
|
|
269
363
|
}
|
|
270
364
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
271
|
-
|
|
365
|
+
if (css && css.locator) {
|
|
366
|
+
css = css.locator;
|
|
367
|
+
}
|
|
368
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
272
369
|
if (result.elementCount === 0) {
|
|
273
370
|
return;
|
|
274
371
|
}
|
|
275
|
-
let textElementCss = "[data-blinq-id
|
|
372
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
276
373
|
// css climb to parent element
|
|
277
374
|
const climbArray = [];
|
|
278
375
|
for (let i = 0; i < climb; i++) {
|
|
279
376
|
climbArray.push("..");
|
|
280
377
|
}
|
|
281
378
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
320
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
321
|
-
if (!shadowElement) {
|
|
322
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
326
|
-
elements = elements.concat(shadowElements);
|
|
327
|
-
}
|
|
328
|
-
let randomToken = null;
|
|
329
|
-
const foundElements = [];
|
|
330
|
-
if (regex) {
|
|
331
|
-
let regexpSearch = new RegExp(text, "im");
|
|
332
|
-
for (let i = 0; i < elements.length; i++) {
|
|
333
|
-
const element = elements[i];
|
|
334
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
335
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
336
|
-
foundElements.push(element);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
text = text.trim();
|
|
342
|
-
for (let i = 0; i < elements.length; i++) {
|
|
343
|
-
const element = elements[i];
|
|
344
|
-
if (partial) {
|
|
345
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
346
|
-
(element.value && element.value.includes(text))) {
|
|
347
|
-
foundElements.push(element);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
352
|
-
(element.value && element.value === text)) {
|
|
353
|
-
foundElements.push(element);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
let noChildElements = [];
|
|
359
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
360
|
-
let element = foundElements[i];
|
|
361
|
-
let hasChild = false;
|
|
362
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
363
|
-
if (i === j) {
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
if (isParent(element, foundElements[j])) {
|
|
367
|
-
hasChild = true;
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (!hasChild) {
|
|
372
|
-
noChildElements.push(element);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
let elementCount = 0;
|
|
376
|
-
if (noChildElements.length > 0) {
|
|
377
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
378
|
-
if (randomToken === null) {
|
|
379
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
380
|
-
}
|
|
381
|
-
let element = noChildElements[i];
|
|
382
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
383
|
-
elementCount++;
|
|
384
|
-
}
|
|
379
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
380
|
+
if (css) {
|
|
381
|
+
resultCss = resultCss + " >> " + css;
|
|
382
|
+
}
|
|
383
|
+
return resultCss;
|
|
384
|
+
}
|
|
385
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
386
|
+
const query = `${_convertToRegexQuery(text1, regex1, !partial1, ignoreCase)}`;
|
|
387
|
+
const locator = scope.locator(query);
|
|
388
|
+
const count = await locator.count();
|
|
389
|
+
if (!tag1) {
|
|
390
|
+
tag1 = "*";
|
|
391
|
+
}
|
|
392
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
393
|
+
let tagCount = 0;
|
|
394
|
+
for (let i = 0; i < count; i++) {
|
|
395
|
+
const element = locator.nth(i);
|
|
396
|
+
// check if the tag matches
|
|
397
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
398
|
+
if (!tag.startsWith("*")) {
|
|
399
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (!el.setAttribute) {
|
|
404
|
+
el = el.parentElement;
|
|
405
|
+
}
|
|
406
|
+
// remove any attributes start with data-blinq-id
|
|
407
|
+
// for (let i = 0; i < el.attributes.length; i++) {
|
|
408
|
+
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
409
|
+
// el.removeAttribute(el.attributes[i].name);
|
|
410
|
+
// }
|
|
411
|
+
// }
|
|
412
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
413
|
+
return true;
|
|
414
|
+
}, [tag1, randomToken]))) {
|
|
415
|
+
continue;
|
|
385
416
|
}
|
|
386
|
-
|
|
387
|
-
}
|
|
417
|
+
tagCount++;
|
|
418
|
+
}
|
|
419
|
+
return { elementCount: tagCount, randomToken };
|
|
388
420
|
}
|
|
389
|
-
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
421
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false, element_name = null) {
|
|
422
|
+
if (!info) {
|
|
423
|
+
info = {};
|
|
424
|
+
}
|
|
425
|
+
if (!info.failCause) {
|
|
426
|
+
info.failCause = {};
|
|
427
|
+
}
|
|
428
|
+
if (!info.log) {
|
|
429
|
+
info.log = "";
|
|
430
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
431
|
+
}
|
|
390
432
|
let locatorSearch = selectorHierarchy[index];
|
|
433
|
+
let originalLocatorSearch = "";
|
|
434
|
+
try {
|
|
435
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
436
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
437
|
+
}
|
|
438
|
+
catch (e) {
|
|
439
|
+
console.error(e);
|
|
440
|
+
}
|
|
391
441
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
392
442
|
let locator = null;
|
|
393
443
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
394
|
-
|
|
444
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
445
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
395
446
|
if (!locatorString) {
|
|
447
|
+
info.failCause.textNotFound = true;
|
|
448
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
396
449
|
return;
|
|
397
450
|
}
|
|
398
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
451
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
399
452
|
}
|
|
400
453
|
else if (locatorSearch.text) {
|
|
401
|
-
let
|
|
454
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
455
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
402
456
|
if (result.elementCount === 0) {
|
|
457
|
+
info.failCause.textNotFound = true;
|
|
458
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
403
459
|
return;
|
|
404
460
|
}
|
|
405
|
-
locatorSearch.css = "[data-blinq-id
|
|
461
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
406
462
|
if (locatorSearch.childCss) {
|
|
407
463
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
408
464
|
}
|
|
409
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
465
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
410
466
|
}
|
|
411
467
|
else {
|
|
412
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
468
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
413
469
|
}
|
|
414
470
|
// let cssHref = false;
|
|
415
471
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
416
472
|
// cssHref = true;
|
|
417
473
|
// }
|
|
418
474
|
let count = await locator.count();
|
|
475
|
+
if (count > 0 && !info.failCause.count) {
|
|
476
|
+
info.failCause.count = count;
|
|
477
|
+
}
|
|
419
478
|
//info.log += "total elements found " + count + "\n";
|
|
420
479
|
//let visibleCount = 0;
|
|
421
480
|
let visibleLocator = null;
|
|
422
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
481
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
423
482
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
483
|
+
if (info.locatorLog) {
|
|
484
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
485
|
+
}
|
|
424
486
|
return;
|
|
425
487
|
}
|
|
488
|
+
if (info.locatorLog && count === 0) {
|
|
489
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
490
|
+
}
|
|
426
491
|
for (let j = 0; j < count; j++) {
|
|
427
492
|
let visible = await locator.nth(j).isVisible();
|
|
428
493
|
const enabled = await locator.nth(j).isEnabled();
|
|
429
494
|
if (!visibleOnly) {
|
|
430
495
|
visible = true;
|
|
431
496
|
}
|
|
432
|
-
if (visible && enabled) {
|
|
497
|
+
if (visible && (allowDisabled || enabled)) {
|
|
433
498
|
foundLocators.push(locator.nth(j));
|
|
499
|
+
if (info.locatorLog) {
|
|
500
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
501
|
+
}
|
|
434
502
|
}
|
|
435
503
|
else {
|
|
504
|
+
info.failCause.visible = visible;
|
|
505
|
+
info.failCause.enabled = enabled;
|
|
436
506
|
if (!info.printMessages) {
|
|
437
507
|
info.printMessages = {};
|
|
438
508
|
}
|
|
509
|
+
if (info.locatorLog && !visible) {
|
|
510
|
+
info.failCause.lastError = `${formatElementName(element_name)} is not visible, searching for ${originalLocatorSearch}`;
|
|
511
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
512
|
+
}
|
|
513
|
+
if (info.locatorLog && !enabled) {
|
|
514
|
+
info.failCause.lastError = `${formatElementName(element_name)} is disabled, searching for ${originalLocatorSearch}`;
|
|
515
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
516
|
+
}
|
|
439
517
|
if (!info.printMessages[j.toString()]) {
|
|
440
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
518
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
441
519
|
info.printMessages[j.toString()] = true;
|
|
442
520
|
}
|
|
443
521
|
}
|
|
444
522
|
}
|
|
445
523
|
}
|
|
446
524
|
async closeUnexpectedPopups(info, _params) {
|
|
525
|
+
if (!info) {
|
|
526
|
+
info = {};
|
|
527
|
+
info.failCause = {};
|
|
528
|
+
info.log = "";
|
|
529
|
+
}
|
|
447
530
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
448
531
|
if (!info) {
|
|
449
532
|
info = {};
|
|
450
533
|
}
|
|
451
|
-
info.log += "scan for popup handlers" + "\n";
|
|
534
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
452
535
|
const handlerGroup = [];
|
|
453
536
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
454
537
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
455
538
|
}
|
|
456
|
-
const scopes =
|
|
539
|
+
const scopes = this.page.frames().filter((frame) => frame.url() !== "about:blank");
|
|
457
540
|
let result = null;
|
|
458
541
|
let scope = null;
|
|
459
542
|
for (let i = 0; i < scopes.length; i++) {
|
|
@@ -475,53 +558,108 @@ class StableBrowser {
|
|
|
475
558
|
}
|
|
476
559
|
if (result.foundElements.length > 0) {
|
|
477
560
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
478
|
-
|
|
561
|
+
try {
|
|
562
|
+
await scope?.evaluate(() => {
|
|
563
|
+
window.__isClosingPopups = true;
|
|
564
|
+
});
|
|
565
|
+
await dialogCloseLocator.click();
|
|
566
|
+
// wait for the dialog to close
|
|
567
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
568
|
+
}
|
|
569
|
+
catch (e) {
|
|
570
|
+
}
|
|
571
|
+
finally {
|
|
572
|
+
await scope?.evaluate(() => {
|
|
573
|
+
window.__isClosingPopups = false;
|
|
574
|
+
});
|
|
575
|
+
}
|
|
479
576
|
return { rerun: true };
|
|
480
577
|
}
|
|
481
578
|
}
|
|
482
579
|
}
|
|
483
580
|
return { rerun: false };
|
|
484
581
|
}
|
|
485
|
-
async _locate(selectors, info, _params, timeout =
|
|
582
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
583
|
+
if (!timeout) {
|
|
584
|
+
timeout = 30000;
|
|
585
|
+
}
|
|
486
586
|
for (let i = 0; i < 3; i++) {
|
|
487
587
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
488
588
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
489
589
|
let selector = selectors.locators[j];
|
|
490
590
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
491
591
|
}
|
|
492
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
592
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
493
593
|
if (!element.rerun) {
|
|
494
|
-
|
|
594
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
595
|
+
element.evaluate((el, randomToken) => {
|
|
596
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
597
|
+
}, randomToken);
|
|
598
|
+
// if (element._frame) {
|
|
599
|
+
// return element;
|
|
600
|
+
// }
|
|
601
|
+
const scope = element._frame ?? element.page();
|
|
602
|
+
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
603
|
+
let prefixSelector = "";
|
|
604
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
605
|
+
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
606
|
+
if (frameSelectorIndex !== -1) {
|
|
607
|
+
// remove everything after the >> internal:control=enter-frame
|
|
608
|
+
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
609
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
610
|
+
}
|
|
611
|
+
// if (element?._frame?._selector) {
|
|
612
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
613
|
+
// }
|
|
614
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
615
|
+
return scope.locator(newSelector);
|
|
495
616
|
}
|
|
496
617
|
}
|
|
497
618
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
498
619
|
}
|
|
499
|
-
async
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
620
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
621
|
+
if (!info) {
|
|
622
|
+
info = {};
|
|
623
|
+
info.failCause = {};
|
|
624
|
+
info.log = "";
|
|
625
|
+
}
|
|
626
|
+
let startTime = Date.now();
|
|
505
627
|
let scope = this.page;
|
|
628
|
+
if (selectors.frame) {
|
|
629
|
+
return selectors.frame;
|
|
630
|
+
}
|
|
506
631
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
507
|
-
const findFrame = (frame, framescope) => {
|
|
632
|
+
const findFrame = async (frame, framescope) => {
|
|
508
633
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
509
634
|
let frameLocator = frame.selectors[i];
|
|
510
635
|
if (frameLocator.css) {
|
|
511
|
-
|
|
512
|
-
|
|
636
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
637
|
+
if (frameLocator.index) {
|
|
638
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
639
|
+
}
|
|
640
|
+
try {
|
|
641
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
642
|
+
timeout: 5000,
|
|
643
|
+
});
|
|
644
|
+
framescope = testframescope;
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
console.error("frame not found " + frameLocator.css);
|
|
649
|
+
}
|
|
513
650
|
}
|
|
514
651
|
}
|
|
515
652
|
if (frame.children) {
|
|
516
|
-
return findFrame(frame.children, framescope);
|
|
653
|
+
return await findFrame(frame.children, framescope);
|
|
517
654
|
}
|
|
518
655
|
return framescope;
|
|
519
656
|
};
|
|
520
|
-
|
|
657
|
+
let fLocator = null;
|
|
521
658
|
while (true) {
|
|
522
659
|
let frameFound = false;
|
|
523
660
|
if (selectors.nestFrmLoc) {
|
|
524
|
-
|
|
661
|
+
fLocator = selectors.nestFrmLoc;
|
|
662
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
525
663
|
frameFound = true;
|
|
526
664
|
break;
|
|
527
665
|
}
|
|
@@ -529,6 +667,7 @@ class StableBrowser {
|
|
|
529
667
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
530
668
|
let frameLocator = selectors.frameLocators[i];
|
|
531
669
|
if (frameLocator.css) {
|
|
670
|
+
fLocator = frameLocator.css;
|
|
532
671
|
scope = scope.frameLocator(frameLocator.css);
|
|
533
672
|
frameFound = true;
|
|
534
673
|
break;
|
|
@@ -536,20 +675,55 @@ class StableBrowser {
|
|
|
536
675
|
}
|
|
537
676
|
}
|
|
538
677
|
if (!frameFound && selectors.iframe_src) {
|
|
678
|
+
fLocator = selectors.iframe_src;
|
|
539
679
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
540
680
|
}
|
|
541
681
|
if (!scope) {
|
|
542
|
-
info
|
|
543
|
-
|
|
682
|
+
if (info && info.locatorLog) {
|
|
683
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
684
|
+
}
|
|
685
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
686
|
+
if (Date.now() - startTime > timeout) {
|
|
687
|
+
info.failCause.iframeNotFound = true;
|
|
688
|
+
info.failCause.lastError = `unable to locate iframe "${selectors.iframe_src}"`;
|
|
544
689
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
545
690
|
}
|
|
546
691
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
547
692
|
}
|
|
548
693
|
else {
|
|
694
|
+
if (info && info.locatorLog) {
|
|
695
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
696
|
+
}
|
|
549
697
|
break;
|
|
550
698
|
}
|
|
551
699
|
}
|
|
552
700
|
}
|
|
701
|
+
if (!scope) {
|
|
702
|
+
scope = this.page;
|
|
703
|
+
}
|
|
704
|
+
return scope;
|
|
705
|
+
}
|
|
706
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
707
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
708
|
+
return scope.evaluate(() => {
|
|
709
|
+
var bodyContent = document.body.innerHTML;
|
|
710
|
+
return bodyContent;
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
714
|
+
if (!info) {
|
|
715
|
+
info = {};
|
|
716
|
+
info.failCause = {};
|
|
717
|
+
info.log = "";
|
|
718
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
719
|
+
}
|
|
720
|
+
let highPriorityTimeout = 5000;
|
|
721
|
+
let visibleOnlyTimeout = 6000;
|
|
722
|
+
let startTime = Date.now();
|
|
723
|
+
let locatorsCount = 0;
|
|
724
|
+
let lazy_scroll = false;
|
|
725
|
+
//let arrayMode = Array.isArray(selectors);
|
|
726
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
553
727
|
let selectorsLocators = null;
|
|
554
728
|
selectorsLocators = selectors.locators;
|
|
555
729
|
// group selectors by priority
|
|
@@ -585,18 +759,13 @@ class StableBrowser {
|
|
|
585
759
|
}
|
|
586
760
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
587
761
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
588
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
762
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
589
763
|
if (result.foundElements.length === 0) {
|
|
590
764
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
591
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
592
|
-
}
|
|
593
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
594
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
765
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
595
766
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
599
|
-
}
|
|
767
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
768
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
600
769
|
}
|
|
601
770
|
let foundElements = result.foundElements;
|
|
602
771
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -636,24 +805,43 @@ class StableBrowser {
|
|
|
636
805
|
return maxCountElement.locator;
|
|
637
806
|
}
|
|
638
807
|
}
|
|
639
|
-
if (
|
|
808
|
+
if (Date.now() - startTime > timeout) {
|
|
640
809
|
break;
|
|
641
810
|
}
|
|
642
|
-
if (
|
|
643
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
811
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
812
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
644
813
|
highPriorityOnly = false;
|
|
814
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
815
|
+
lazy_scroll = true;
|
|
816
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
817
|
+
}
|
|
645
818
|
}
|
|
646
|
-
if (
|
|
647
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
819
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
820
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
648
821
|
visibleOnly = false;
|
|
649
822
|
}
|
|
650
823
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
824
|
+
// sheck of more of half of the timeout has passed
|
|
825
|
+
if (Date.now() - startTime > timeout / 2) {
|
|
826
|
+
highPriorityOnly = false;
|
|
827
|
+
visibleOnly = false;
|
|
828
|
+
}
|
|
651
829
|
}
|
|
652
830
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
653
|
-
info.
|
|
831
|
+
// if (info.locatorLog) {
|
|
832
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
833
|
+
// for (let line of lines) {
|
|
834
|
+
// this.logger.debug(line);
|
|
835
|
+
// }
|
|
836
|
+
// }
|
|
837
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
838
|
+
info.failCause.locatorNotFound = true;
|
|
839
|
+
if (!info?.failCause?.lastError) {
|
|
840
|
+
info.failCause.lastError = `failed to locate ${formatElementName(selectors.element_name)}, ${locatorsCount > 0 ? `${locatorsCount} matching elements found` : "no matching elements found"}`;
|
|
841
|
+
}
|
|
654
842
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
655
843
|
}
|
|
656
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
844
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
657
845
|
let foundElements = [];
|
|
658
846
|
const result = {
|
|
659
847
|
foundElements: foundElements,
|
|
@@ -661,14 +849,15 @@ class StableBrowser {
|
|
|
661
849
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
662
850
|
let foundLocators = [];
|
|
663
851
|
try {
|
|
664
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
852
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
665
853
|
}
|
|
666
854
|
catch (e) {
|
|
667
|
-
this
|
|
668
|
-
this.logger.debug(
|
|
855
|
+
// this call can fail it the browser is navigating
|
|
856
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
857
|
+
// this.logger.debug(e);
|
|
669
858
|
foundLocators = [];
|
|
670
859
|
try {
|
|
671
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
860
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
672
861
|
}
|
|
673
862
|
catch (e) {
|
|
674
863
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -682,90 +871,228 @@ class StableBrowser {
|
|
|
682
871
|
});
|
|
683
872
|
result.locatorIndex = i;
|
|
684
873
|
}
|
|
874
|
+
if (foundLocators.length > 1) {
|
|
875
|
+
// remove elements that consume the same space with 10 pixels tolerance
|
|
876
|
+
const boxes = [];
|
|
877
|
+
for (let j = 0; j < foundLocators.length; j++) {
|
|
878
|
+
boxes.push({ box: await foundLocators[j].boundingBox(), locator: foundLocators[j] });
|
|
879
|
+
}
|
|
880
|
+
for (let j = 0; j < boxes.length; j++) {
|
|
881
|
+
for (let k = 0; k < boxes.length; k++) {
|
|
882
|
+
if (j === k) {
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
// check if x, y, width, height are the same with 10 pixels tolerance
|
|
886
|
+
if (Math.abs(boxes[j].box.x - boxes[k].box.x) < 10 &&
|
|
887
|
+
Math.abs(boxes[j].box.y - boxes[k].box.y) < 10 &&
|
|
888
|
+
Math.abs(boxes[j].box.width - boxes[k].box.width) < 10 &&
|
|
889
|
+
Math.abs(boxes[j].box.height - boxes[k].box.height) < 10) {
|
|
890
|
+
// as the element is not unique, will remove it
|
|
891
|
+
boxes.splice(k, 1);
|
|
892
|
+
k--;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
if (boxes.length === 1) {
|
|
897
|
+
result.foundElements.push({
|
|
898
|
+
locator: boxes[0].locator.first(),
|
|
899
|
+
box: boxes[0].box,
|
|
900
|
+
unique: true,
|
|
901
|
+
});
|
|
902
|
+
result.locatorIndex = i;
|
|
903
|
+
}
|
|
904
|
+
else {
|
|
905
|
+
info.failCause.foundMultiple = true;
|
|
906
|
+
if (info.locatorLog) {
|
|
907
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
685
911
|
}
|
|
686
912
|
return result;
|
|
687
913
|
}
|
|
688
|
-
async
|
|
689
|
-
|
|
914
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
915
|
+
const state = {
|
|
916
|
+
locate: false,
|
|
917
|
+
scroll: false,
|
|
918
|
+
highlight: false,
|
|
919
|
+
_params,
|
|
920
|
+
options,
|
|
921
|
+
world,
|
|
922
|
+
type: Types.CLICK,
|
|
923
|
+
text: "Click element",
|
|
924
|
+
operation: "simpleClick",
|
|
925
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
926
|
+
};
|
|
927
|
+
_preCommand(state, this);
|
|
690
928
|
const startTime = Date.now();
|
|
691
|
-
|
|
692
|
-
|
|
929
|
+
let timeout = 30000;
|
|
930
|
+
if (options && options.timeout) {
|
|
931
|
+
timeout = options.timeout;
|
|
693
932
|
}
|
|
694
|
-
|
|
695
|
-
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
696
|
-
info.operation = "click";
|
|
697
|
-
info.selectors = selectors;
|
|
698
|
-
let error = null;
|
|
699
|
-
let screenshotId = null;
|
|
700
|
-
let screenshotPath = null;
|
|
701
|
-
try {
|
|
702
|
-
let element = await this._locate(selectors, info, _params);
|
|
703
|
-
await this.scrollIfNeeded(element, info);
|
|
704
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
933
|
+
while (true) {
|
|
705
934
|
try {
|
|
706
|
-
await this.
|
|
707
|
-
|
|
708
|
-
|
|
935
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
936
|
+
if (result?.elementNumber >= 0) {
|
|
937
|
+
const selectors = {
|
|
938
|
+
frame: result?.frame,
|
|
939
|
+
locators: [
|
|
940
|
+
{
|
|
941
|
+
css: result?.css,
|
|
942
|
+
},
|
|
943
|
+
],
|
|
944
|
+
};
|
|
945
|
+
await this.click(selectors, _params, options, world);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
709
948
|
}
|
|
710
949
|
catch (e) {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
950
|
+
if (performance.now() - startTime > timeout) {
|
|
951
|
+
// throw e;
|
|
952
|
+
try {
|
|
953
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
954
|
+
}
|
|
955
|
+
finally {
|
|
956
|
+
await _commandFinally(state, this);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
716
959
|
}
|
|
960
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
964
|
+
const state = {
|
|
965
|
+
locate: false,
|
|
966
|
+
scroll: false,
|
|
967
|
+
highlight: false,
|
|
968
|
+
_params,
|
|
969
|
+
options,
|
|
970
|
+
world,
|
|
971
|
+
type: Types.FILL,
|
|
972
|
+
text: "Fill element",
|
|
973
|
+
operation: "simpleClickType",
|
|
974
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
975
|
+
};
|
|
976
|
+
_preCommand(state, this);
|
|
977
|
+
const startTime = Date.now();
|
|
978
|
+
let timeout = 30000;
|
|
979
|
+
if (options && options.timeout) {
|
|
980
|
+
timeout = options.timeout;
|
|
981
|
+
}
|
|
982
|
+
while (true) {
|
|
983
|
+
try {
|
|
984
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
985
|
+
if (result?.elementNumber >= 0) {
|
|
986
|
+
const selectors = {
|
|
987
|
+
frame: result?.frame,
|
|
988
|
+
locators: [
|
|
989
|
+
{
|
|
990
|
+
css: result?.css,
|
|
991
|
+
},
|
|
992
|
+
],
|
|
993
|
+
};
|
|
994
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
catch (e) {
|
|
999
|
+
if (performance.now() - startTime > timeout) {
|
|
1000
|
+
// throw e;
|
|
1001
|
+
try {
|
|
1002
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
1003
|
+
}
|
|
1004
|
+
finally {
|
|
1005
|
+
await _commandFinally(state, this);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
async click(selectors, _params, options = {}, world = null) {
|
|
1013
|
+
const state = {
|
|
1014
|
+
selectors,
|
|
1015
|
+
_params,
|
|
1016
|
+
options,
|
|
1017
|
+
world,
|
|
1018
|
+
text: "Click element",
|
|
1019
|
+
_text: "Click on " + selectors.element_name,
|
|
1020
|
+
type: Types.CLICK,
|
|
1021
|
+
operation: "click",
|
|
1022
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1023
|
+
};
|
|
1024
|
+
try {
|
|
1025
|
+
await _preCommand(state, this);
|
|
1026
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
717
1027
|
await this.waitForPageLoad();
|
|
718
|
-
return info;
|
|
1028
|
+
return state.info;
|
|
719
1029
|
}
|
|
720
1030
|
catch (e) {
|
|
721
|
-
|
|
722
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
723
|
-
info.screenshotPath = screenshotPath;
|
|
724
|
-
Object.assign(e, { info: info });
|
|
725
|
-
error = e;
|
|
726
|
-
throw e;
|
|
1031
|
+
await _commandError(state, e, this);
|
|
727
1032
|
}
|
|
728
1033
|
finally {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1034
|
+
await _commandFinally(state, this);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
1038
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1039
|
+
const state = {
|
|
1040
|
+
selectors,
|
|
1041
|
+
_params,
|
|
1042
|
+
options,
|
|
1043
|
+
world,
|
|
1044
|
+
text: "Wait for element",
|
|
1045
|
+
_text: "Wait for " + selectors.element_name,
|
|
1046
|
+
type: Types.WAIT_ELEMENT,
|
|
1047
|
+
operation: "waitForElement",
|
|
1048
|
+
log: "***** wait for " + selectors.element_name + " *****\n",
|
|
1049
|
+
};
|
|
1050
|
+
let found = false;
|
|
1051
|
+
try {
|
|
1052
|
+
await _preCommand(state, this);
|
|
1053
|
+
// if (state.options && state.options.context) {
|
|
1054
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
1055
|
+
// }
|
|
1056
|
+
await state.element.waitFor({ timeout: timeout });
|
|
1057
|
+
found = true;
|
|
1058
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1059
|
+
}
|
|
1060
|
+
catch (e) {
|
|
1061
|
+
console.error("Error on waitForElement", e);
|
|
1062
|
+
// await _commandError(state, e, this);
|
|
749
1063
|
}
|
|
1064
|
+
finally {
|
|
1065
|
+
await _commandFinally(state, this);
|
|
1066
|
+
}
|
|
1067
|
+
return found;
|
|
750
1068
|
}
|
|
751
1069
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
1070
|
+
const state = {
|
|
1071
|
+
selectors,
|
|
1072
|
+
_params,
|
|
1073
|
+
options,
|
|
1074
|
+
world,
|
|
1075
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
1076
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
1077
|
+
_text: checked ? `Check ${selectors.element_name}` : `Uncheck ${selectors.element_name}`,
|
|
1078
|
+
operation: "setCheck",
|
|
1079
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
1080
|
+
};
|
|
762
1081
|
try {
|
|
763
|
-
|
|
764
|
-
|
|
1082
|
+
await _preCommand(state, this);
|
|
1083
|
+
state.info.checked = checked;
|
|
1084
|
+
// let element = await this._locate(selectors, info, _params);
|
|
1085
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
765
1086
|
try {
|
|
766
|
-
|
|
767
|
-
|
|
1087
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1088
|
+
// console.log(`Highlighting while running from recorder`);
|
|
1089
|
+
await this._highlightElements(state.element);
|
|
1090
|
+
await state.element.setChecked(checked);
|
|
768
1091
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1092
|
+
// await this._unHighlightElements(element);
|
|
1093
|
+
// }
|
|
1094
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1095
|
+
// await this._unHighlightElements(element);
|
|
769
1096
|
}
|
|
770
1097
|
catch (e) {
|
|
771
1098
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -773,179 +1100,102 @@ class StableBrowser {
|
|
|
773
1100
|
}
|
|
774
1101
|
else {
|
|
775
1102
|
//await this.closeUnexpectedPopups();
|
|
776
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
777
|
-
element = await this._locate(selectors, info, _params);
|
|
778
|
-
await element.setChecked(checked, { timeout: 5000, force: true });
|
|
1103
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
1104
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
1105
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
779
1106
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
780
1107
|
}
|
|
781
1108
|
}
|
|
782
1109
|
await this.waitForPageLoad();
|
|
783
|
-
return info;
|
|
1110
|
+
return state.info;
|
|
784
1111
|
}
|
|
785
1112
|
catch (e) {
|
|
786
|
-
|
|
787
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
788
|
-
info.screenshotPath = screenshotPath;
|
|
789
|
-
Object.assign(e, { info: info });
|
|
790
|
-
error = e;
|
|
791
|
-
throw e;
|
|
1113
|
+
await _commandError(state, e, this);
|
|
792
1114
|
}
|
|
793
1115
|
finally {
|
|
794
|
-
|
|
795
|
-
this._reportToWorld(world, {
|
|
796
|
-
element_name: selectors.element_name,
|
|
797
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
798
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
799
|
-
screenshotId,
|
|
800
|
-
result: error
|
|
801
|
-
? {
|
|
802
|
-
status: "FAILED",
|
|
803
|
-
startTime,
|
|
804
|
-
endTime,
|
|
805
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
806
|
-
}
|
|
807
|
-
: {
|
|
808
|
-
status: "PASSED",
|
|
809
|
-
startTime,
|
|
810
|
-
endTime,
|
|
811
|
-
},
|
|
812
|
-
info: info,
|
|
813
|
-
});
|
|
1116
|
+
await _commandFinally(state, this);
|
|
814
1117
|
}
|
|
815
1118
|
}
|
|
816
1119
|
async hover(selectors, _params, options = {}, world = null) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1120
|
+
const state = {
|
|
1121
|
+
selectors,
|
|
1122
|
+
_params,
|
|
1123
|
+
options,
|
|
1124
|
+
world,
|
|
1125
|
+
type: Types.HOVER,
|
|
1126
|
+
text: `Hover element`,
|
|
1127
|
+
_text: `Hover on ${selectors.element_name}`,
|
|
1128
|
+
operation: "hover",
|
|
1129
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1130
|
+
};
|
|
826
1131
|
try {
|
|
827
|
-
|
|
828
|
-
(
|
|
829
|
-
|
|
830
|
-
await this._highlightElements(element);
|
|
831
|
-
await element.hover();
|
|
832
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
833
|
-
}
|
|
834
|
-
catch (e) {
|
|
835
|
-
//await this.closeUnexpectedPopups();
|
|
836
|
-
info.log += "hover failed, will try again" + "\n";
|
|
837
|
-
element = await this._locate(selectors, info, _params);
|
|
838
|
-
await element.hover({ timeout: 10000 });
|
|
839
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
840
|
-
}
|
|
1132
|
+
await _preCommand(state, this);
|
|
1133
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1134
|
+
await _screenshot(state, this);
|
|
841
1135
|
await this.waitForPageLoad();
|
|
842
|
-
return info;
|
|
1136
|
+
return state.info;
|
|
843
1137
|
}
|
|
844
1138
|
catch (e) {
|
|
845
|
-
|
|
846
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
847
|
-
info.screenshotPath = screenshotPath;
|
|
848
|
-
Object.assign(e, { info: info });
|
|
849
|
-
error = e;
|
|
850
|
-
throw e;
|
|
1139
|
+
await _commandError(state, e, this);
|
|
851
1140
|
}
|
|
852
1141
|
finally {
|
|
853
|
-
|
|
854
|
-
this._reportToWorld(world, {
|
|
855
|
-
element_name: selectors.element_name,
|
|
856
|
-
type: Types.HOVER,
|
|
857
|
-
text: `Hover element`,
|
|
858
|
-
screenshotId,
|
|
859
|
-
result: error
|
|
860
|
-
? {
|
|
861
|
-
status: "FAILED",
|
|
862
|
-
startTime,
|
|
863
|
-
endTime,
|
|
864
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
865
|
-
}
|
|
866
|
-
: {
|
|
867
|
-
status: "PASSED",
|
|
868
|
-
startTime,
|
|
869
|
-
endTime,
|
|
870
|
-
},
|
|
871
|
-
info: info,
|
|
872
|
-
});
|
|
1142
|
+
await _commandFinally(state, this);
|
|
873
1143
|
}
|
|
874
1144
|
}
|
|
875
1145
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
876
|
-
this._validateSelectors(selectors);
|
|
877
1146
|
if (!values) {
|
|
878
1147
|
throw new Error("values is null");
|
|
879
1148
|
}
|
|
880
|
-
const
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1149
|
+
const state = {
|
|
1150
|
+
selectors,
|
|
1151
|
+
_params,
|
|
1152
|
+
options,
|
|
1153
|
+
world,
|
|
1154
|
+
value: values.toString(),
|
|
1155
|
+
type: Types.SELECT,
|
|
1156
|
+
text: `Select option: ${values}`,
|
|
1157
|
+
_text: `Select option: ${values} on ${selectors.element_name}`,
|
|
1158
|
+
operation: "selectOption",
|
|
1159
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1160
|
+
};
|
|
888
1161
|
try {
|
|
889
|
-
|
|
890
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1162
|
+
await _preCommand(state, this);
|
|
891
1163
|
try {
|
|
892
|
-
await
|
|
893
|
-
await element.selectOption(values);
|
|
1164
|
+
await state.element.selectOption(values);
|
|
894
1165
|
}
|
|
895
1166
|
catch (e) {
|
|
896
1167
|
//await this.closeUnexpectedPopups();
|
|
897
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
898
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1168
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1169
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
899
1170
|
}
|
|
900
1171
|
await this.waitForPageLoad();
|
|
901
|
-
return info;
|
|
1172
|
+
return state.info;
|
|
902
1173
|
}
|
|
903
1174
|
catch (e) {
|
|
904
|
-
|
|
905
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
906
|
-
info.screenshotPath = screenshotPath;
|
|
907
|
-
Object.assign(e, { info: info });
|
|
908
|
-
this.logger.info("click failed, will try next selector");
|
|
909
|
-
error = e;
|
|
910
|
-
throw e;
|
|
1175
|
+
await _commandError(state, e, this);
|
|
911
1176
|
}
|
|
912
1177
|
finally {
|
|
913
|
-
|
|
914
|
-
this._reportToWorld(world, {
|
|
915
|
-
element_name: selectors.element_name,
|
|
916
|
-
type: Types.SELECT,
|
|
917
|
-
text: `Select option: ${values}`,
|
|
918
|
-
value: values.toString(),
|
|
919
|
-
screenshotId,
|
|
920
|
-
result: error
|
|
921
|
-
? {
|
|
922
|
-
status: "FAILED",
|
|
923
|
-
startTime,
|
|
924
|
-
endTime,
|
|
925
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
926
|
-
}
|
|
927
|
-
: {
|
|
928
|
-
status: "PASSED",
|
|
929
|
-
startTime,
|
|
930
|
-
endTime,
|
|
931
|
-
},
|
|
932
|
-
info: info,
|
|
933
|
-
});
|
|
1178
|
+
await _commandFinally(state, this);
|
|
934
1179
|
}
|
|
935
1180
|
}
|
|
936
1181
|
async type(_value, _params = null, options = {}, world = null) {
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1182
|
+
const state = {
|
|
1183
|
+
value: _value,
|
|
1184
|
+
_params,
|
|
1185
|
+
options,
|
|
1186
|
+
world,
|
|
1187
|
+
locate: false,
|
|
1188
|
+
scroll: false,
|
|
1189
|
+
highlight: false,
|
|
1190
|
+
type: Types.TYPE_PRESS,
|
|
1191
|
+
text: `Type value: ${_value}`,
|
|
1192
|
+
_text: `Type value: ${_value}`,
|
|
1193
|
+
operation: "type",
|
|
1194
|
+
log: "",
|
|
1195
|
+
};
|
|
946
1196
|
try {
|
|
947
|
-
|
|
948
|
-
const valueSegment =
|
|
1197
|
+
await _preCommand(state, this);
|
|
1198
|
+
const valueSegment = state.value.split("&&");
|
|
949
1199
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
950
1200
|
if (i > 0) {
|
|
951
1201
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -965,134 +1215,77 @@ class StableBrowser {
|
|
|
965
1215
|
await this.page.keyboard.type(value);
|
|
966
1216
|
}
|
|
967
1217
|
}
|
|
968
|
-
return info;
|
|
1218
|
+
return state.info;
|
|
969
1219
|
}
|
|
970
1220
|
catch (e) {
|
|
971
|
-
|
|
972
|
-
this.logger.error("type failed " + info.log);
|
|
973
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
974
|
-
info.screenshotPath = screenshotPath;
|
|
975
|
-
Object.assign(e, { info: info });
|
|
976
|
-
error = e;
|
|
977
|
-
throw e;
|
|
1221
|
+
await _commandError(state, e, this);
|
|
978
1222
|
}
|
|
979
1223
|
finally {
|
|
980
|
-
|
|
981
|
-
this._reportToWorld(world, {
|
|
982
|
-
type: Types.TYPE_PRESS,
|
|
983
|
-
screenshotId,
|
|
984
|
-
value: _value,
|
|
985
|
-
text: `type value: ${_value}`,
|
|
986
|
-
result: error
|
|
987
|
-
? {
|
|
988
|
-
status: "FAILED",
|
|
989
|
-
startTime,
|
|
990
|
-
endTime,
|
|
991
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
992
|
-
}
|
|
993
|
-
: {
|
|
994
|
-
status: "PASSED",
|
|
995
|
-
startTime,
|
|
996
|
-
endTime,
|
|
997
|
-
},
|
|
998
|
-
info: info,
|
|
999
|
-
});
|
|
1224
|
+
await _commandFinally(state, this);
|
|
1000
1225
|
}
|
|
1001
1226
|
}
|
|
1002
1227
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
let screenshotPath = null;
|
|
1228
|
+
const state = {
|
|
1229
|
+
selectors,
|
|
1230
|
+
_params,
|
|
1231
|
+
value,
|
|
1232
|
+
options,
|
|
1233
|
+
world,
|
|
1234
|
+
type: Types.SET_INPUT,
|
|
1235
|
+
text: `Set input value`,
|
|
1236
|
+
operation: "setInputValue",
|
|
1237
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1238
|
+
};
|
|
1015
1239
|
try {
|
|
1016
|
-
|
|
1017
|
-
let
|
|
1018
|
-
await this.scrollIfNeeded(element, info);
|
|
1019
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1020
|
-
await this._highlightElements(element);
|
|
1240
|
+
await _preCommand(state, this);
|
|
1241
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1021
1242
|
try {
|
|
1022
|
-
await element.evaluateHandle((el, value) => {
|
|
1243
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1023
1244
|
el.value = value;
|
|
1024
1245
|
}, value);
|
|
1025
1246
|
}
|
|
1026
1247
|
catch (error) {
|
|
1027
1248
|
this.logger.error("setInputValue failed, will try again");
|
|
1028
|
-
|
|
1029
|
-
info.
|
|
1030
|
-
|
|
1031
|
-
await element.evaluateHandle((el, value) => {
|
|
1249
|
+
await _screenshot(state, this);
|
|
1250
|
+
Object.assign(error, { info: state.info });
|
|
1251
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1032
1252
|
el.value = value;
|
|
1033
1253
|
});
|
|
1034
1254
|
}
|
|
1035
1255
|
}
|
|
1036
1256
|
catch (e) {
|
|
1037
|
-
|
|
1038
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1039
|
-
info.screenshotPath = screenshotPath;
|
|
1040
|
-
Object.assign(e, { info: info });
|
|
1041
|
-
error = e;
|
|
1042
|
-
throw e;
|
|
1257
|
+
await _commandError(state, e, this);
|
|
1043
1258
|
}
|
|
1044
1259
|
finally {
|
|
1045
|
-
|
|
1046
|
-
this._reportToWorld(world, {
|
|
1047
|
-
element_name: selectors.element_name,
|
|
1048
|
-
type: Types.SET_INPUT,
|
|
1049
|
-
text: `Set input value`,
|
|
1050
|
-
value: value,
|
|
1051
|
-
screenshotId,
|
|
1052
|
-
result: error
|
|
1053
|
-
? {
|
|
1054
|
-
status: "FAILED",
|
|
1055
|
-
startTime,
|
|
1056
|
-
endTime,
|
|
1057
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1058
|
-
}
|
|
1059
|
-
: {
|
|
1060
|
-
status: "PASSED",
|
|
1061
|
-
startTime,
|
|
1062
|
-
endTime,
|
|
1063
|
-
},
|
|
1064
|
-
info: info,
|
|
1065
|
-
});
|
|
1260
|
+
await _commandFinally(state, this);
|
|
1066
1261
|
}
|
|
1067
1262
|
}
|
|
1068
1263
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1264
|
+
const state = {
|
|
1265
|
+
selectors,
|
|
1266
|
+
_params,
|
|
1267
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1268
|
+
options,
|
|
1269
|
+
world,
|
|
1270
|
+
type: Types.SET_DATE_TIME,
|
|
1271
|
+
text: `Set date time value: ${value}`,
|
|
1272
|
+
_text: `Set date time value: ${value} on ${selectors.element_name}`,
|
|
1273
|
+
operation: "setDateTime",
|
|
1274
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1275
|
+
throwError: false,
|
|
1276
|
+
};
|
|
1079
1277
|
try {
|
|
1080
|
-
|
|
1081
|
-
let element = await this._locate(selectors, info, _params);
|
|
1082
|
-
//insert red border around the element
|
|
1083
|
-
await this.scrollIfNeeded(element, info);
|
|
1084
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1085
|
-
await this._highlightElements(element);
|
|
1278
|
+
await _preCommand(state, this);
|
|
1086
1279
|
try {
|
|
1087
|
-
await element
|
|
1280
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1088
1281
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1089
1282
|
if (format) {
|
|
1090
|
-
value = dayjs(value).format(format);
|
|
1091
|
-
await element.fill(value);
|
|
1283
|
+
state.value = dayjs(state.value).format(format);
|
|
1284
|
+
await state.element.fill(state.value);
|
|
1092
1285
|
}
|
|
1093
1286
|
else {
|
|
1094
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1095
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1287
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1288
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1096
1289
|
el.value = ""; // clear input
|
|
1097
1290
|
el.value = dateTimeValue;
|
|
1098
1291
|
}, dateTimeValue);
|
|
@@ -1105,20 +1298,19 @@ class StableBrowser {
|
|
|
1105
1298
|
}
|
|
1106
1299
|
catch (err) {
|
|
1107
1300
|
//await this.closeUnexpectedPopups();
|
|
1108
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1301
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1109
1302
|
this.logger.info("Trying again");
|
|
1110
|
-
|
|
1111
|
-
info.
|
|
1112
|
-
Object.assign(err, { info: info });
|
|
1303
|
+
await _screenshot(state, this);
|
|
1304
|
+
Object.assign(err, { info: state.info });
|
|
1113
1305
|
await element.click();
|
|
1114
1306
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1115
1307
|
if (format) {
|
|
1116
|
-
value = dayjs(value).format(format);
|
|
1117
|
-
await element.fill(value);
|
|
1308
|
+
state.value = dayjs(state.value).format(format);
|
|
1309
|
+
await state.element.fill(state.value);
|
|
1118
1310
|
}
|
|
1119
1311
|
else {
|
|
1120
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1121
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1312
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1313
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1122
1314
|
el.value = ""; // clear input
|
|
1123
1315
|
el.value = dateTimeValue;
|
|
1124
1316
|
}, dateTimeValue);
|
|
@@ -1131,84 +1323,63 @@ class StableBrowser {
|
|
|
1131
1323
|
}
|
|
1132
1324
|
}
|
|
1133
1325
|
catch (e) {
|
|
1134
|
-
|
|
1135
|
-
throw e;
|
|
1326
|
+
await _commandError(state, e, this);
|
|
1136
1327
|
}
|
|
1137
1328
|
finally {
|
|
1138
|
-
|
|
1139
|
-
this._reportToWorld(world, {
|
|
1140
|
-
element_name: selectors.element_name,
|
|
1141
|
-
type: Types.SET_DATE_TIME,
|
|
1142
|
-
screenshotId,
|
|
1143
|
-
value: value,
|
|
1144
|
-
text: `setDateTime input with value: ${value}`,
|
|
1145
|
-
result: error
|
|
1146
|
-
? {
|
|
1147
|
-
status: "FAILED",
|
|
1148
|
-
startTime,
|
|
1149
|
-
endTime,
|
|
1150
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1151
|
-
}
|
|
1152
|
-
: {
|
|
1153
|
-
status: "PASSED",
|
|
1154
|
-
startTime,
|
|
1155
|
-
endTime,
|
|
1156
|
-
},
|
|
1157
|
-
info: info,
|
|
1158
|
-
});
|
|
1329
|
+
await _commandFinally(state, this);
|
|
1159
1330
|
}
|
|
1160
1331
|
}
|
|
1161
1332
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1162
|
-
|
|
1163
|
-
const startTime = Date.now();
|
|
1164
|
-
let error = null;
|
|
1165
|
-
let screenshotId = null;
|
|
1166
|
-
let screenshotPath = null;
|
|
1167
|
-
const info = {};
|
|
1168
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1169
|
-
info.operation = "clickType";
|
|
1170
|
-
info.selectors = selectors;
|
|
1333
|
+
_value = unEscapeString(_value);
|
|
1171
1334
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1335
|
+
const state = {
|
|
1336
|
+
selectors,
|
|
1337
|
+
_params,
|
|
1338
|
+
value: newValue,
|
|
1339
|
+
originalValue: _value,
|
|
1340
|
+
options,
|
|
1341
|
+
world,
|
|
1342
|
+
type: Types.FILL,
|
|
1343
|
+
text: `Click type input with value: ${_value}`,
|
|
1344
|
+
_text: "Fill " + selectors.element_name + " with value " + maskValue(_value),
|
|
1345
|
+
operation: "clickType",
|
|
1346
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1347
|
+
};
|
|
1348
|
+
if (!options) {
|
|
1349
|
+
options = {};
|
|
1350
|
+
}
|
|
1172
1351
|
if (newValue !== _value) {
|
|
1173
1352
|
//this.logger.info(_value + "=" + newValue);
|
|
1174
1353
|
_value = newValue;
|
|
1175
1354
|
}
|
|
1176
|
-
info.value = _value;
|
|
1177
1355
|
try {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1182
|
-
await this._highlightElements(element);
|
|
1183
|
-
if (options === null || options === undefined || !options.press) {
|
|
1356
|
+
await _preCommand(state, this);
|
|
1357
|
+
state.info.value = _value;
|
|
1358
|
+
if (!options.press) {
|
|
1184
1359
|
try {
|
|
1185
|
-
let currentValue = await element.inputValue();
|
|
1360
|
+
let currentValue = await state.element.inputValue();
|
|
1186
1361
|
if (currentValue) {
|
|
1187
|
-
await element.fill("");
|
|
1362
|
+
await state.element.fill("");
|
|
1188
1363
|
}
|
|
1189
1364
|
}
|
|
1190
1365
|
catch (e) {
|
|
1191
1366
|
this.logger.info("unable to clear input value");
|
|
1192
1367
|
}
|
|
1193
1368
|
}
|
|
1194
|
-
if (options
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
}
|
|
1198
|
-
catch (e) {
|
|
1199
|
-
await element.dispatchEvent("click");
|
|
1200
|
-
}
|
|
1369
|
+
if (options.press) {
|
|
1370
|
+
options.timeout = 5000;
|
|
1371
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1201
1372
|
}
|
|
1202
1373
|
else {
|
|
1203
1374
|
try {
|
|
1204
|
-
await element.focus();
|
|
1375
|
+
await state.element.focus();
|
|
1205
1376
|
}
|
|
1206
1377
|
catch (e) {
|
|
1207
|
-
await element.dispatchEvent("focus");
|
|
1378
|
+
await state.element.dispatchEvent("focus");
|
|
1208
1379
|
}
|
|
1209
1380
|
}
|
|
1210
1381
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1211
|
-
const valueSegment =
|
|
1382
|
+
const valueSegment = state.value.split("&&");
|
|
1212
1383
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1213
1384
|
if (i > 0) {
|
|
1214
1385
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1228,13 +1399,19 @@ class StableBrowser {
|
|
|
1228
1399
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1229
1400
|
}
|
|
1230
1401
|
}
|
|
1402
|
+
await _screenshot(state, this);
|
|
1231
1403
|
if (enter === true) {
|
|
1232
1404
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1233
1405
|
await this.page.keyboard.press("Enter");
|
|
1234
1406
|
await this.waitForPageLoad();
|
|
1235
1407
|
}
|
|
1236
1408
|
else if (enter === false) {
|
|
1237
|
-
|
|
1409
|
+
try {
|
|
1410
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1411
|
+
}
|
|
1412
|
+
catch (e) {
|
|
1413
|
+
// ignore
|
|
1414
|
+
}
|
|
1238
1415
|
//await this.page.keyboard.press("Tab");
|
|
1239
1416
|
}
|
|
1240
1417
|
else {
|
|
@@ -1243,111 +1420,60 @@ class StableBrowser {
|
|
|
1243
1420
|
await this.waitForPageLoad();
|
|
1244
1421
|
}
|
|
1245
1422
|
}
|
|
1246
|
-
return info;
|
|
1423
|
+
return state.info;
|
|
1247
1424
|
}
|
|
1248
1425
|
catch (e) {
|
|
1249
|
-
|
|
1250
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1251
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1252
|
-
info.screenshotPath = screenshotPath;
|
|
1253
|
-
Object.assign(e, { info: info });
|
|
1254
|
-
error = e;
|
|
1255
|
-
throw e;
|
|
1426
|
+
await _commandError(state, e, this);
|
|
1256
1427
|
}
|
|
1257
1428
|
finally {
|
|
1258
|
-
|
|
1259
|
-
this._reportToWorld(world, {
|
|
1260
|
-
element_name: selectors.element_name,
|
|
1261
|
-
type: Types.FILL,
|
|
1262
|
-
screenshotId,
|
|
1263
|
-
value: _value,
|
|
1264
|
-
text: `clickType input with value: ${_value}`,
|
|
1265
|
-
result: error
|
|
1266
|
-
? {
|
|
1267
|
-
status: "FAILED",
|
|
1268
|
-
startTime,
|
|
1269
|
-
endTime,
|
|
1270
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1271
|
-
}
|
|
1272
|
-
: {
|
|
1273
|
-
status: "PASSED",
|
|
1274
|
-
startTime,
|
|
1275
|
-
endTime,
|
|
1276
|
-
},
|
|
1277
|
-
info: info,
|
|
1278
|
-
});
|
|
1429
|
+
await _commandFinally(state, this);
|
|
1279
1430
|
}
|
|
1280
1431
|
}
|
|
1281
1432
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1433
|
+
const state = {
|
|
1434
|
+
selectors,
|
|
1435
|
+
_params,
|
|
1436
|
+
value: unEscapeString(value),
|
|
1437
|
+
options,
|
|
1438
|
+
world,
|
|
1439
|
+
type: Types.FILL,
|
|
1440
|
+
text: `Fill input with value: ${value}`,
|
|
1441
|
+
operation: "fill",
|
|
1442
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1443
|
+
};
|
|
1292
1444
|
try {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
await
|
|
1296
|
-
await element.fill(value);
|
|
1297
|
-
await element.dispatchEvent("change");
|
|
1445
|
+
await _preCommand(state, this);
|
|
1446
|
+
await state.element.fill(value);
|
|
1447
|
+
await state.element.dispatchEvent("change");
|
|
1298
1448
|
if (enter) {
|
|
1299
1449
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1300
1450
|
await this.page.keyboard.press("Enter");
|
|
1301
1451
|
}
|
|
1302
1452
|
await this.waitForPageLoad();
|
|
1303
|
-
return info;
|
|
1453
|
+
return state.info;
|
|
1304
1454
|
}
|
|
1305
1455
|
catch (e) {
|
|
1306
|
-
|
|
1307
|
-
this.logger.error("fill failed " + info.log);
|
|
1308
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1309
|
-
info.screenshotPath = screenshotPath;
|
|
1310
|
-
Object.assign(e, { info: info });
|
|
1311
|
-
error = e;
|
|
1312
|
-
throw e;
|
|
1456
|
+
await _commandError(state, e, this);
|
|
1313
1457
|
}
|
|
1314
1458
|
finally {
|
|
1315
|
-
|
|
1316
|
-
this._reportToWorld(world, {
|
|
1317
|
-
element_name: selectors.element_name,
|
|
1318
|
-
type: Types.FILL,
|
|
1319
|
-
screenshotId,
|
|
1320
|
-
value,
|
|
1321
|
-
text: `Fill input with value: ${value}`,
|
|
1322
|
-
result: error
|
|
1323
|
-
? {
|
|
1324
|
-
status: "FAILED",
|
|
1325
|
-
startTime,
|
|
1326
|
-
endTime,
|
|
1327
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1328
|
-
}
|
|
1329
|
-
: {
|
|
1330
|
-
status: "PASSED",
|
|
1331
|
-
startTime,
|
|
1332
|
-
endTime,
|
|
1333
|
-
},
|
|
1334
|
-
info: info,
|
|
1335
|
-
});
|
|
1459
|
+
await _commandFinally(state, this);
|
|
1336
1460
|
}
|
|
1337
1461
|
}
|
|
1338
1462
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1339
1463
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1340
1464
|
}
|
|
1341
1465
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1342
|
-
this.
|
|
1466
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1467
|
+
_validateSelectors(selectors);
|
|
1343
1468
|
let screenshotId = null;
|
|
1344
1469
|
let screenshotPath = null;
|
|
1345
1470
|
if (!info.log) {
|
|
1346
1471
|
info.log = "";
|
|
1472
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1347
1473
|
}
|
|
1348
1474
|
info.operation = "getText";
|
|
1349
1475
|
info.selectors = selectors;
|
|
1350
|
-
let element = await this._locate(selectors, info, _params);
|
|
1476
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1351
1477
|
if (climb > 0) {
|
|
1352
1478
|
const climbArray = [];
|
|
1353
1479
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1366,6 +1492,18 @@ class StableBrowser {
|
|
|
1366
1492
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1367
1493
|
try {
|
|
1368
1494
|
await this._highlightElements(element);
|
|
1495
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1496
|
+
// // console.log(`Highlighting for get text while running from recorder`);
|
|
1497
|
+
// this._highlightElements(element)
|
|
1498
|
+
// .then(async () => {
|
|
1499
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
1500
|
+
// this._unhighlightElements(element).then(
|
|
1501
|
+
// () => {}
|
|
1502
|
+
// // console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
1503
|
+
// );
|
|
1504
|
+
// })
|
|
1505
|
+
// .catch(e);
|
|
1506
|
+
// }
|
|
1369
1507
|
const elementText = await element.innerText();
|
|
1370
1508
|
return {
|
|
1371
1509
|
text: elementText,
|
|
@@ -1377,192 +1515,149 @@ class StableBrowser {
|
|
|
1377
1515
|
}
|
|
1378
1516
|
catch (e) {
|
|
1379
1517
|
//await this.closeUnexpectedPopups();
|
|
1380
|
-
this.logger.info("no innerText will use textContent");
|
|
1518
|
+
this.logger.info("no innerText, will use textContent");
|
|
1381
1519
|
const elementText = await element.textContent();
|
|
1382
1520
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1383
1521
|
}
|
|
1384
1522
|
}
|
|
1385
1523
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1386
|
-
var _a;
|
|
1387
|
-
this._validateSelectors(selectors);
|
|
1388
1524
|
if (!pattern) {
|
|
1389
1525
|
throw new Error("pattern is null");
|
|
1390
1526
|
}
|
|
1391
1527
|
if (!text) {
|
|
1392
1528
|
throw new Error("text is null");
|
|
1393
1529
|
}
|
|
1530
|
+
const state = {
|
|
1531
|
+
selectors,
|
|
1532
|
+
_params,
|
|
1533
|
+
pattern,
|
|
1534
|
+
value: pattern,
|
|
1535
|
+
options,
|
|
1536
|
+
world,
|
|
1537
|
+
locate: false,
|
|
1538
|
+
scroll: false,
|
|
1539
|
+
screenshot: false,
|
|
1540
|
+
highlight: false,
|
|
1541
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1542
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1543
|
+
_text: "Verify element " + selectors.element_name + " contains pattern " + pattern,
|
|
1544
|
+
operation: "containsPattern",
|
|
1545
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1546
|
+
};
|
|
1394
1547
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1395
1548
|
if (newValue !== text) {
|
|
1396
1549
|
this.logger.info(text + "=" + newValue);
|
|
1397
1550
|
text = newValue;
|
|
1398
1551
|
}
|
|
1399
|
-
const startTime = Date.now();
|
|
1400
|
-
let error = null;
|
|
1401
|
-
let screenshotId = null;
|
|
1402
|
-
let screenshotPath = null;
|
|
1403
|
-
const info = {};
|
|
1404
|
-
info.log =
|
|
1405
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1406
|
-
info.operation = "containsPattern";
|
|
1407
|
-
info.selectors = selectors;
|
|
1408
|
-
info.value = text;
|
|
1409
|
-
info.pattern = pattern;
|
|
1410
1552
|
let foundObj = null;
|
|
1411
1553
|
try {
|
|
1412
|
-
|
|
1554
|
+
await _preCommand(state, this);
|
|
1555
|
+
state.info.pattern = pattern;
|
|
1556
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1413
1557
|
if (foundObj && foundObj.element) {
|
|
1414
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1558
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1415
1559
|
}
|
|
1416
|
-
|
|
1560
|
+
await _screenshot(state, this);
|
|
1417
1561
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1418
1562
|
pattern = pattern.replace("{text}", escapedText);
|
|
1419
1563
|
let regex = new RegExp(pattern, "im");
|
|
1420
|
-
if (!regex.test(foundObj
|
|
1421
|
-
info.foundText = foundObj
|
|
1564
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1565
|
+
state.info.foundText = foundObj?.text;
|
|
1422
1566
|
throw new Error("element doesn't contain text " + text);
|
|
1423
1567
|
}
|
|
1424
|
-
return info;
|
|
1568
|
+
return state.info;
|
|
1425
1569
|
}
|
|
1426
1570
|
catch (e) {
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1430
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1431
|
-
info.screenshotPath = screenshotPath;
|
|
1432
|
-
Object.assign(e, { info: info });
|
|
1433
|
-
error = e;
|
|
1434
|
-
throw e;
|
|
1571
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1572
|
+
await _commandError(state, e, this);
|
|
1435
1573
|
}
|
|
1436
1574
|
finally {
|
|
1437
|
-
|
|
1438
|
-
this._reportToWorld(world, {
|
|
1439
|
-
element_name: selectors.element_name,
|
|
1440
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1441
|
-
value: pattern,
|
|
1442
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1443
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1444
|
-
result: error
|
|
1445
|
-
? {
|
|
1446
|
-
status: "FAILED",
|
|
1447
|
-
startTime,
|
|
1448
|
-
endTime,
|
|
1449
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1450
|
-
}
|
|
1451
|
-
: {
|
|
1452
|
-
status: "PASSED",
|
|
1453
|
-
startTime,
|
|
1454
|
-
endTime,
|
|
1455
|
-
},
|
|
1456
|
-
info: info,
|
|
1457
|
-
});
|
|
1575
|
+
await _commandFinally(state, this);
|
|
1458
1576
|
}
|
|
1459
1577
|
}
|
|
1460
1578
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1461
|
-
|
|
1462
|
-
|
|
1579
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1580
|
+
const startTime = Date.now();
|
|
1581
|
+
const state = {
|
|
1582
|
+
selectors,
|
|
1583
|
+
_params,
|
|
1584
|
+
value: text,
|
|
1585
|
+
options,
|
|
1586
|
+
world,
|
|
1587
|
+
locate: false,
|
|
1588
|
+
scroll: false,
|
|
1589
|
+
screenshot: false,
|
|
1590
|
+
highlight: false,
|
|
1591
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1592
|
+
text: `Verify element contains text: ${text}`,
|
|
1593
|
+
operation: "containsText",
|
|
1594
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1595
|
+
};
|
|
1463
1596
|
if (!text) {
|
|
1464
1597
|
throw new Error("text is null");
|
|
1465
1598
|
}
|
|
1466
|
-
|
|
1467
|
-
let error = null;
|
|
1468
|
-
let screenshotId = null;
|
|
1469
|
-
let screenshotPath = null;
|
|
1470
|
-
const info = {};
|
|
1471
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1472
|
-
info.operation = "containsText";
|
|
1473
|
-
info.selectors = selectors;
|
|
1599
|
+
text = unEscapeString(text);
|
|
1474
1600
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1475
1601
|
if (newValue !== text) {
|
|
1476
1602
|
this.logger.info(text + "=" + newValue);
|
|
1477
1603
|
text = newValue;
|
|
1478
1604
|
}
|
|
1479
|
-
info.value = text;
|
|
1480
1605
|
let foundObj = null;
|
|
1481
1606
|
try {
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1607
|
+
while (Date.now() - startTime < timeout) {
|
|
1608
|
+
try {
|
|
1609
|
+
await _preCommand(state, this);
|
|
1610
|
+
foundObj = await this._getText(selectors, climb, _params, { timeout: 3000 }, state.info, world);
|
|
1611
|
+
if (foundObj && foundObj.element) {
|
|
1612
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1613
|
+
}
|
|
1614
|
+
await _screenshot(state, this);
|
|
1615
|
+
const dateAlternatives = findDateAlternatives(text);
|
|
1616
|
+
const numberAlternatives = findNumberAlternatives(text);
|
|
1617
|
+
if (dateAlternatives.date) {
|
|
1618
|
+
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1619
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1620
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1621
|
+
return state.info;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1494
1624
|
}
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1625
|
+
else if (numberAlternatives.number) {
|
|
1626
|
+
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1627
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1628
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1629
|
+
return state.info;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
else if (foundObj?.text.includes(text) || foundObj?.value?.includes(text)) {
|
|
1634
|
+
return state.info;
|
|
1503
1635
|
}
|
|
1504
1636
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
throw new Error("element doesn't contain text " + text);
|
|
1637
|
+
catch (e) {
|
|
1638
|
+
// Log error but continue retrying until timeout is reached
|
|
1639
|
+
this.logger.warn("Retrying containsText due to: " + e.message);
|
|
1640
|
+
}
|
|
1641
|
+
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
|
|
1511
1642
|
}
|
|
1512
|
-
|
|
1643
|
+
state.info.foundText = foundObj?.text;
|
|
1644
|
+
state.info.value = foundObj?.value;
|
|
1645
|
+
throw new Error("element doesn't contain text " + text);
|
|
1513
1646
|
}
|
|
1514
1647
|
catch (e) {
|
|
1515
|
-
|
|
1516
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1517
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1518
|
-
info.screenshotPath = screenshotPath;
|
|
1519
|
-
Object.assign(e, { info: info });
|
|
1520
|
-
error = e;
|
|
1648
|
+
await _commandError(state, e, this);
|
|
1521
1649
|
throw e;
|
|
1522
1650
|
}
|
|
1523
1651
|
finally {
|
|
1524
|
-
|
|
1525
|
-
this._reportToWorld(world, {
|
|
1526
|
-
element_name: selectors.element_name,
|
|
1527
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1528
|
-
text: `Verify element contains text: ${text}`,
|
|
1529
|
-
value: text,
|
|
1530
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1531
|
-
result: error
|
|
1532
|
-
? {
|
|
1533
|
-
status: "FAILED",
|
|
1534
|
-
startTime,
|
|
1535
|
-
endTime,
|
|
1536
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1537
|
-
}
|
|
1538
|
-
: {
|
|
1539
|
-
status: "PASSED",
|
|
1540
|
-
startTime,
|
|
1541
|
-
endTime,
|
|
1542
|
-
},
|
|
1543
|
-
info: info,
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
_getDataFile(world = null) {
|
|
1548
|
-
let dataFile = null;
|
|
1549
|
-
if (world && world.reportFolder) {
|
|
1550
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1551
|
-
}
|
|
1552
|
-
else if (this.reportFolder) {
|
|
1553
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1554
|
-
}
|
|
1555
|
-
else if (this.context && this.context.reportFolder) {
|
|
1556
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1557
|
-
}
|
|
1558
|
-
else {
|
|
1559
|
-
dataFile = "data.json";
|
|
1652
|
+
await _commandFinally(state, this);
|
|
1560
1653
|
}
|
|
1561
|
-
return dataFile;
|
|
1562
1654
|
}
|
|
1563
1655
|
async waitForUserInput(message, world = null) {
|
|
1564
1656
|
if (!message) {
|
|
1565
|
-
message = "Press any key to continue";
|
|
1657
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1658
|
+
}
|
|
1659
|
+
else {
|
|
1660
|
+
message = "# Wait for user input. " + message;
|
|
1566
1661
|
}
|
|
1567
1662
|
message += "\n";
|
|
1568
1663
|
const value = await new Promise((resolve) => {
|
|
@@ -1585,13 +1680,22 @@ class StableBrowser {
|
|
|
1585
1680
|
return;
|
|
1586
1681
|
}
|
|
1587
1682
|
// if data file exists, load it
|
|
1588
|
-
const dataFile =
|
|
1683
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1589
1684
|
let data = this.getTestData(world);
|
|
1590
1685
|
// merge the testData with the existing data
|
|
1591
1686
|
Object.assign(data, testData);
|
|
1592
1687
|
// save the data to the file
|
|
1593
1688
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1594
1689
|
}
|
|
1690
|
+
overwriteTestData(testData, world = null) {
|
|
1691
|
+
if (!testData) {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
// if data file exists, load it
|
|
1695
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1696
|
+
// save the data to the file
|
|
1697
|
+
fs.writeFileSync(dataFile, JSON.stringify(testData, null, 2));
|
|
1698
|
+
}
|
|
1595
1699
|
_getDataFilePath(fileName) {
|
|
1596
1700
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1597
1701
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1688,7 +1792,7 @@ class StableBrowser {
|
|
|
1688
1792
|
}
|
|
1689
1793
|
}
|
|
1690
1794
|
getTestData(world = null) {
|
|
1691
|
-
const dataFile =
|
|
1795
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1692
1796
|
let data = {};
|
|
1693
1797
|
if (fs.existsSync(dataFile)) {
|
|
1694
1798
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1720,11 +1824,9 @@ class StableBrowser {
|
|
|
1720
1824
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1721
1825
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1722
1826
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
}
|
|
1727
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1827
|
+
// to make sure the path doesn't start with -
|
|
1828
|
+
const uuidStr = "id_" + randomUUID();
|
|
1829
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1728
1830
|
try {
|
|
1729
1831
|
await this.takeScreenshot(screenshotPath);
|
|
1730
1832
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1734,15 +1836,15 @@ class StableBrowser {
|
|
|
1734
1836
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1735
1837
|
// }
|
|
1736
1838
|
// });
|
|
1839
|
+
result.screenshotId = uuidStr;
|
|
1840
|
+
result.screenshotPath = screenshotPath;
|
|
1841
|
+
if (info && info.box) {
|
|
1842
|
+
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1843
|
+
}
|
|
1737
1844
|
}
|
|
1738
1845
|
catch (e) {
|
|
1739
1846
|
this.logger.info("unable to take screenshot, ignored");
|
|
1740
1847
|
}
|
|
1741
|
-
result.screenshotId = nextIndex;
|
|
1742
|
-
result.screenshotPath = screenshotPath;
|
|
1743
|
-
if (info && info.box) {
|
|
1744
|
-
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1745
|
-
}
|
|
1746
1848
|
}
|
|
1747
1849
|
else if (options && options.screenshot) {
|
|
1748
1850
|
result.screenshotPath = options.screenshotPath;
|
|
@@ -1767,7 +1869,6 @@ class StableBrowser {
|
|
|
1767
1869
|
}
|
|
1768
1870
|
async takeScreenshot(screenshotPath) {
|
|
1769
1871
|
const playContext = this.context.playContext;
|
|
1770
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1771
1872
|
// Using CDP to capture the screenshot
|
|
1772
1873
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1773
1874
|
document.body.scrollWidth,
|
|
@@ -1777,164 +1878,198 @@ class StableBrowser {
|
|
|
1777
1878
|
document.body.clientWidth,
|
|
1778
1879
|
document.documentElement.clientWidth,
|
|
1779
1880
|
])));
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
screenshotBuffer =
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
}
|
|
1813
|
-
|
|
1814
|
-
|
|
1881
|
+
let screenshotBuffer = null;
|
|
1882
|
+
// if (focusedElement) {
|
|
1883
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1884
|
+
// await this._unhighlightElements(focusedElement);
|
|
1885
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1886
|
+
// console.log(`Unhighlighted previous element`);
|
|
1887
|
+
// }
|
|
1888
|
+
// if (focusedElement) {
|
|
1889
|
+
// await this._highlightElements(focusedElement);
|
|
1890
|
+
// }
|
|
1891
|
+
if (this.context.browserName === "chromium") {
|
|
1892
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1893
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1894
|
+
format: "png",
|
|
1895
|
+
// clip: {
|
|
1896
|
+
// x: 0,
|
|
1897
|
+
// y: 0,
|
|
1898
|
+
// width: viewportWidth,
|
|
1899
|
+
// height: viewportHeight,
|
|
1900
|
+
// scale: 1,
|
|
1901
|
+
// },
|
|
1902
|
+
});
|
|
1903
|
+
await client.detach();
|
|
1904
|
+
if (!screenshotPath) {
|
|
1905
|
+
return data;
|
|
1906
|
+
}
|
|
1907
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1908
|
+
}
|
|
1909
|
+
else {
|
|
1910
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1911
|
+
}
|
|
1912
|
+
// if (focusedElement) {
|
|
1913
|
+
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
1914
|
+
// await this._unhighlightElements(focusedElement);
|
|
1915
|
+
// }
|
|
1916
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1917
|
+
// Get the image dimensions
|
|
1918
|
+
const { width, height } = image.bitmap;
|
|
1919
|
+
const resizeRatio = viewportWidth / width;
|
|
1920
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1921
|
+
if (width > viewportWidth) {
|
|
1922
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1923
|
+
await image.write(screenshotPath);
|
|
1924
|
+
}
|
|
1925
|
+
else {
|
|
1926
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1927
|
+
}
|
|
1928
|
+
return screenshotBuffer;
|
|
1815
1929
|
}
|
|
1816
1930
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1931
|
+
const state = {
|
|
1932
|
+
selectors,
|
|
1933
|
+
_params,
|
|
1934
|
+
options,
|
|
1935
|
+
world,
|
|
1936
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1937
|
+
text: `Verify element exists in page`,
|
|
1938
|
+
operation: "verifyElementExistInPage",
|
|
1939
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1940
|
+
};
|
|
1822
1941
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1823
|
-
const info = {};
|
|
1824
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1825
|
-
info.operation = "verify";
|
|
1826
|
-
info.selectors = selectors;
|
|
1827
1942
|
try {
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
}
|
|
1832
|
-
await this._highlightElements(element);
|
|
1833
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1834
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1835
|
-
return info;
|
|
1943
|
+
await _preCommand(state, this);
|
|
1944
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1945
|
+
return state.info;
|
|
1836
1946
|
}
|
|
1837
1947
|
catch (e) {
|
|
1838
|
-
|
|
1839
|
-
this.logger.error("verify failed " + info.log);
|
|
1840
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1841
|
-
info.screenshotPath = screenshotPath;
|
|
1842
|
-
Object.assign(e, { info: info });
|
|
1843
|
-
error = e;
|
|
1844
|
-
throw e;
|
|
1948
|
+
await _commandError(state, e, this);
|
|
1845
1949
|
}
|
|
1846
1950
|
finally {
|
|
1847
|
-
|
|
1848
|
-
this._reportToWorld(world, {
|
|
1849
|
-
element_name: selectors.element_name,
|
|
1850
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1851
|
-
text: "Verify element exists in page",
|
|
1852
|
-
screenshotId,
|
|
1853
|
-
result: error
|
|
1854
|
-
? {
|
|
1855
|
-
status: "FAILED",
|
|
1856
|
-
startTime,
|
|
1857
|
-
endTime,
|
|
1858
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1859
|
-
}
|
|
1860
|
-
: {
|
|
1861
|
-
status: "PASSED",
|
|
1862
|
-
startTime,
|
|
1863
|
-
endTime,
|
|
1864
|
-
},
|
|
1865
|
-
info: info,
|
|
1866
|
-
});
|
|
1951
|
+
await _commandFinally(state, this);
|
|
1867
1952
|
}
|
|
1868
1953
|
}
|
|
1869
1954
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1955
|
+
const state = {
|
|
1956
|
+
selectors,
|
|
1957
|
+
_params,
|
|
1958
|
+
attribute,
|
|
1959
|
+
variable,
|
|
1960
|
+
options,
|
|
1961
|
+
world,
|
|
1962
|
+
type: Types.EXTRACT,
|
|
1963
|
+
text: `Extract attribute from element`,
|
|
1964
|
+
_text: `Extract attribute ${attribute} from ${selectors.element_name}`,
|
|
1965
|
+
operation: "extractAttribute",
|
|
1966
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1967
|
+
allowDisabled: true,
|
|
1968
|
+
};
|
|
1875
1969
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1876
|
-
const info = {};
|
|
1877
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1878
|
-
info.operation = "extract";
|
|
1879
|
-
info.selectors = selectors;
|
|
1880
1970
|
try {
|
|
1881
|
-
|
|
1882
|
-
await this._highlightElements(element);
|
|
1883
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1971
|
+
await _preCommand(state, this);
|
|
1884
1972
|
switch (attribute) {
|
|
1885
1973
|
case "inner_text":
|
|
1886
|
-
|
|
1974
|
+
state.value = await state.element.innerText();
|
|
1887
1975
|
break;
|
|
1888
1976
|
case "href":
|
|
1889
|
-
|
|
1977
|
+
state.value = await state.element.getAttribute("href");
|
|
1890
1978
|
break;
|
|
1891
1979
|
case "value":
|
|
1892
|
-
|
|
1980
|
+
state.value = await state.element.inputValue();
|
|
1981
|
+
break;
|
|
1982
|
+
case "text":
|
|
1983
|
+
state.value = await state.element.textContent();
|
|
1893
1984
|
break;
|
|
1894
1985
|
default:
|
|
1895
|
-
|
|
1986
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1896
1987
|
break;
|
|
1897
1988
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
this.logger.info("set test data: " + variable + "=" + info.value);
|
|
1904
|
-
return info;
|
|
1989
|
+
state.info.value = state.value;
|
|
1990
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1991
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1992
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1993
|
+
return state.info;
|
|
1905
1994
|
}
|
|
1906
1995
|
catch (e) {
|
|
1907
|
-
|
|
1908
|
-
this.logger.error("extract failed " + info.log);
|
|
1909
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1910
|
-
info.screenshotPath = screenshotPath;
|
|
1911
|
-
Object.assign(e, { info: info });
|
|
1912
|
-
error = e;
|
|
1913
|
-
throw e;
|
|
1996
|
+
await _commandError(state, e, this);
|
|
1914
1997
|
}
|
|
1915
1998
|
finally {
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1999
|
+
await _commandFinally(state, this);
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
2003
|
+
const state = {
|
|
2004
|
+
selectors,
|
|
2005
|
+
_params,
|
|
2006
|
+
attribute,
|
|
2007
|
+
value,
|
|
2008
|
+
options,
|
|
2009
|
+
world,
|
|
2010
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
2011
|
+
highlight: true,
|
|
2012
|
+
screenshot: true,
|
|
2013
|
+
text: `Verify element attribute`,
|
|
2014
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
2015
|
+
operation: "verifyAttribute",
|
|
2016
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
2017
|
+
allowDisabled: true,
|
|
2018
|
+
};
|
|
2019
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2020
|
+
let val;
|
|
2021
|
+
let expectedValue;
|
|
2022
|
+
try {
|
|
2023
|
+
await _preCommand(state, this);
|
|
2024
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
2025
|
+
state.info.expectedValue = expectedValue;
|
|
2026
|
+
switch (attribute) {
|
|
2027
|
+
case "innerText":
|
|
2028
|
+
val = String(await state.element.innerText());
|
|
2029
|
+
break;
|
|
2030
|
+
case "text":
|
|
2031
|
+
val = String(await state.element.textContent());
|
|
2032
|
+
break;
|
|
2033
|
+
case "value":
|
|
2034
|
+
val = String(await state.element.inputValue());
|
|
2035
|
+
break;
|
|
2036
|
+
case "checked":
|
|
2037
|
+
val = String(await state.element.isChecked());
|
|
2038
|
+
break;
|
|
2039
|
+
case "disabled":
|
|
2040
|
+
val = String(await state.element.isDisabled());
|
|
2041
|
+
break;
|
|
2042
|
+
case "readOnly":
|
|
2043
|
+
const isEditable = await state.element.isEditable();
|
|
2044
|
+
val = String(!isEditable);
|
|
2045
|
+
break;
|
|
2046
|
+
default:
|
|
2047
|
+
val = String(await state.element.getAttribute(attribute));
|
|
2048
|
+
break;
|
|
2049
|
+
}
|
|
2050
|
+
state.info.value = val;
|
|
2051
|
+
let regex;
|
|
2052
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2053
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
2054
|
+
regex = new RegExp(patternBody, "g");
|
|
2055
|
+
}
|
|
2056
|
+
else {
|
|
2057
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2058
|
+
regex = new RegExp(escapedPattern, "g");
|
|
2059
|
+
}
|
|
2060
|
+
if (!val.match(regex)) {
|
|
2061
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2062
|
+
state.info.failCause.assertionFailed = true;
|
|
2063
|
+
state.info.failCause.lastError = errorMessage;
|
|
2064
|
+
throw new Error(errorMessage);
|
|
2065
|
+
}
|
|
2066
|
+
return state.info;
|
|
2067
|
+
}
|
|
2068
|
+
catch (e) {
|
|
2069
|
+
await _commandError(state, e, this);
|
|
2070
|
+
}
|
|
2071
|
+
finally {
|
|
2072
|
+
await _commandFinally(state, this);
|
|
1938
2073
|
}
|
|
1939
2074
|
}
|
|
1940
2075
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1955,7 +2090,7 @@ class StableBrowser {
|
|
|
1955
2090
|
if (options && options.timeout) {
|
|
1956
2091
|
timeout = options.timeout;
|
|
1957
2092
|
}
|
|
1958
|
-
const serviceUrl =
|
|
2093
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1959
2094
|
const request = {
|
|
1960
2095
|
method: "POST",
|
|
1961
2096
|
url: serviceUrl,
|
|
@@ -2011,7 +2146,8 @@ class StableBrowser {
|
|
|
2011
2146
|
catch (e) {
|
|
2012
2147
|
errorCount++;
|
|
2013
2148
|
if (errorCount > 3) {
|
|
2014
|
-
throw e;
|
|
2149
|
+
// throw e;
|
|
2150
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
2015
2151
|
}
|
|
2016
2152
|
// ignore
|
|
2017
2153
|
}
|
|
@@ -2025,27 +2161,32 @@ class StableBrowser {
|
|
|
2025
2161
|
async _highlightElements(scope, css) {
|
|
2026
2162
|
try {
|
|
2027
2163
|
if (!scope) {
|
|
2164
|
+
// console.log(`Scope is not defined`);
|
|
2028
2165
|
return;
|
|
2029
2166
|
}
|
|
2030
2167
|
if (!css) {
|
|
2031
2168
|
scope
|
|
2032
2169
|
.evaluate((node) => {
|
|
2033
2170
|
if (node && node.style) {
|
|
2034
|
-
let
|
|
2035
|
-
|
|
2171
|
+
let originalOutline = node.style.outline;
|
|
2172
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2173
|
+
// node.__previousOutline = originalOutline;
|
|
2174
|
+
node.style.outline = "2px solid red";
|
|
2175
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
2036
2176
|
if (window) {
|
|
2037
2177
|
window.addEventListener("beforeunload", function (e) {
|
|
2038
|
-
node.style.
|
|
2178
|
+
node.style.outline = originalOutline;
|
|
2039
2179
|
});
|
|
2040
2180
|
}
|
|
2041
2181
|
setTimeout(function () {
|
|
2042
|
-
node.style.
|
|
2182
|
+
node.style.outline = originalOutline;
|
|
2043
2183
|
}, 2000);
|
|
2044
2184
|
}
|
|
2045
2185
|
})
|
|
2046
2186
|
.then(() => { })
|
|
2047
2187
|
.catch((e) => {
|
|
2048
2188
|
// ignore
|
|
2189
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2049
2190
|
});
|
|
2050
2191
|
}
|
|
2051
2192
|
else {
|
|
@@ -2061,17 +2202,18 @@ class StableBrowser {
|
|
|
2061
2202
|
if (!element.style) {
|
|
2062
2203
|
return;
|
|
2063
2204
|
}
|
|
2064
|
-
|
|
2205
|
+
let originalOutline = element.style.outline;
|
|
2206
|
+
element.__previousOutline = originalOutline;
|
|
2065
2207
|
// Set the new border to be red and 2px solid
|
|
2066
|
-
element.style.
|
|
2208
|
+
element.style.outline = "2px solid red";
|
|
2067
2209
|
if (window) {
|
|
2068
2210
|
window.addEventListener("beforeunload", function (e) {
|
|
2069
|
-
element.style.
|
|
2211
|
+
element.style.outline = originalOutline;
|
|
2070
2212
|
});
|
|
2071
2213
|
}
|
|
2072
2214
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2073
2215
|
setTimeout(function () {
|
|
2074
|
-
element.style.
|
|
2216
|
+
element.style.outline = originalOutline;
|
|
2075
2217
|
}, 2000);
|
|
2076
2218
|
}
|
|
2077
2219
|
return;
|
|
@@ -2079,6 +2221,7 @@ class StableBrowser {
|
|
|
2079
2221
|
.then(() => { })
|
|
2080
2222
|
.catch((e) => {
|
|
2081
2223
|
// ignore
|
|
2224
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2082
2225
|
});
|
|
2083
2226
|
}
|
|
2084
2227
|
}
|
|
@@ -2086,6 +2229,54 @@ class StableBrowser {
|
|
|
2086
2229
|
console.debug(error);
|
|
2087
2230
|
}
|
|
2088
2231
|
}
|
|
2232
|
+
// async _unhighlightElements(scope, css) {
|
|
2233
|
+
// try {
|
|
2234
|
+
// if (!scope) {
|
|
2235
|
+
// return;
|
|
2236
|
+
// }
|
|
2237
|
+
// if (!css) {
|
|
2238
|
+
// scope
|
|
2239
|
+
// .evaluate((node) => {
|
|
2240
|
+
// if (node && node.style) {
|
|
2241
|
+
// if (!node.__previousOutline) {
|
|
2242
|
+
// node.style.outline = "";
|
|
2243
|
+
// } else {
|
|
2244
|
+
// node.style.outline = node.__previousOutline;
|
|
2245
|
+
// }
|
|
2246
|
+
// }
|
|
2247
|
+
// })
|
|
2248
|
+
// .then(() => {})
|
|
2249
|
+
// .catch((e) => {
|
|
2250
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2251
|
+
// });
|
|
2252
|
+
// } else {
|
|
2253
|
+
// scope
|
|
2254
|
+
// .evaluate(([css]) => {
|
|
2255
|
+
// if (!css) {
|
|
2256
|
+
// return;
|
|
2257
|
+
// }
|
|
2258
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2259
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2260
|
+
// let element = elements[i];
|
|
2261
|
+
// if (!element.style) {
|
|
2262
|
+
// return;
|
|
2263
|
+
// }
|
|
2264
|
+
// if (!element.__previousOutline) {
|
|
2265
|
+
// element.style.outline = "";
|
|
2266
|
+
// } else {
|
|
2267
|
+
// element.style.outline = element.__previousOutline;
|
|
2268
|
+
// }
|
|
2269
|
+
// }
|
|
2270
|
+
// })
|
|
2271
|
+
// .then(() => {})
|
|
2272
|
+
// .catch((e) => {
|
|
2273
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2274
|
+
// });
|
|
2275
|
+
// }
|
|
2276
|
+
// } catch (error) {
|
|
2277
|
+
// // console.debug(error);
|
|
2278
|
+
// }
|
|
2279
|
+
// }
|
|
2089
2280
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2090
2281
|
const startTime = Date.now();
|
|
2091
2282
|
let error = null;
|
|
@@ -2122,20 +2313,22 @@ class StableBrowser {
|
|
|
2122
2313
|
info.screenshotPath = screenshotPath;
|
|
2123
2314
|
Object.assign(e, { info: info });
|
|
2124
2315
|
error = e;
|
|
2125
|
-
throw e;
|
|
2316
|
+
// throw e;
|
|
2317
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2126
2318
|
}
|
|
2127
2319
|
finally {
|
|
2128
2320
|
const endTime = Date.now();
|
|
2129
|
-
|
|
2321
|
+
_reportToWorld(world, {
|
|
2130
2322
|
type: Types.VERIFY_PAGE_PATH,
|
|
2131
2323
|
text: "Verify page path",
|
|
2324
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2132
2325
|
screenshotId,
|
|
2133
2326
|
result: error
|
|
2134
2327
|
? {
|
|
2135
2328
|
status: "FAILED",
|
|
2136
2329
|
startTime,
|
|
2137
2330
|
endTime,
|
|
2138
|
-
message: error
|
|
2331
|
+
message: error?.message,
|
|
2139
2332
|
}
|
|
2140
2333
|
: {
|
|
2141
2334
|
status: "PASSED",
|
|
@@ -2146,94 +2339,58 @@ class StableBrowser {
|
|
|
2146
2339
|
});
|
|
2147
2340
|
}
|
|
2148
2341
|
}
|
|
2149
|
-
async
|
|
2342
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2150
2343
|
const startTime = Date.now();
|
|
2151
|
-
const timeout = this._getLoadTimeout(options);
|
|
2152
2344
|
let error = null;
|
|
2153
2345
|
let screenshotId = null;
|
|
2154
2346
|
let screenshotPath = null;
|
|
2155
2347
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2156
2348
|
const info = {};
|
|
2157
|
-
info.log = "***** verify
|
|
2158
|
-
info.operation = "
|
|
2159
|
-
const newValue = await this._replaceWithLocalData(
|
|
2160
|
-
if (newValue !==
|
|
2161
|
-
this.logger.info(
|
|
2162
|
-
|
|
2163
|
-
}
|
|
2164
|
-
info.
|
|
2165
|
-
let dateAlternatives = findDateAlternatives(text);
|
|
2166
|
-
let numberAlternatives = findNumberAlternatives(text);
|
|
2349
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2350
|
+
info.operation = "verifyPageTitle";
|
|
2351
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2352
|
+
if (newValue !== title) {
|
|
2353
|
+
this.logger.info(title + "=" + newValue);
|
|
2354
|
+
title = newValue;
|
|
2355
|
+
}
|
|
2356
|
+
info.title = title;
|
|
2167
2357
|
try {
|
|
2168
|
-
|
|
2169
|
-
const
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2174
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
|
|
2175
|
-
result.frame = frames[i];
|
|
2176
|
-
results.push(result);
|
|
2177
|
-
}
|
|
2178
|
-
}
|
|
2179
|
-
else if (numberAlternatives.number) {
|
|
2180
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2181
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
|
|
2182
|
-
result.frame = frames[i];
|
|
2183
|
-
results.push(result);
|
|
2184
|
-
}
|
|
2185
|
-
}
|
|
2186
|
-
else {
|
|
2187
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, {});
|
|
2188
|
-
result.frame = frames[i];
|
|
2189
|
-
results.push(result);
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
info.results = results;
|
|
2193
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2194
|
-
if (resultWithElementsFound.length === 0) {
|
|
2195
|
-
if (Date.now() - startTime > timeout) {
|
|
2196
|
-
throw new Error(`Text ${text} not found in page`);
|
|
2358
|
+
for (let i = 0; i < 30; i++) {
|
|
2359
|
+
const foundTitle = await this.page.title();
|
|
2360
|
+
if (!foundTitle.includes(title)) {
|
|
2361
|
+
if (i === 29) {
|
|
2362
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2197
2363
|
}
|
|
2198
2364
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2199
2365
|
continue;
|
|
2200
2366
|
}
|
|
2201
|
-
if (resultWithElementsFound[0].randomToken) {
|
|
2202
|
-
const frame = resultWithElementsFound[0].frame;
|
|
2203
|
-
const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
|
|
2204
|
-
await this._highlightElements(frame, dataAttribute);
|
|
2205
|
-
const element = await frame.$(dataAttribute);
|
|
2206
|
-
if (element) {
|
|
2207
|
-
await this.scrollIfNeeded(element, info);
|
|
2208
|
-
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
2367
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2212
2368
|
return info;
|
|
2213
2369
|
}
|
|
2214
|
-
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2215
2370
|
}
|
|
2216
2371
|
catch (e) {
|
|
2217
2372
|
//await this.closeUnexpectedPopups();
|
|
2218
|
-
this.logger.error("verify
|
|
2373
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2219
2374
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2220
2375
|
info.screenshotPath = screenshotPath;
|
|
2221
2376
|
Object.assign(e, { info: info });
|
|
2222
2377
|
error = e;
|
|
2223
|
-
throw e;
|
|
2378
|
+
// throw e;
|
|
2379
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2224
2380
|
}
|
|
2225
2381
|
finally {
|
|
2226
2382
|
const endTime = Date.now();
|
|
2227
|
-
|
|
2228
|
-
type: Types.
|
|
2229
|
-
text: "Verify
|
|
2383
|
+
_reportToWorld(world, {
|
|
2384
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2385
|
+
text: "Verify page title",
|
|
2386
|
+
_text: "Verify the page title contains " + title,
|
|
2230
2387
|
screenshotId,
|
|
2231
2388
|
result: error
|
|
2232
2389
|
? {
|
|
2233
2390
|
status: "FAILED",
|
|
2234
2391
|
startTime,
|
|
2235
2392
|
endTime,
|
|
2236
|
-
message: error
|
|
2393
|
+
message: error?.message,
|
|
2237
2394
|
}
|
|
2238
2395
|
: {
|
|
2239
2396
|
status: "PASSED",
|
|
@@ -2244,15 +2401,317 @@ class StableBrowser {
|
|
|
2244
2401
|
});
|
|
2245
2402
|
}
|
|
2246
2403
|
}
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2404
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2405
|
+
const frames = this.page.frames();
|
|
2406
|
+
let results = [];
|
|
2407
|
+
// let ignoreCase = false;
|
|
2408
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2409
|
+
if (dateAlternatives.date) {
|
|
2410
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2411
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2412
|
+
result.frame = frames[i];
|
|
2413
|
+
results.push(result);
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
else if (numberAlternatives.number) {
|
|
2417
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2418
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2419
|
+
result.frame = frames[i];
|
|
2420
|
+
results.push(result);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
else {
|
|
2424
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2425
|
+
result.frame = frames[i];
|
|
2426
|
+
results.push(result);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
state.info.results = results;
|
|
2430
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2431
|
+
return resultWithElementsFound;
|
|
2432
|
+
}
|
|
2433
|
+
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2434
|
+
text = unEscapeString(text);
|
|
2435
|
+
const state = {
|
|
2436
|
+
text_search: text,
|
|
2437
|
+
options,
|
|
2438
|
+
world,
|
|
2439
|
+
locate: false,
|
|
2440
|
+
scroll: false,
|
|
2441
|
+
highlight: false,
|
|
2442
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2443
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2444
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2445
|
+
operation: "verifyTextExistInPage",
|
|
2446
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2447
|
+
};
|
|
2448
|
+
if (testForRegex(text)) {
|
|
2449
|
+
text = text.replace(/\\"/g, '"');
|
|
2450
|
+
}
|
|
2451
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2452
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2453
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2454
|
+
if (newValue !== text) {
|
|
2455
|
+
this.logger.info(text + "=" + newValue);
|
|
2456
|
+
text = newValue;
|
|
2457
|
+
}
|
|
2458
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2459
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2460
|
+
try {
|
|
2461
|
+
await _preCommand(state, this);
|
|
2462
|
+
state.info.text = text;
|
|
2463
|
+
while (true) {
|
|
2464
|
+
let resultWithElementsFound = {
|
|
2465
|
+
length: 0,
|
|
2466
|
+
};
|
|
2467
|
+
try {
|
|
2468
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2469
|
+
}
|
|
2470
|
+
catch (error) {
|
|
2471
|
+
// ignore
|
|
2472
|
+
}
|
|
2473
|
+
if (resultWithElementsFound.length === 0) {
|
|
2474
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2475
|
+
throw new Error(`Text ${text} not found in page`);
|
|
2476
|
+
}
|
|
2477
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2478
|
+
continue;
|
|
2479
|
+
}
|
|
2480
|
+
try {
|
|
2481
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2482
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2483
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2484
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2485
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2486
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2487
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2488
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2489
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2490
|
+
// .then(async () => {
|
|
2491
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2492
|
+
// })
|
|
2493
|
+
// .catch(
|
|
2494
|
+
// (e) => {}
|
|
2495
|
+
// console.error(e)
|
|
2496
|
+
// );
|
|
2497
|
+
// });
|
|
2498
|
+
// }
|
|
2499
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2500
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2501
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2502
|
+
if (element) {
|
|
2503
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2504
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2505
|
+
// await _screenshot(state, this, element);
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
await _screenshot(state, this);
|
|
2509
|
+
return state.info;
|
|
2510
|
+
}
|
|
2511
|
+
catch (error) {
|
|
2512
|
+
console.error(error);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2516
|
+
}
|
|
2517
|
+
catch (e) {
|
|
2518
|
+
await _commandError(state, e, this);
|
|
2519
|
+
}
|
|
2520
|
+
finally {
|
|
2521
|
+
await _commandFinally(state, this);
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2525
|
+
text = unEscapeString(text);
|
|
2526
|
+
const state = {
|
|
2527
|
+
text_search: text,
|
|
2528
|
+
options,
|
|
2529
|
+
world,
|
|
2530
|
+
locate: false,
|
|
2531
|
+
scroll: false,
|
|
2532
|
+
highlight: false,
|
|
2533
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2534
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2535
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2536
|
+
operation: "verifyTextNotExistInPage",
|
|
2537
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2538
|
+
};
|
|
2539
|
+
if (testForRegex(text)) {
|
|
2540
|
+
text = text.replace(/\\"/g, '"');
|
|
2541
|
+
}
|
|
2542
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2543
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2544
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2545
|
+
if (newValue !== text) {
|
|
2546
|
+
this.logger.info(text + "=" + newValue);
|
|
2547
|
+
text = newValue;
|
|
2548
|
+
}
|
|
2549
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2550
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2551
|
+
try {
|
|
2552
|
+
await _preCommand(state, this);
|
|
2553
|
+
state.info.text = text;
|
|
2554
|
+
let resultWithElementsFound = {
|
|
2555
|
+
length: null, // initial cannot be 0
|
|
2556
|
+
};
|
|
2557
|
+
while (true) {
|
|
2558
|
+
try {
|
|
2559
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2560
|
+
}
|
|
2561
|
+
catch (error) {
|
|
2562
|
+
// ignore
|
|
2563
|
+
}
|
|
2564
|
+
if (resultWithElementsFound.length === 0) {
|
|
2565
|
+
await _screenshot(state, this);
|
|
2566
|
+
return state.info;
|
|
2567
|
+
}
|
|
2568
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2569
|
+
throw new Error(`Text ${text} found in page`);
|
|
2570
|
+
}
|
|
2571
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
catch (e) {
|
|
2575
|
+
await _commandError(state, e, this);
|
|
2576
|
+
}
|
|
2577
|
+
finally {
|
|
2578
|
+
await _commandFinally(state, this);
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2582
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2583
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2584
|
+
const state = {
|
|
2585
|
+
text_search: textToVerify,
|
|
2586
|
+
options,
|
|
2587
|
+
world,
|
|
2588
|
+
locate: false,
|
|
2589
|
+
scroll: false,
|
|
2590
|
+
highlight: false,
|
|
2591
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2592
|
+
text: `Verify text with relation to another text`,
|
|
2593
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2594
|
+
operation: "verify_text_with_relation",
|
|
2595
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2596
|
+
};
|
|
2597
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2598
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2599
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2600
|
+
if (newValue !== textAnchor) {
|
|
2601
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2602
|
+
textAnchor = newValue;
|
|
2603
|
+
}
|
|
2604
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2605
|
+
if (newValue !== textToVerify) {
|
|
2606
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2607
|
+
textToVerify = newValue;
|
|
2608
|
+
}
|
|
2609
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2610
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2611
|
+
let foundAncore = false;
|
|
2612
|
+
try {
|
|
2613
|
+
await _preCommand(state, this);
|
|
2614
|
+
state.info.text = textToVerify;
|
|
2615
|
+
let resultWithElementsFound = {
|
|
2616
|
+
length: 0,
|
|
2617
|
+
};
|
|
2618
|
+
while (true) {
|
|
2619
|
+
try {
|
|
2620
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2621
|
+
}
|
|
2622
|
+
catch (error) {
|
|
2623
|
+
// ignore
|
|
2624
|
+
}
|
|
2625
|
+
if (resultWithElementsFound.length === 0) {
|
|
2626
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2627
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2628
|
+
}
|
|
2629
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2630
|
+
continue;
|
|
2631
|
+
}
|
|
2632
|
+
try {
|
|
2633
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2634
|
+
foundAncore = true;
|
|
2635
|
+
const result = resultWithElementsFound[i];
|
|
2636
|
+
const token = result.randomToken;
|
|
2637
|
+
const frame = result.frame;
|
|
2638
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2639
|
+
const climbArray1 = [];
|
|
2640
|
+
for (let i = 0; i < climb; i++) {
|
|
2641
|
+
climbArray1.push("..");
|
|
2642
|
+
}
|
|
2643
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2644
|
+
css = css + " >> " + climbXpath;
|
|
2645
|
+
const count = await frame.locator(css).count();
|
|
2646
|
+
for (let j = 0; j < count; j++) {
|
|
2647
|
+
const continer = await frame.locator(css).nth(j);
|
|
2648
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2649
|
+
if (result.elementCount > 0) {
|
|
2650
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2651
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2652
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2653
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2654
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2655
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2656
|
+
// .then(async () => {
|
|
2657
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2658
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2659
|
+
// () => {}
|
|
2660
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2661
|
+
// );
|
|
2662
|
+
// })
|
|
2663
|
+
// .catch(e);
|
|
2664
|
+
// }
|
|
2665
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2666
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2667
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2668
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2669
|
+
if (element) {
|
|
2670
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2671
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2672
|
+
}
|
|
2673
|
+
await _screenshot(state, this);
|
|
2674
|
+
return state.info;
|
|
2675
|
+
}
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
catch (error) {
|
|
2680
|
+
console.error(error);
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2684
|
+
}
|
|
2685
|
+
catch (e) {
|
|
2686
|
+
await _commandError(state, e, this);
|
|
2687
|
+
}
|
|
2688
|
+
finally {
|
|
2689
|
+
await _commandFinally(state, this);
|
|
2251
2690
|
}
|
|
2252
|
-
|
|
2253
|
-
|
|
2691
|
+
}
|
|
2692
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2693
|
+
const frames = this.page.frames();
|
|
2694
|
+
let results = [];
|
|
2695
|
+
let ignoreCase = false;
|
|
2696
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2697
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2698
|
+
result.frame = frames[i];
|
|
2699
|
+
const climbArray = [];
|
|
2700
|
+
for (let i = 0; i < climb; i++) {
|
|
2701
|
+
climbArray.push("..");
|
|
2702
|
+
}
|
|
2703
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2704
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2705
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2706
|
+
if (count > 0) {
|
|
2707
|
+
result.elementCount = count;
|
|
2708
|
+
result.locator = newLocator;
|
|
2709
|
+
results.push(result);
|
|
2710
|
+
}
|
|
2254
2711
|
}
|
|
2255
|
-
|
|
2712
|
+
// state.info.results = results;
|
|
2713
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2714
|
+
return resultWithElementsFound;
|
|
2256
2715
|
}
|
|
2257
2716
|
async visualVerification(text, options = {}, world = null) {
|
|
2258
2717
|
const startTime = Date.now();
|
|
@@ -2268,14 +2727,17 @@ class StableBrowser {
|
|
|
2268
2727
|
throw new Error("TOKEN is not set");
|
|
2269
2728
|
}
|
|
2270
2729
|
try {
|
|
2271
|
-
let serviceUrl =
|
|
2730
|
+
let serviceUrl = _getServerUrl();
|
|
2272
2731
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2273
2732
|
info.screenshotPath = screenshotPath;
|
|
2274
2733
|
const screenshot = await this.takeScreenshot();
|
|
2275
|
-
|
|
2276
|
-
method: "
|
|
2734
|
+
let request = {
|
|
2735
|
+
method: "post",
|
|
2736
|
+
maxBodyLength: Infinity,
|
|
2277
2737
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2278
2738
|
headers: {
|
|
2739
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2740
|
+
"x-source": "aaa",
|
|
2279
2741
|
"Content-Type": "application/json",
|
|
2280
2742
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2281
2743
|
},
|
|
@@ -2284,7 +2746,7 @@ class StableBrowser {
|
|
|
2284
2746
|
screenshot: screenshot,
|
|
2285
2747
|
}),
|
|
2286
2748
|
};
|
|
2287
|
-
|
|
2749
|
+
const result = await axios.request(request);
|
|
2288
2750
|
if (result.data.status !== true) {
|
|
2289
2751
|
throw new Error("Visual validation failed");
|
|
2290
2752
|
}
|
|
@@ -2304,20 +2766,22 @@ class StableBrowser {
|
|
|
2304
2766
|
info.screenshotPath = screenshotPath;
|
|
2305
2767
|
Object.assign(e, { info: info });
|
|
2306
2768
|
error = e;
|
|
2307
|
-
throw e;
|
|
2769
|
+
// throw e;
|
|
2770
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2308
2771
|
}
|
|
2309
2772
|
finally {
|
|
2310
2773
|
const endTime = Date.now();
|
|
2311
|
-
|
|
2774
|
+
_reportToWorld(world, {
|
|
2312
2775
|
type: Types.VERIFY_VISUAL,
|
|
2313
2776
|
text: "Visual verification",
|
|
2777
|
+
_text: "Visual verification of " + text,
|
|
2314
2778
|
screenshotId,
|
|
2315
2779
|
result: error
|
|
2316
2780
|
? {
|
|
2317
2781
|
status: "FAILED",
|
|
2318
2782
|
startTime,
|
|
2319
2783
|
endTime,
|
|
2320
|
-
message: error
|
|
2784
|
+
message: error?.message,
|
|
2321
2785
|
}
|
|
2322
2786
|
: {
|
|
2323
2787
|
status: "PASSED",
|
|
@@ -2349,13 +2813,14 @@ class StableBrowser {
|
|
|
2349
2813
|
this.logger.info("Table data verified");
|
|
2350
2814
|
}
|
|
2351
2815
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2352
|
-
|
|
2816
|
+
_validateSelectors(selectors);
|
|
2353
2817
|
const startTime = Date.now();
|
|
2354
2818
|
let error = null;
|
|
2355
2819
|
let screenshotId = null;
|
|
2356
2820
|
let screenshotPath = null;
|
|
2357
2821
|
const info = {};
|
|
2358
2822
|
info.log = "";
|
|
2823
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2359
2824
|
info.operation = "getTableData";
|
|
2360
2825
|
info.selectors = selectors;
|
|
2361
2826
|
try {
|
|
@@ -2371,11 +2836,12 @@ class StableBrowser {
|
|
|
2371
2836
|
info.screenshotPath = screenshotPath;
|
|
2372
2837
|
Object.assign(e, { info: info });
|
|
2373
2838
|
error = e;
|
|
2374
|
-
throw e;
|
|
2839
|
+
// throw e;
|
|
2840
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2375
2841
|
}
|
|
2376
2842
|
finally {
|
|
2377
2843
|
const endTime = Date.now();
|
|
2378
|
-
|
|
2844
|
+
_reportToWorld(world, {
|
|
2379
2845
|
element_name: selectors.element_name,
|
|
2380
2846
|
type: Types.GET_TABLE_DATA,
|
|
2381
2847
|
text: "Get table data",
|
|
@@ -2385,7 +2851,7 @@ class StableBrowser {
|
|
|
2385
2851
|
status: "FAILED",
|
|
2386
2852
|
startTime,
|
|
2387
2853
|
endTime,
|
|
2388
|
-
message: error
|
|
2854
|
+
message: error?.message,
|
|
2389
2855
|
}
|
|
2390
2856
|
: {
|
|
2391
2857
|
status: "PASSED",
|
|
@@ -2397,7 +2863,7 @@ class StableBrowser {
|
|
|
2397
2863
|
}
|
|
2398
2864
|
}
|
|
2399
2865
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2400
|
-
|
|
2866
|
+
_validateSelectors(selectors);
|
|
2401
2867
|
if (!query) {
|
|
2402
2868
|
throw new Error("query is null");
|
|
2403
2869
|
}
|
|
@@ -2430,7 +2896,7 @@ class StableBrowser {
|
|
|
2430
2896
|
info.operation = "analyzeTable";
|
|
2431
2897
|
info.selectors = selectors;
|
|
2432
2898
|
info.query = query;
|
|
2433
|
-
query =
|
|
2899
|
+
query = _fixUsingParams(query, _params);
|
|
2434
2900
|
info.query_fixed = query;
|
|
2435
2901
|
info.operator = operator;
|
|
2436
2902
|
info.value = value;
|
|
@@ -2536,11 +3002,12 @@ class StableBrowser {
|
|
|
2536
3002
|
info.screenshotPath = screenshotPath;
|
|
2537
3003
|
Object.assign(e, { info: info });
|
|
2538
3004
|
error = e;
|
|
2539
|
-
throw e;
|
|
3005
|
+
// throw e;
|
|
3006
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2540
3007
|
}
|
|
2541
3008
|
finally {
|
|
2542
3009
|
const endTime = Date.now();
|
|
2543
|
-
|
|
3010
|
+
_reportToWorld(world, {
|
|
2544
3011
|
element_name: selectors.element_name,
|
|
2545
3012
|
type: Types.ANALYZE_TABLE,
|
|
2546
3013
|
text: "Analyze table",
|
|
@@ -2550,7 +3017,7 @@ class StableBrowser {
|
|
|
2550
3017
|
status: "FAILED",
|
|
2551
3018
|
startTime,
|
|
2552
3019
|
endTime,
|
|
2553
|
-
message: error
|
|
3020
|
+
message: error?.message,
|
|
2554
3021
|
}
|
|
2555
3022
|
: {
|
|
2556
3023
|
status: "PASSED",
|
|
@@ -2562,27 +3029,7 @@ class StableBrowser {
|
|
|
2562
3029
|
}
|
|
2563
3030
|
}
|
|
2564
3031
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2565
|
-
|
|
2566
|
-
return value;
|
|
2567
|
-
}
|
|
2568
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2569
|
-
let regex = /{{(.*?)}}/g;
|
|
2570
|
-
let matches = value.match(regex);
|
|
2571
|
-
if (matches) {
|
|
2572
|
-
const testData = this.getTestData(world);
|
|
2573
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2574
|
-
let match = matches[i];
|
|
2575
|
-
let key = match.substring(2, match.length - 2);
|
|
2576
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2577
|
-
if (newValue !== null) {
|
|
2578
|
-
value = value.replace(match, newValue);
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2583
|
-
return await decrypt(value, null, totpWait);
|
|
2584
|
-
}
|
|
2585
|
-
return value;
|
|
3032
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2586
3033
|
}
|
|
2587
3034
|
_getLoadTimeout(options) {
|
|
2588
3035
|
let timeout = 15000;
|
|
@@ -2594,6 +3041,32 @@ class StableBrowser {
|
|
|
2594
3041
|
}
|
|
2595
3042
|
return timeout;
|
|
2596
3043
|
}
|
|
3044
|
+
_getFindElementTimeout(options) {
|
|
3045
|
+
if (options && options.timeout) {
|
|
3046
|
+
return options.timeout;
|
|
3047
|
+
}
|
|
3048
|
+
if (this.configuration.find_element_timeout) {
|
|
3049
|
+
return this.configuration.find_element_timeout;
|
|
3050
|
+
}
|
|
3051
|
+
return 30000;
|
|
3052
|
+
}
|
|
3053
|
+
async saveStoreState(path = null, world = null) {
|
|
3054
|
+
const storageState = await this.page.context().storageState();
|
|
3055
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3056
|
+
if (path) {
|
|
3057
|
+
// save { storageState: storageState } into the path
|
|
3058
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3059
|
+
}
|
|
3060
|
+
else {
|
|
3061
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3062
|
+
}
|
|
3063
|
+
}
|
|
3064
|
+
async restoreSaveState(path = null, world = null) {
|
|
3065
|
+
await refreshBrowser(this, path, world);
|
|
3066
|
+
this.registerEventListeners(this.context);
|
|
3067
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3068
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3069
|
+
}
|
|
2597
3070
|
async waitForPageLoad(options = {}, world = null) {
|
|
2598
3071
|
let timeout = this._getLoadTimeout(options);
|
|
2599
3072
|
const promiseArray = [];
|
|
@@ -2633,7 +3106,7 @@ class StableBrowser {
|
|
|
2633
3106
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2634
3107
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2635
3108
|
const endTime = Date.now();
|
|
2636
|
-
|
|
3109
|
+
_reportToWorld(world, {
|
|
2637
3110
|
type: Types.GET_PAGE_STATUS,
|
|
2638
3111
|
text: "Wait for page load",
|
|
2639
3112
|
screenshotId,
|
|
@@ -2642,7 +3115,7 @@ class StableBrowser {
|
|
|
2642
3115
|
status: "FAILED",
|
|
2643
3116
|
startTime,
|
|
2644
3117
|
endTime,
|
|
2645
|
-
message: error
|
|
3118
|
+
message: error?.message,
|
|
2646
3119
|
}
|
|
2647
3120
|
: {
|
|
2648
3121
|
status: "PASSED",
|
|
@@ -2653,41 +3126,123 @@ class StableBrowser {
|
|
|
2653
3126
|
}
|
|
2654
3127
|
}
|
|
2655
3128
|
async closePage(options = {}, world = null) {
|
|
2656
|
-
const
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
3129
|
+
const state = {
|
|
3130
|
+
options,
|
|
3131
|
+
world,
|
|
3132
|
+
locate: false,
|
|
3133
|
+
scroll: false,
|
|
3134
|
+
highlight: false,
|
|
3135
|
+
type: Types.CLOSE_PAGE,
|
|
3136
|
+
text: `Close page`,
|
|
3137
|
+
_text: `Close the page`,
|
|
3138
|
+
operation: "closePage",
|
|
3139
|
+
log: "***** close page *****\n",
|
|
3140
|
+
throwError: false,
|
|
3141
|
+
};
|
|
2661
3142
|
try {
|
|
3143
|
+
await _preCommand(state, this);
|
|
2662
3144
|
await this.page.close();
|
|
2663
3145
|
}
|
|
2664
3146
|
catch (e) {
|
|
2665
3147
|
console.log(".");
|
|
3148
|
+
await _commandError(state, e, this);
|
|
2666
3149
|
}
|
|
2667
3150
|
finally {
|
|
2668
|
-
await
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
3151
|
+
await _commandFinally(state, this);
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3155
|
+
let operation = null;
|
|
3156
|
+
if (!options || !options.operation) {
|
|
3157
|
+
throw new Error("operation is not defined");
|
|
3158
|
+
}
|
|
3159
|
+
operation = options.operation;
|
|
3160
|
+
// validate operation is one of the supported operations
|
|
3161
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3162
|
+
throw new Error("operation is not supported");
|
|
3163
|
+
}
|
|
3164
|
+
const state = {
|
|
3165
|
+
options,
|
|
3166
|
+
world,
|
|
3167
|
+
locate: false,
|
|
3168
|
+
scroll: false,
|
|
3169
|
+
highlight: false,
|
|
3170
|
+
type: Types.TABLE_OPERATION,
|
|
3171
|
+
text: `Table operation`,
|
|
3172
|
+
_text: `Table ${operation} operation`,
|
|
3173
|
+
operation: operation,
|
|
3174
|
+
log: "***** Table operation *****\n",
|
|
3175
|
+
};
|
|
3176
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3177
|
+
try {
|
|
3178
|
+
await _preCommand(state, this);
|
|
3179
|
+
const start = Date.now();
|
|
3180
|
+
let cellArea = null;
|
|
3181
|
+
while (true) {
|
|
3182
|
+
try {
|
|
3183
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3184
|
+
if (cellArea) {
|
|
3185
|
+
break;
|
|
2681
3186
|
}
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
3187
|
+
}
|
|
3188
|
+
catch (e) {
|
|
3189
|
+
// ignore
|
|
3190
|
+
}
|
|
3191
|
+
if (Date.now() - start > timeout) {
|
|
3192
|
+
throw new Error(`Cell not found in table`);
|
|
3193
|
+
}
|
|
3194
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3195
|
+
}
|
|
3196
|
+
switch (operation) {
|
|
3197
|
+
case "click":
|
|
3198
|
+
if (!options.css) {
|
|
3199
|
+
// will click in the center of the cell
|
|
3200
|
+
let xOffset = 0;
|
|
3201
|
+
let yOffset = 0;
|
|
3202
|
+
if (options.xOffset) {
|
|
3203
|
+
xOffset = options.xOffset;
|
|
3204
|
+
}
|
|
3205
|
+
if (options.yOffset) {
|
|
3206
|
+
yOffset = options.yOffset;
|
|
3207
|
+
}
|
|
3208
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3209
|
+
}
|
|
3210
|
+
else {
|
|
3211
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3212
|
+
if (results.length === 0) {
|
|
3213
|
+
throw new Error(`Element not found in cell area`);
|
|
3214
|
+
}
|
|
3215
|
+
state.element = results[0];
|
|
3216
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3217
|
+
}
|
|
3218
|
+
break;
|
|
3219
|
+
case "hover+click":
|
|
3220
|
+
if (!options.css) {
|
|
3221
|
+
throw new Error("css is not defined");
|
|
3222
|
+
}
|
|
3223
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3224
|
+
if (results.length === 0) {
|
|
3225
|
+
throw new Error(`Element not found in cell area`);
|
|
3226
|
+
}
|
|
3227
|
+
state.element = results[0];
|
|
3228
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3229
|
+
break;
|
|
3230
|
+
default:
|
|
3231
|
+
throw new Error("operation is not supported");
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
catch (e) {
|
|
3235
|
+
await _commandError(state, e, this);
|
|
3236
|
+
}
|
|
3237
|
+
finally {
|
|
3238
|
+
await _commandFinally(state, this);
|
|
2689
3239
|
}
|
|
2690
3240
|
}
|
|
3241
|
+
saveTestDataAsGlobal(options, world) {
|
|
3242
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
3243
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
3244
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
3245
|
+
}
|
|
2691
3246
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2692
3247
|
const startTime = Date.now();
|
|
2693
3248
|
let error = null;
|
|
@@ -2705,21 +3260,23 @@ class StableBrowser {
|
|
|
2705
3260
|
}
|
|
2706
3261
|
catch (e) {
|
|
2707
3262
|
console.log(".");
|
|
3263
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2708
3264
|
}
|
|
2709
3265
|
finally {
|
|
2710
3266
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2711
3267
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2712
3268
|
const endTime = Date.now();
|
|
2713
|
-
|
|
3269
|
+
_reportToWorld(world, {
|
|
2714
3270
|
type: Types.SET_VIEWPORT,
|
|
2715
3271
|
text: "set viewport size to " + width + "x" + hight,
|
|
3272
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2716
3273
|
screenshotId,
|
|
2717
3274
|
result: error
|
|
2718
3275
|
? {
|
|
2719
3276
|
status: "FAILED",
|
|
2720
3277
|
startTime,
|
|
2721
3278
|
endTime,
|
|
2722
|
-
message: error
|
|
3279
|
+
message: error?.message,
|
|
2723
3280
|
}
|
|
2724
3281
|
: {
|
|
2725
3282
|
status: "PASSED",
|
|
@@ -2741,12 +3298,13 @@ class StableBrowser {
|
|
|
2741
3298
|
}
|
|
2742
3299
|
catch (e) {
|
|
2743
3300
|
console.log(".");
|
|
3301
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2744
3302
|
}
|
|
2745
3303
|
finally {
|
|
2746
3304
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2747
3305
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2748
3306
|
const endTime = Date.now();
|
|
2749
|
-
|
|
3307
|
+
_reportToWorld(world, {
|
|
2750
3308
|
type: Types.GET_PAGE_STATUS,
|
|
2751
3309
|
text: "page relaod",
|
|
2752
3310
|
screenshotId,
|
|
@@ -2755,7 +3313,7 @@ class StableBrowser {
|
|
|
2755
3313
|
status: "FAILED",
|
|
2756
3314
|
startTime,
|
|
2757
3315
|
endTime,
|
|
2758
|
-
message: error
|
|
3316
|
+
message: error?.message,
|
|
2759
3317
|
}
|
|
2760
3318
|
: {
|
|
2761
3319
|
status: "PASSED",
|
|
@@ -2782,11 +3340,122 @@ class StableBrowser {
|
|
|
2782
3340
|
console.log("#-#");
|
|
2783
3341
|
}
|
|
2784
3342
|
}
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
3343
|
+
async beforeScenario(world, scenario) {
|
|
3344
|
+
this.beforeScenarioCalled = true;
|
|
3345
|
+
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
3346
|
+
this.scenarioName = scenario.pickle.name;
|
|
3347
|
+
}
|
|
3348
|
+
if (scenario && scenario.gherkinDocument && scenario.gherkinDocument.feature) {
|
|
3349
|
+
this.featureName = scenario.gherkinDocument.feature.name;
|
|
3350
|
+
}
|
|
3351
|
+
if (this.context) {
|
|
3352
|
+
this.context.examplesRow = extractStepExampleParameters(scenario);
|
|
3353
|
+
}
|
|
3354
|
+
if (this.tags === null && scenario && scenario.pickle && scenario.pickle.tags) {
|
|
3355
|
+
this.tags = scenario.pickle.tags.map((tag) => tag.name);
|
|
3356
|
+
// check if @global_test_data tag is present
|
|
3357
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3358
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3359
|
+
}
|
|
3360
|
+
}
|
|
3361
|
+
// update test data based on feature/scenario
|
|
3362
|
+
let envName = null;
|
|
3363
|
+
if (this.context && this.context.environment) {
|
|
3364
|
+
envName = this.context.environment.name;
|
|
3365
|
+
}
|
|
3366
|
+
if (!process.env.TEMP_RUN) {
|
|
3367
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3368
|
+
}
|
|
3369
|
+
await loadBrunoParams(this.context, this.context.environment.name);
|
|
3370
|
+
}
|
|
3371
|
+
async afterScenario(world, scenario) { }
|
|
3372
|
+
async beforeStep(world, step) {
|
|
3373
|
+
if (!this.beforeScenarioCalled) {
|
|
3374
|
+
this.beforeScenario(world, step);
|
|
3375
|
+
}
|
|
3376
|
+
if (this.stepIndex === undefined) {
|
|
3377
|
+
this.stepIndex = 0;
|
|
3378
|
+
}
|
|
3379
|
+
else {
|
|
3380
|
+
this.stepIndex++;
|
|
3381
|
+
}
|
|
3382
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3383
|
+
this.stepName = step.pickleStep.text;
|
|
3384
|
+
this.logger.info("step: " + this.stepName);
|
|
3385
|
+
}
|
|
3386
|
+
else if (step && step.text) {
|
|
3387
|
+
this.stepName = step.text;
|
|
3388
|
+
}
|
|
3389
|
+
else {
|
|
3390
|
+
this.stepName = "step " + this.stepIndex;
|
|
3391
|
+
}
|
|
3392
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3393
|
+
if (this.context.browserObject.context) {
|
|
3394
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
if (this.initSnapshotTaken === false) {
|
|
3398
|
+
this.initSnapshotTaken = true;
|
|
3399
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3400
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3401
|
+
if (snapshot) {
|
|
3402
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
async getAriaSnapshot() {
|
|
3408
|
+
try {
|
|
3409
|
+
// find the page url
|
|
3410
|
+
const url = await this.page.url();
|
|
3411
|
+
// extract the path from the url
|
|
3412
|
+
const path = new URL(url).pathname;
|
|
3413
|
+
// get the page title
|
|
3414
|
+
const title = await this.page.title();
|
|
3415
|
+
// go over other frams
|
|
3416
|
+
const frames = this.page.frames();
|
|
3417
|
+
const snapshots = [];
|
|
3418
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3419
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3420
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3421
|
+
content.push(`- frame: ${i}`);
|
|
3422
|
+
const frame = frames[i];
|
|
3423
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3424
|
+
content.push(snapshot);
|
|
3425
|
+
}
|
|
3426
|
+
return content.join("\n");
|
|
3427
|
+
}
|
|
3428
|
+
catch (e) {
|
|
3429
|
+
console.error(e);
|
|
3430
|
+
}
|
|
3431
|
+
return null;
|
|
3432
|
+
}
|
|
3433
|
+
async afterStep(world, step) {
|
|
3434
|
+
this.stepName = null;
|
|
3435
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3436
|
+
if (this.context.browserObject.context) {
|
|
3437
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
3438
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3439
|
+
});
|
|
3440
|
+
if (world && world.attach) {
|
|
3441
|
+
await world.attach(JSON.stringify({
|
|
3442
|
+
type: "trace",
|
|
3443
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3444
|
+
}), "application/json+trace");
|
|
3445
|
+
}
|
|
3446
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
if (this.context) {
|
|
3450
|
+
this.context.examplesRow = null;
|
|
3451
|
+
}
|
|
3452
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3453
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3454
|
+
if (snapshot) {
|
|
3455
|
+
const obj = {};
|
|
3456
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3457
|
+
}
|
|
2788
3458
|
}
|
|
2789
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2790
3459
|
}
|
|
2791
3460
|
}
|
|
2792
3461
|
function createTimedPromise(promise, label) {
|
|
@@ -2794,151 +3463,5 @@ function createTimedPromise(promise, label) {
|
|
|
2794
3463
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2795
3464
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2796
3465
|
}
|
|
2797
|
-
const KEYBOARD_EVENTS = [
|
|
2798
|
-
"ALT",
|
|
2799
|
-
"AltGraph",
|
|
2800
|
-
"CapsLock",
|
|
2801
|
-
"Control",
|
|
2802
|
-
"Fn",
|
|
2803
|
-
"FnLock",
|
|
2804
|
-
"Hyper",
|
|
2805
|
-
"Meta",
|
|
2806
|
-
"NumLock",
|
|
2807
|
-
"ScrollLock",
|
|
2808
|
-
"Shift",
|
|
2809
|
-
"Super",
|
|
2810
|
-
"Symbol",
|
|
2811
|
-
"SymbolLock",
|
|
2812
|
-
"Enter",
|
|
2813
|
-
"Tab",
|
|
2814
|
-
"ArrowDown",
|
|
2815
|
-
"ArrowLeft",
|
|
2816
|
-
"ArrowRight",
|
|
2817
|
-
"ArrowUp",
|
|
2818
|
-
"End",
|
|
2819
|
-
"Home",
|
|
2820
|
-
"PageDown",
|
|
2821
|
-
"PageUp",
|
|
2822
|
-
"Backspace",
|
|
2823
|
-
"Clear",
|
|
2824
|
-
"Copy",
|
|
2825
|
-
"CrSel",
|
|
2826
|
-
"Cut",
|
|
2827
|
-
"Delete",
|
|
2828
|
-
"EraseEof",
|
|
2829
|
-
"ExSel",
|
|
2830
|
-
"Insert",
|
|
2831
|
-
"Paste",
|
|
2832
|
-
"Redo",
|
|
2833
|
-
"Undo",
|
|
2834
|
-
"Accept",
|
|
2835
|
-
"Again",
|
|
2836
|
-
"Attn",
|
|
2837
|
-
"Cancel",
|
|
2838
|
-
"ContextMenu",
|
|
2839
|
-
"Escape",
|
|
2840
|
-
"Execute",
|
|
2841
|
-
"Find",
|
|
2842
|
-
"Finish",
|
|
2843
|
-
"Help",
|
|
2844
|
-
"Pause",
|
|
2845
|
-
"Play",
|
|
2846
|
-
"Props",
|
|
2847
|
-
"Select",
|
|
2848
|
-
"ZoomIn",
|
|
2849
|
-
"ZoomOut",
|
|
2850
|
-
"BrightnessDown",
|
|
2851
|
-
"BrightnessUp",
|
|
2852
|
-
"Eject",
|
|
2853
|
-
"LogOff",
|
|
2854
|
-
"Power",
|
|
2855
|
-
"PowerOff",
|
|
2856
|
-
"PrintScreen",
|
|
2857
|
-
"Hibernate",
|
|
2858
|
-
"Standby",
|
|
2859
|
-
"WakeUp",
|
|
2860
|
-
"AllCandidates",
|
|
2861
|
-
"Alphanumeric",
|
|
2862
|
-
"CodeInput",
|
|
2863
|
-
"Compose",
|
|
2864
|
-
"Convert",
|
|
2865
|
-
"Dead",
|
|
2866
|
-
"FinalMode",
|
|
2867
|
-
"GroupFirst",
|
|
2868
|
-
"GroupLast",
|
|
2869
|
-
"GroupNext",
|
|
2870
|
-
"GroupPrevious",
|
|
2871
|
-
"ModeChange",
|
|
2872
|
-
"NextCandidate",
|
|
2873
|
-
"NonConvert",
|
|
2874
|
-
"PreviousCandidate",
|
|
2875
|
-
"Process",
|
|
2876
|
-
"SingleCandidate",
|
|
2877
|
-
"HangulMode",
|
|
2878
|
-
"HanjaMode",
|
|
2879
|
-
"JunjaMode",
|
|
2880
|
-
"Eisu",
|
|
2881
|
-
"Hankaku",
|
|
2882
|
-
"Hiragana",
|
|
2883
|
-
"HiraganaKatakana",
|
|
2884
|
-
"KanaMode",
|
|
2885
|
-
"KanjiMode",
|
|
2886
|
-
"Katakana",
|
|
2887
|
-
"Romaji",
|
|
2888
|
-
"Zenkaku",
|
|
2889
|
-
"ZenkakuHanaku",
|
|
2890
|
-
"F1",
|
|
2891
|
-
"F2",
|
|
2892
|
-
"F3",
|
|
2893
|
-
"F4",
|
|
2894
|
-
"F5",
|
|
2895
|
-
"F6",
|
|
2896
|
-
"F7",
|
|
2897
|
-
"F8",
|
|
2898
|
-
"F9",
|
|
2899
|
-
"F10",
|
|
2900
|
-
"F11",
|
|
2901
|
-
"F12",
|
|
2902
|
-
"Soft1",
|
|
2903
|
-
"Soft2",
|
|
2904
|
-
"Soft3",
|
|
2905
|
-
"Soft4",
|
|
2906
|
-
"ChannelDown",
|
|
2907
|
-
"ChannelUp",
|
|
2908
|
-
"Close",
|
|
2909
|
-
"MailForward",
|
|
2910
|
-
"MailReply",
|
|
2911
|
-
"MailSend",
|
|
2912
|
-
"MediaFastForward",
|
|
2913
|
-
"MediaPause",
|
|
2914
|
-
"MediaPlay",
|
|
2915
|
-
"MediaPlayPause",
|
|
2916
|
-
"MediaRecord",
|
|
2917
|
-
"MediaRewind",
|
|
2918
|
-
"MediaStop",
|
|
2919
|
-
"MediaTrackNext",
|
|
2920
|
-
"MediaTrackPrevious",
|
|
2921
|
-
"AudioBalanceLeft",
|
|
2922
|
-
"AudioBalanceRight",
|
|
2923
|
-
"AudioBassBoostDown",
|
|
2924
|
-
"AudioBassBoostToggle",
|
|
2925
|
-
"AudioBassBoostUp",
|
|
2926
|
-
"AudioFaderFront",
|
|
2927
|
-
"AudioFaderRear",
|
|
2928
|
-
"AudioSurroundModeNext",
|
|
2929
|
-
"AudioTrebleDown",
|
|
2930
|
-
"AudioTrebleUp",
|
|
2931
|
-
"AudioVolumeDown",
|
|
2932
|
-
"AudioVolumeMute",
|
|
2933
|
-
"AudioVolumeUp",
|
|
2934
|
-
"MicrophoneToggle",
|
|
2935
|
-
"MicrophoneVolumeDown",
|
|
2936
|
-
"MicrophoneVolumeMute",
|
|
2937
|
-
"MicrophoneVolumeUp",
|
|
2938
|
-
"TV",
|
|
2939
|
-
"TV3DMode",
|
|
2940
|
-
"TVAntennaCable",
|
|
2941
|
-
"TVAudioDescription",
|
|
2942
|
-
];
|
|
2943
3466
|
export { StableBrowser };
|
|
2944
3467
|
//# sourceMappingURL=stable_browser.js.map
|