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