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