automation_model 1.0.432-dev → 1.0.432
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/lib/api.d.ts +43 -1
- package/lib/api.js +241 -41
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +5 -2
- package/lib/auto_page.js +177 -46
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +7 -3
- package/lib/browser_manager.js +157 -49
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.d.ts +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 +103 -36
- package/lib/stable_browser.js +1781 -1242
- 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 +13 -8
package/lib/stable_browser.js
CHANGED
|
@@ -2,21 +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";
|
|
18
|
-
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
|
+
import { getTestData } from "./auto_page.js";
|
|
19
|
+
import { locate_element } from "./locate_element.js";
|
|
20
|
+
import { randomUUID } from "crypto";
|
|
21
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
22
|
+
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
23
|
+
import { LocatorLog } from "./locator_log.js";
|
|
24
|
+
import axios from "axios";
|
|
25
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
26
|
+
export const Types = {
|
|
19
27
|
CLICK: "click_element",
|
|
28
|
+
WAIT_ELEMENT: "wait_element",
|
|
20
29
|
NAVIGATE: "navigate",
|
|
21
30
|
FILL: "fill_element",
|
|
22
31
|
EXECUTE: "execute_page_method",
|
|
@@ -26,6 +35,8 @@ const Types = {
|
|
|
26
35
|
GET_PAGE_STATUS: "get_page_status",
|
|
27
36
|
CLICK_ROW_ACTION: "click_row_action",
|
|
28
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",
|
|
29
40
|
ANALYZE_TABLE: "analyze_table",
|
|
30
41
|
SELECT: "select_combobox",
|
|
31
42
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -36,21 +47,41 @@ const Types = {
|
|
|
36
47
|
UNCHECK: "uncheck_element",
|
|
37
48
|
EXTRACT: "extract_attribute",
|
|
38
49
|
CLOSE_PAGE: "close_page",
|
|
50
|
+
TABLE_OPERATION: "table_operation",
|
|
39
51
|
SET_DATE_TIME: "set_date_time",
|
|
40
52
|
SET_VIEWPORT: "set_viewport",
|
|
41
53
|
VERIFY_VISUAL: "verify_visual",
|
|
42
54
|
LOAD_DATA: "load_data",
|
|
43
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";
|
|
44
64
|
};
|
|
45
65
|
class StableBrowser {
|
|
46
|
-
|
|
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) {
|
|
47
80
|
this.browser = browser;
|
|
48
81
|
this.page = page;
|
|
49
82
|
this.logger = logger;
|
|
50
83
|
this.context = context;
|
|
51
|
-
this.
|
|
52
|
-
this.webLogFile = null;
|
|
53
|
-
this.configuration = null;
|
|
84
|
+
this.world = world;
|
|
54
85
|
if (!this.logger) {
|
|
55
86
|
this.logger = console;
|
|
56
87
|
}
|
|
@@ -75,17 +106,32 @@ class StableBrowser {
|
|
|
75
106
|
catch (e) {
|
|
76
107
|
this.logger.error("unable to read ai_config.json");
|
|
77
108
|
}
|
|
78
|
-
const logFolder = path.join(this.project_path, "logs", "web");
|
|
79
|
-
this.webLogFile = this.getWebLogFile(logFolder);
|
|
80
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
81
|
-
this.registerRequestListener();
|
|
82
|
-
context.pages = [this.page];
|
|
83
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
|
+
}
|
|
84
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
|
+
}
|
|
85
129
|
context.pageLoading.status = true;
|
|
86
130
|
this.page = page;
|
|
87
131
|
context.page = page;
|
|
88
132
|
context.pages.push(page);
|
|
133
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
134
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
89
135
|
page.on("close", async () => {
|
|
90
136
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
91
137
|
this.context.pages.pop();
|
|
@@ -110,117 +156,164 @@ class StableBrowser {
|
|
|
110
156
|
context.pageLoading.status = false;
|
|
111
157
|
}.bind(this));
|
|
112
158
|
}
|
|
113
|
-
|
|
114
|
-
if (
|
|
115
|
-
|
|
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
|
+
}
|
|
116
195
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|
|
120
206
|
}
|
|
121
|
-
|
|
122
|
-
return path.join(logFolder, fileName);
|
|
207
|
+
throw new Error("Tab not found: " + tabTitleOrIndex);
|
|
123
208
|
}
|
|
124
|
-
registerConsoleLogListener(page, context
|
|
209
|
+
registerConsoleLogListener(page, context) {
|
|
125
210
|
if (!this.context.webLogger) {
|
|
126
211
|
this.context.webLogger = [];
|
|
127
212
|
}
|
|
128
213
|
page.on("console", async (msg) => {
|
|
129
|
-
|
|
214
|
+
const obj = {
|
|
130
215
|
type: msg.type(),
|
|
131
216
|
text: msg.text(),
|
|
132
217
|
location: msg.location(),
|
|
133
218
|
time: new Date().toISOString(),
|
|
134
|
-
}
|
|
135
|
-
|
|
219
|
+
};
|
|
220
|
+
this.context.webLogger.push(obj);
|
|
221
|
+
if (msg.type() === "error") {
|
|
222
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
223
|
+
}
|
|
136
224
|
});
|
|
137
225
|
}
|
|
138
|
-
registerRequestListener() {
|
|
139
|
-
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();
|
|
140
232
|
try {
|
|
141
|
-
const pageUrl = new URL(
|
|
233
|
+
const pageUrl = new URL(page.url());
|
|
142
234
|
const requestUrl = new URL(data.url());
|
|
143
235
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
144
236
|
const method = data.method();
|
|
145
|
-
if (
|
|
237
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
146
238
|
const token = await data.headerValue("Authorization");
|
|
147
239
|
if (token) {
|
|
148
|
-
|
|
240
|
+
context.authtoken = token;
|
|
149
241
|
}
|
|
150
242
|
}
|
|
151
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" });
|
|
152
256
|
}
|
|
153
257
|
catch (error) {
|
|
154
|
-
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));
|
|
155
266
|
}
|
|
156
267
|
});
|
|
157
268
|
}
|
|
158
269
|
// async closeUnexpectedPopups() {
|
|
159
270
|
// await closeUnexpectedPopups(this.page);
|
|
160
271
|
// }
|
|
161
|
-
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
|
+
}
|
|
162
276
|
if (!url.startsWith("http")) {
|
|
163
277
|
url = "https://" + url;
|
|
164
278
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (!_params || typeof text !== "string") {
|
|
185
|
-
return text;
|
|
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);
|
|
186
298
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// remove the _ prefix
|
|
191
|
-
regValue = key.substring(1);
|
|
192
|
-
}
|
|
193
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
299
|
+
catch (error) {
|
|
300
|
+
console.error("Error on goto", error);
|
|
301
|
+
_commandError(state, error, this);
|
|
194
302
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
198
|
-
// check if not null
|
|
199
|
-
if (!locator) {
|
|
200
|
-
return locator;
|
|
303
|
+
finally {
|
|
304
|
+
await _commandFinally(state, this);
|
|
201
305
|
}
|
|
202
|
-
// clone the locator
|
|
203
|
-
locator = JSON.parse(JSON.stringify(locator));
|
|
204
|
-
this.scanAndManipulate(locator, _params);
|
|
205
|
-
return locator;
|
|
206
306
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
else if (this._isObject(currentObj[key])) {
|
|
217
|
-
// Recursively scan nested objects
|
|
218
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
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);
|
|
219
315
|
}
|
|
220
316
|
}
|
|
221
|
-
}
|
|
222
|
-
_getLocator(locator, scope, _params) {
|
|
223
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
224
317
|
let locatorReturn;
|
|
225
318
|
if (locator.role) {
|
|
226
319
|
if (locator.role[1].nameReg) {
|
|
@@ -228,7 +321,7 @@ class StableBrowser {
|
|
|
228
321
|
delete locator.role[1].nameReg;
|
|
229
322
|
}
|
|
230
323
|
// if (locator.role[1].name) {
|
|
231
|
-
// locator.role[1].name =
|
|
324
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
232
325
|
// }
|
|
233
326
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
234
327
|
}
|
|
@@ -247,7 +340,7 @@ class StableBrowser {
|
|
|
247
340
|
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
248
341
|
}
|
|
249
342
|
}
|
|
250
|
-
if (locator
|
|
343
|
+
if (locator?.engine) {
|
|
251
344
|
if (locator.engine === "css") {
|
|
252
345
|
locatorReturn = scope.locator(locator.selector);
|
|
253
346
|
}
|
|
@@ -268,192 +361,181 @@ class StableBrowser {
|
|
|
268
361
|
return locatorReturn;
|
|
269
362
|
}
|
|
270
363
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
271
|
-
|
|
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);
|
|
272
368
|
if (result.elementCount === 0) {
|
|
273
369
|
return;
|
|
274
370
|
}
|
|
275
|
-
let textElementCss = "[data-blinq-id
|
|
371
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
276
372
|
// css climb to parent element
|
|
277
373
|
const climbArray = [];
|
|
278
374
|
for (let i = 0; i < climb; i++) {
|
|
279
375
|
climbArray.push("..");
|
|
280
376
|
}
|
|
281
377
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
320
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
321
|
-
if (!shadowElement) {
|
|
322
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
326
|
-
elements = elements.concat(shadowElements);
|
|
327
|
-
}
|
|
328
|
-
let randomToken = null;
|
|
329
|
-
const foundElements = [];
|
|
330
|
-
if (regex) {
|
|
331
|
-
let regexpSearch = new RegExp(text, "im");
|
|
332
|
-
for (let i = 0; i < elements.length; i++) {
|
|
333
|
-
const element = elements[i];
|
|
334
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
335
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
336
|
-
foundElements.push(element);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
text = text.trim();
|
|
342
|
-
for (let i = 0; i < elements.length; i++) {
|
|
343
|
-
const element = elements[i];
|
|
344
|
-
if (partial) {
|
|
345
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
346
|
-
(element.value && element.value.includes(text))) {
|
|
347
|
-
foundElements.push(element);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
else {
|
|
351
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
352
|
-
(element.value && element.value === text)) {
|
|
353
|
-
foundElements.push(element);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
let noChildElements = [];
|
|
359
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
360
|
-
let element = foundElements[i];
|
|
361
|
-
let hasChild = false;
|
|
362
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
363
|
-
if (i === j) {
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
if (isParent(element, foundElements[j])) {
|
|
367
|
-
hasChild = true;
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
if (!hasChild) {
|
|
372
|
-
noChildElements.push(element);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
let elementCount = 0;
|
|
376
|
-
if (noChildElements.length > 0) {
|
|
377
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
378
|
-
if (randomToken === null) {
|
|
379
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
380
|
-
}
|
|
381
|
-
let element = noChildElements[i];
|
|
382
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
383
|
-
elementCount++;
|
|
384
|
-
}
|
|
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;
|
|
385
415
|
}
|
|
386
|
-
|
|
387
|
-
}
|
|
416
|
+
tagCount++;
|
|
417
|
+
}
|
|
418
|
+
return { elementCount: tagCount, randomToken };
|
|
388
419
|
}
|
|
389
|
-
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
|
+
}
|
|
390
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
|
+
}
|
|
391
440
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
392
441
|
let locator = null;
|
|
393
442
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
394
|
-
|
|
443
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
444
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
395
445
|
if (!locatorString) {
|
|
446
|
+
info.failCause.textNotFound = true;
|
|
447
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
396
448
|
return;
|
|
397
449
|
}
|
|
398
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
450
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
399
451
|
}
|
|
400
452
|
else if (locatorSearch.text) {
|
|
401
|
-
let
|
|
453
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
454
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
402
455
|
if (result.elementCount === 0) {
|
|
456
|
+
info.failCause.textNotFound = true;
|
|
457
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
403
458
|
return;
|
|
404
459
|
}
|
|
405
|
-
locatorSearch.css = "[data-blinq-id
|
|
460
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
406
461
|
if (locatorSearch.childCss) {
|
|
407
462
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
408
463
|
}
|
|
409
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
464
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
410
465
|
}
|
|
411
466
|
else {
|
|
412
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
467
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
413
468
|
}
|
|
414
469
|
// let cssHref = false;
|
|
415
470
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
416
471
|
// cssHref = true;
|
|
417
472
|
// }
|
|
418
473
|
let count = await locator.count();
|
|
474
|
+
if (count > 0 && !info.failCause.count) {
|
|
475
|
+
info.failCause.count = count;
|
|
476
|
+
}
|
|
419
477
|
//info.log += "total elements found " + count + "\n";
|
|
420
478
|
//let visibleCount = 0;
|
|
421
479
|
let visibleLocator = null;
|
|
422
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
480
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
423
481
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
482
|
+
if (info.locatorLog) {
|
|
483
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
484
|
+
}
|
|
424
485
|
return;
|
|
425
486
|
}
|
|
487
|
+
if (info.locatorLog && count === 0) {
|
|
488
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
489
|
+
}
|
|
426
490
|
for (let j = 0; j < count; j++) {
|
|
427
491
|
let visible = await locator.nth(j).isVisible();
|
|
428
492
|
const enabled = await locator.nth(j).isEnabled();
|
|
429
493
|
if (!visibleOnly) {
|
|
430
494
|
visible = true;
|
|
431
495
|
}
|
|
432
|
-
if (visible && enabled) {
|
|
496
|
+
if (visible && (allowDisabled || enabled)) {
|
|
433
497
|
foundLocators.push(locator.nth(j));
|
|
498
|
+
if (info.locatorLog) {
|
|
499
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
500
|
+
}
|
|
434
501
|
}
|
|
435
502
|
else {
|
|
503
|
+
info.failCause.visible = visible;
|
|
504
|
+
info.failCause.enabled = enabled;
|
|
436
505
|
if (!info.printMessages) {
|
|
437
506
|
info.printMessages = {};
|
|
438
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
|
+
}
|
|
439
516
|
if (!info.printMessages[j.toString()]) {
|
|
440
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
517
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
441
518
|
info.printMessages[j.toString()] = true;
|
|
442
519
|
}
|
|
443
520
|
}
|
|
444
521
|
}
|
|
445
522
|
}
|
|
446
523
|
async closeUnexpectedPopups(info, _params) {
|
|
524
|
+
if (!info) {
|
|
525
|
+
info = {};
|
|
526
|
+
info.failCause = {};
|
|
527
|
+
info.log = "";
|
|
528
|
+
}
|
|
447
529
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
448
530
|
if (!info) {
|
|
449
531
|
info = {};
|
|
450
532
|
}
|
|
451
|
-
info.log += "scan for popup handlers" + "\n";
|
|
533
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
452
534
|
const handlerGroup = [];
|
|
453
535
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
454
536
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
455
537
|
}
|
|
456
|
-
const scopes =
|
|
538
|
+
const scopes = this.page.frames().filter((frame) => frame.url() !== "about:blank");
|
|
457
539
|
let result = null;
|
|
458
540
|
let scope = null;
|
|
459
541
|
for (let i = 0; i < scopes.length; i++) {
|
|
@@ -475,42 +557,116 @@ class StableBrowser {
|
|
|
475
557
|
}
|
|
476
558
|
if (result.foundElements.length > 0) {
|
|
477
559
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
478
|
-
|
|
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
|
+
}
|
|
479
575
|
return { rerun: true };
|
|
480
576
|
}
|
|
481
577
|
}
|
|
482
578
|
}
|
|
483
579
|
return { rerun: false };
|
|
484
580
|
}
|
|
485
|
-
async _locate(selectors, info, _params, timeout =
|
|
581
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
582
|
+
if (!timeout) {
|
|
583
|
+
timeout = 30000;
|
|
584
|
+
}
|
|
486
585
|
for (let i = 0; i < 3; i++) {
|
|
487
586
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
488
587
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
489
588
|
let selector = selectors.locators[j];
|
|
490
589
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
491
590
|
}
|
|
492
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
591
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
493
592
|
if (!element.rerun) {
|
|
494
|
-
|
|
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);
|
|
495
615
|
}
|
|
496
616
|
}
|
|
497
617
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
498
618
|
}
|
|
499
|
-
async
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
619
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
620
|
+
if (!info) {
|
|
621
|
+
info = {};
|
|
622
|
+
info.failCause = {};
|
|
623
|
+
info.log = "";
|
|
624
|
+
}
|
|
625
|
+
let startTime = Date.now();
|
|
505
626
|
let scope = this.page;
|
|
627
|
+
if (selectors.frame) {
|
|
628
|
+
return selectors.frame;
|
|
629
|
+
}
|
|
506
630
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
507
|
-
|
|
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;
|
|
508
657
|
while (true) {
|
|
509
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
|
+
}
|
|
510
665
|
if (selectors.frameLocators) {
|
|
511
666
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
512
667
|
let frameLocator = selectors.frameLocators[i];
|
|
513
668
|
if (frameLocator.css) {
|
|
669
|
+
fLocator = frameLocator.css;
|
|
514
670
|
scope = scope.frameLocator(frameLocator.css);
|
|
515
671
|
frameFound = true;
|
|
516
672
|
break;
|
|
@@ -518,20 +674,55 @@ class StableBrowser {
|
|
|
518
674
|
}
|
|
519
675
|
}
|
|
520
676
|
if (!frameFound && selectors.iframe_src) {
|
|
677
|
+
fLocator = selectors.iframe_src;
|
|
521
678
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
522
679
|
}
|
|
523
680
|
if (!scope) {
|
|
524
|
-
info
|
|
525
|
-
|
|
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}"`;
|
|
526
688
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
527
689
|
}
|
|
528
690
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
529
691
|
}
|
|
530
692
|
else {
|
|
693
|
+
if (info && info.locatorLog) {
|
|
694
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
695
|
+
}
|
|
531
696
|
break;
|
|
532
697
|
}
|
|
533
698
|
}
|
|
534
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);
|
|
535
726
|
let selectorsLocators = null;
|
|
536
727
|
selectorsLocators = selectors.locators;
|
|
537
728
|
// group selectors by priority
|
|
@@ -567,18 +758,13 @@ class StableBrowser {
|
|
|
567
758
|
}
|
|
568
759
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
569
760
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
570
|
-
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);
|
|
571
762
|
if (result.foundElements.length === 0) {
|
|
572
763
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
573
|
-
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);
|
|
574
765
|
}
|
|
575
|
-
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
576
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
577
|
-
}
|
|
578
|
-
else {
|
|
579
|
-
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
580
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
581
|
-
}
|
|
766
|
+
if (result.foundElements.length === 0 && (onlyPriority3 || !highPriorityOnly)) {
|
|
767
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled, selectors?.element_name);
|
|
582
768
|
}
|
|
583
769
|
let foundElements = result.foundElements;
|
|
584
770
|
if (foundElements.length === 1 && foundElements[0].unique) {
|
|
@@ -618,24 +804,43 @@ class StableBrowser {
|
|
|
618
804
|
return maxCountElement.locator;
|
|
619
805
|
}
|
|
620
806
|
}
|
|
621
|
-
if (
|
|
807
|
+
if (Date.now() - startTime > timeout) {
|
|
622
808
|
break;
|
|
623
809
|
}
|
|
624
|
-
if (
|
|
625
|
-
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";
|
|
626
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
|
+
}
|
|
627
817
|
}
|
|
628
|
-
if (
|
|
629
|
-
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";
|
|
630
820
|
visibleOnly = false;
|
|
631
821
|
}
|
|
632
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
|
+
}
|
|
633
828
|
}
|
|
634
829
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
635
|
-
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
|
+
}
|
|
636
841
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
637
842
|
}
|
|
638
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
843
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
639
844
|
let foundElements = [];
|
|
640
845
|
const result = {
|
|
641
846
|
foundElements: foundElements,
|
|
@@ -643,14 +848,15 @@ class StableBrowser {
|
|
|
643
848
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
644
849
|
let foundLocators = [];
|
|
645
850
|
try {
|
|
646
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
851
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
647
852
|
}
|
|
648
853
|
catch (e) {
|
|
649
|
-
this
|
|
650
|
-
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);
|
|
651
857
|
foundLocators = [];
|
|
652
858
|
try {
|
|
653
|
-
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);
|
|
654
860
|
}
|
|
655
861
|
catch (e) {
|
|
656
862
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -664,90 +870,228 @@ class StableBrowser {
|
|
|
664
870
|
});
|
|
665
871
|
result.locatorIndex = i;
|
|
666
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
|
+
}
|
|
667
910
|
}
|
|
668
911
|
return result;
|
|
669
912
|
}
|
|
670
|
-
async
|
|
671
|
-
|
|
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);
|
|
672
927
|
const startTime = Date.now();
|
|
673
|
-
|
|
674
|
-
|
|
928
|
+
let timeout = 30000;
|
|
929
|
+
if (options && options.timeout) {
|
|
930
|
+
timeout = options.timeout;
|
|
675
931
|
}
|
|
676
|
-
|
|
677
|
-
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
678
|
-
info.operation = "click";
|
|
679
|
-
info.selectors = selectors;
|
|
680
|
-
let error = null;
|
|
681
|
-
let screenshotId = null;
|
|
682
|
-
let screenshotPath = null;
|
|
683
|
-
try {
|
|
684
|
-
let element = await this._locate(selectors, info, _params);
|
|
685
|
-
await this.scrollIfNeeded(element, info);
|
|
686
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
932
|
+
while (true) {
|
|
687
933
|
try {
|
|
688
|
-
await this.
|
|
689
|
-
|
|
690
|
-
|
|
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
|
+
}
|
|
691
947
|
}
|
|
692
948
|
catch (e) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
+
}
|
|
698
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);
|
|
699
1026
|
await this.waitForPageLoad();
|
|
700
|
-
return info;
|
|
1027
|
+
return state.info;
|
|
701
1028
|
}
|
|
702
1029
|
catch (e) {
|
|
703
|
-
|
|
704
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
705
|
-
info.screenshotPath = screenshotPath;
|
|
706
|
-
Object.assign(e, { info: info });
|
|
707
|
-
error = e;
|
|
708
|
-
throw e;
|
|
1030
|
+
await _commandError(state, e, this);
|
|
709
1031
|
}
|
|
710
1032
|
finally {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
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);
|
|
731
1065
|
}
|
|
1066
|
+
return found;
|
|
732
1067
|
}
|
|
733
1068
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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
|
+
};
|
|
744
1080
|
try {
|
|
745
|
-
|
|
746
|
-
|
|
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));
|
|
747
1085
|
try {
|
|
748
|
-
|
|
749
|
-
|
|
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);
|
|
750
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);
|
|
751
1095
|
}
|
|
752
1096
|
catch (e) {
|
|
753
1097
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -755,179 +1099,102 @@ class StableBrowser {
|
|
|
755
1099
|
}
|
|
756
1100
|
else {
|
|
757
1101
|
//await this.closeUnexpectedPopups();
|
|
758
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
759
|
-
element = await this._locate(selectors, info, _params);
|
|
760
|
-
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 });
|
|
761
1105
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
762
1106
|
}
|
|
763
1107
|
}
|
|
764
1108
|
await this.waitForPageLoad();
|
|
765
|
-
return info;
|
|
1109
|
+
return state.info;
|
|
766
1110
|
}
|
|
767
1111
|
catch (e) {
|
|
768
|
-
|
|
769
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
770
|
-
info.screenshotPath = screenshotPath;
|
|
771
|
-
Object.assign(e, { info: info });
|
|
772
|
-
error = e;
|
|
773
|
-
throw e;
|
|
1112
|
+
await _commandError(state, e, this);
|
|
774
1113
|
}
|
|
775
1114
|
finally {
|
|
776
|
-
|
|
777
|
-
this._reportToWorld(world, {
|
|
778
|
-
element_name: selectors.element_name,
|
|
779
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
780
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
781
|
-
screenshotId,
|
|
782
|
-
result: error
|
|
783
|
-
? {
|
|
784
|
-
status: "FAILED",
|
|
785
|
-
startTime,
|
|
786
|
-
endTime,
|
|
787
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
788
|
-
}
|
|
789
|
-
: {
|
|
790
|
-
status: "PASSED",
|
|
791
|
-
startTime,
|
|
792
|
-
endTime,
|
|
793
|
-
},
|
|
794
|
-
info: info,
|
|
795
|
-
});
|
|
1115
|
+
await _commandFinally(state, this);
|
|
796
1116
|
}
|
|
797
1117
|
}
|
|
798
1118
|
async hover(selectors, _params, options = {}, world = null) {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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
|
+
};
|
|
808
1130
|
try {
|
|
809
|
-
|
|
810
|
-
(
|
|
811
|
-
|
|
812
|
-
await this._highlightElements(element);
|
|
813
|
-
await element.hover();
|
|
814
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
815
|
-
}
|
|
816
|
-
catch (e) {
|
|
817
|
-
//await this.closeUnexpectedPopups();
|
|
818
|
-
info.log += "hover failed, will try again" + "\n";
|
|
819
|
-
element = await this._locate(selectors, info, _params);
|
|
820
|
-
await element.hover({ timeout: 10000 });
|
|
821
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
822
|
-
}
|
|
1131
|
+
await _preCommand(state, this);
|
|
1132
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1133
|
+
await _screenshot(state, this);
|
|
823
1134
|
await this.waitForPageLoad();
|
|
824
|
-
return info;
|
|
1135
|
+
return state.info;
|
|
825
1136
|
}
|
|
826
1137
|
catch (e) {
|
|
827
|
-
|
|
828
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
829
|
-
info.screenshotPath = screenshotPath;
|
|
830
|
-
Object.assign(e, { info: info });
|
|
831
|
-
error = e;
|
|
832
|
-
throw e;
|
|
1138
|
+
await _commandError(state, e, this);
|
|
833
1139
|
}
|
|
834
1140
|
finally {
|
|
835
|
-
|
|
836
|
-
this._reportToWorld(world, {
|
|
837
|
-
element_name: selectors.element_name,
|
|
838
|
-
type: Types.HOVER,
|
|
839
|
-
text: `Hover element`,
|
|
840
|
-
screenshotId,
|
|
841
|
-
result: error
|
|
842
|
-
? {
|
|
843
|
-
status: "FAILED",
|
|
844
|
-
startTime,
|
|
845
|
-
endTime,
|
|
846
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
847
|
-
}
|
|
848
|
-
: {
|
|
849
|
-
status: "PASSED",
|
|
850
|
-
startTime,
|
|
851
|
-
endTime,
|
|
852
|
-
},
|
|
853
|
-
info: info,
|
|
854
|
-
});
|
|
1141
|
+
await _commandFinally(state, this);
|
|
855
1142
|
}
|
|
856
1143
|
}
|
|
857
1144
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
858
|
-
this._validateSelectors(selectors);
|
|
859
1145
|
if (!values) {
|
|
860
1146
|
throw new Error("values is null");
|
|
861
1147
|
}
|
|
862
|
-
const
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
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
|
+
};
|
|
870
1160
|
try {
|
|
871
|
-
|
|
872
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1161
|
+
await _preCommand(state, this);
|
|
873
1162
|
try {
|
|
874
|
-
await
|
|
875
|
-
await element.selectOption(values);
|
|
1163
|
+
await state.element.selectOption(values);
|
|
876
1164
|
}
|
|
877
1165
|
catch (e) {
|
|
878
1166
|
//await this.closeUnexpectedPopups();
|
|
879
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
880
|
-
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 });
|
|
881
1169
|
}
|
|
882
1170
|
await this.waitForPageLoad();
|
|
883
|
-
return info;
|
|
1171
|
+
return state.info;
|
|
884
1172
|
}
|
|
885
1173
|
catch (e) {
|
|
886
|
-
|
|
887
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
888
|
-
info.screenshotPath = screenshotPath;
|
|
889
|
-
Object.assign(e, { info: info });
|
|
890
|
-
this.logger.info("click failed, will try next selector");
|
|
891
|
-
error = e;
|
|
892
|
-
throw e;
|
|
1174
|
+
await _commandError(state, e, this);
|
|
893
1175
|
}
|
|
894
1176
|
finally {
|
|
895
|
-
|
|
896
|
-
this._reportToWorld(world, {
|
|
897
|
-
element_name: selectors.element_name,
|
|
898
|
-
type: Types.SELECT,
|
|
899
|
-
text: `Select option: ${values}`,
|
|
900
|
-
value: values.toString(),
|
|
901
|
-
screenshotId,
|
|
902
|
-
result: error
|
|
903
|
-
? {
|
|
904
|
-
status: "FAILED",
|
|
905
|
-
startTime,
|
|
906
|
-
endTime,
|
|
907
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
908
|
-
}
|
|
909
|
-
: {
|
|
910
|
-
status: "PASSED",
|
|
911
|
-
startTime,
|
|
912
|
-
endTime,
|
|
913
|
-
},
|
|
914
|
-
info: info,
|
|
915
|
-
});
|
|
1177
|
+
await _commandFinally(state, this);
|
|
916
1178
|
}
|
|
917
1179
|
}
|
|
918
1180
|
async type(_value, _params = null, options = {}, world = null) {
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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
|
+
};
|
|
928
1195
|
try {
|
|
929
|
-
|
|
930
|
-
const valueSegment =
|
|
1196
|
+
await _preCommand(state, this);
|
|
1197
|
+
const valueSegment = state.value.split("&&");
|
|
931
1198
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
932
1199
|
if (i > 0) {
|
|
933
1200
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -947,134 +1214,77 @@ class StableBrowser {
|
|
|
947
1214
|
await this.page.keyboard.type(value);
|
|
948
1215
|
}
|
|
949
1216
|
}
|
|
950
|
-
return info;
|
|
1217
|
+
return state.info;
|
|
951
1218
|
}
|
|
952
1219
|
catch (e) {
|
|
953
|
-
|
|
954
|
-
this.logger.error("type failed " + info.log);
|
|
955
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
956
|
-
info.screenshotPath = screenshotPath;
|
|
957
|
-
Object.assign(e, { info: info });
|
|
958
|
-
error = e;
|
|
959
|
-
throw e;
|
|
1220
|
+
await _commandError(state, e, this);
|
|
960
1221
|
}
|
|
961
1222
|
finally {
|
|
962
|
-
|
|
963
|
-
this._reportToWorld(world, {
|
|
964
|
-
type: Types.TYPE_PRESS,
|
|
965
|
-
screenshotId,
|
|
966
|
-
value: _value,
|
|
967
|
-
text: `type value: ${_value}`,
|
|
968
|
-
result: error
|
|
969
|
-
? {
|
|
970
|
-
status: "FAILED",
|
|
971
|
-
startTime,
|
|
972
|
-
endTime,
|
|
973
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
974
|
-
}
|
|
975
|
-
: {
|
|
976
|
-
status: "PASSED",
|
|
977
|
-
startTime,
|
|
978
|
-
endTime,
|
|
979
|
-
},
|
|
980
|
-
info: info,
|
|
981
|
-
});
|
|
1223
|
+
await _commandFinally(state, this);
|
|
982
1224
|
}
|
|
983
1225
|
}
|
|
984
1226
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
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
|
+
};
|
|
997
1238
|
try {
|
|
998
|
-
|
|
999
|
-
let
|
|
1000
|
-
await this.scrollIfNeeded(element, info);
|
|
1001
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1002
|
-
await this._highlightElements(element);
|
|
1239
|
+
await _preCommand(state, this);
|
|
1240
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1003
1241
|
try {
|
|
1004
|
-
await element.evaluateHandle((el, value) => {
|
|
1242
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1005
1243
|
el.value = value;
|
|
1006
1244
|
}, value);
|
|
1007
1245
|
}
|
|
1008
1246
|
catch (error) {
|
|
1009
1247
|
this.logger.error("setInputValue failed, will try again");
|
|
1010
|
-
|
|
1011
|
-
info.
|
|
1012
|
-
|
|
1013
|
-
await element.evaluateHandle((el, value) => {
|
|
1248
|
+
await _screenshot(state, this);
|
|
1249
|
+
Object.assign(error, { info: state.info });
|
|
1250
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1014
1251
|
el.value = value;
|
|
1015
1252
|
});
|
|
1016
1253
|
}
|
|
1017
1254
|
}
|
|
1018
1255
|
catch (e) {
|
|
1019
|
-
|
|
1020
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1021
|
-
info.screenshotPath = screenshotPath;
|
|
1022
|
-
Object.assign(e, { info: info });
|
|
1023
|
-
error = e;
|
|
1024
|
-
throw e;
|
|
1256
|
+
await _commandError(state, e, this);
|
|
1025
1257
|
}
|
|
1026
1258
|
finally {
|
|
1027
|
-
|
|
1028
|
-
this._reportToWorld(world, {
|
|
1029
|
-
element_name: selectors.element_name,
|
|
1030
|
-
type: Types.SET_INPUT,
|
|
1031
|
-
text: `Set input value`,
|
|
1032
|
-
value: value,
|
|
1033
|
-
screenshotId,
|
|
1034
|
-
result: error
|
|
1035
|
-
? {
|
|
1036
|
-
status: "FAILED",
|
|
1037
|
-
startTime,
|
|
1038
|
-
endTime,
|
|
1039
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1040
|
-
}
|
|
1041
|
-
: {
|
|
1042
|
-
status: "PASSED",
|
|
1043
|
-
startTime,
|
|
1044
|
-
endTime,
|
|
1045
|
-
},
|
|
1046
|
-
info: info,
|
|
1047
|
-
});
|
|
1259
|
+
await _commandFinally(state, this);
|
|
1048
1260
|
}
|
|
1049
1261
|
}
|
|
1050
1262
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
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
|
+
};
|
|
1061
1276
|
try {
|
|
1062
|
-
|
|
1063
|
-
let element = await this._locate(selectors, info, _params);
|
|
1064
|
-
//insert red border around the element
|
|
1065
|
-
await this.scrollIfNeeded(element, info);
|
|
1066
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1067
|
-
await this._highlightElements(element);
|
|
1277
|
+
await _preCommand(state, this);
|
|
1068
1278
|
try {
|
|
1069
|
-
await element
|
|
1279
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1070
1280
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1071
1281
|
if (format) {
|
|
1072
|
-
value = dayjs(value).format(format);
|
|
1073
|
-
await element.fill(value);
|
|
1282
|
+
state.value = dayjs(state.value).format(format);
|
|
1283
|
+
await state.element.fill(state.value);
|
|
1074
1284
|
}
|
|
1075
1285
|
else {
|
|
1076
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1077
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1286
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1287
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1078
1288
|
el.value = ""; // clear input
|
|
1079
1289
|
el.value = dateTimeValue;
|
|
1080
1290
|
}, dateTimeValue);
|
|
@@ -1087,20 +1297,19 @@ class StableBrowser {
|
|
|
1087
1297
|
}
|
|
1088
1298
|
catch (err) {
|
|
1089
1299
|
//await this.closeUnexpectedPopups();
|
|
1090
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1300
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1091
1301
|
this.logger.info("Trying again");
|
|
1092
|
-
|
|
1093
|
-
info.
|
|
1094
|
-
Object.assign(err, { info: info });
|
|
1302
|
+
await _screenshot(state, this);
|
|
1303
|
+
Object.assign(err, { info: state.info });
|
|
1095
1304
|
await element.click();
|
|
1096
1305
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1097
1306
|
if (format) {
|
|
1098
|
-
value = dayjs(value).format(format);
|
|
1099
|
-
await element.fill(value);
|
|
1307
|
+
state.value = dayjs(state.value).format(format);
|
|
1308
|
+
await state.element.fill(state.value);
|
|
1100
1309
|
}
|
|
1101
1310
|
else {
|
|
1102
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1103
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1311
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1312
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1104
1313
|
el.value = ""; // clear input
|
|
1105
1314
|
el.value = dateTimeValue;
|
|
1106
1315
|
}, dateTimeValue);
|
|
@@ -1113,84 +1322,63 @@ class StableBrowser {
|
|
|
1113
1322
|
}
|
|
1114
1323
|
}
|
|
1115
1324
|
catch (e) {
|
|
1116
|
-
|
|
1117
|
-
throw e;
|
|
1325
|
+
await _commandError(state, e, this);
|
|
1118
1326
|
}
|
|
1119
1327
|
finally {
|
|
1120
|
-
|
|
1121
|
-
this._reportToWorld(world, {
|
|
1122
|
-
element_name: selectors.element_name,
|
|
1123
|
-
type: Types.SET_DATE_TIME,
|
|
1124
|
-
screenshotId,
|
|
1125
|
-
value: value,
|
|
1126
|
-
text: `setDateTime input with value: ${value}`,
|
|
1127
|
-
result: error
|
|
1128
|
-
? {
|
|
1129
|
-
status: "FAILED",
|
|
1130
|
-
startTime,
|
|
1131
|
-
endTime,
|
|
1132
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1133
|
-
}
|
|
1134
|
-
: {
|
|
1135
|
-
status: "PASSED",
|
|
1136
|
-
startTime,
|
|
1137
|
-
endTime,
|
|
1138
|
-
},
|
|
1139
|
-
info: info,
|
|
1140
|
-
});
|
|
1328
|
+
await _commandFinally(state, this);
|
|
1141
1329
|
}
|
|
1142
1330
|
}
|
|
1143
1331
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1144
|
-
|
|
1145
|
-
const startTime = Date.now();
|
|
1146
|
-
let error = null;
|
|
1147
|
-
let screenshotId = null;
|
|
1148
|
-
let screenshotPath = null;
|
|
1149
|
-
const info = {};
|
|
1150
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1151
|
-
info.operation = "clickType";
|
|
1152
|
-
info.selectors = selectors;
|
|
1332
|
+
_value = unEscapeString(_value);
|
|
1153
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
|
+
}
|
|
1154
1350
|
if (newValue !== _value) {
|
|
1155
1351
|
//this.logger.info(_value + "=" + newValue);
|
|
1156
1352
|
_value = newValue;
|
|
1157
1353
|
}
|
|
1158
|
-
info.value = _value;
|
|
1159
1354
|
try {
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1164
|
-
await this._highlightElements(element);
|
|
1165
|
-
if (options === null || options === undefined || !options.press) {
|
|
1355
|
+
await _preCommand(state, this);
|
|
1356
|
+
state.info.value = _value;
|
|
1357
|
+
if (!options.press) {
|
|
1166
1358
|
try {
|
|
1167
|
-
let currentValue = await element.inputValue();
|
|
1359
|
+
let currentValue = await state.element.inputValue();
|
|
1168
1360
|
if (currentValue) {
|
|
1169
|
-
await element.fill("");
|
|
1361
|
+
await state.element.fill("");
|
|
1170
1362
|
}
|
|
1171
1363
|
}
|
|
1172
1364
|
catch (e) {
|
|
1173
1365
|
this.logger.info("unable to clear input value");
|
|
1174
1366
|
}
|
|
1175
1367
|
}
|
|
1176
|
-
if (options
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
}
|
|
1180
|
-
catch (e) {
|
|
1181
|
-
await element.dispatchEvent("click");
|
|
1182
|
-
}
|
|
1368
|
+
if (options.press) {
|
|
1369
|
+
options.timeout = 5000;
|
|
1370
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1183
1371
|
}
|
|
1184
1372
|
else {
|
|
1185
1373
|
try {
|
|
1186
|
-
await element.focus();
|
|
1374
|
+
await state.element.focus();
|
|
1187
1375
|
}
|
|
1188
1376
|
catch (e) {
|
|
1189
|
-
await element.dispatchEvent("focus");
|
|
1377
|
+
await state.element.dispatchEvent("focus");
|
|
1190
1378
|
}
|
|
1191
1379
|
}
|
|
1192
1380
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1193
|
-
const valueSegment =
|
|
1381
|
+
const valueSegment = state.value.split("&&");
|
|
1194
1382
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1195
1383
|
if (i > 0) {
|
|
1196
1384
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1210,13 +1398,19 @@ class StableBrowser {
|
|
|
1210
1398
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1211
1399
|
}
|
|
1212
1400
|
}
|
|
1401
|
+
await _screenshot(state, this);
|
|
1213
1402
|
if (enter === true) {
|
|
1214
1403
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1215
1404
|
await this.page.keyboard.press("Enter");
|
|
1216
1405
|
await this.waitForPageLoad();
|
|
1217
1406
|
}
|
|
1218
1407
|
else if (enter === false) {
|
|
1219
|
-
|
|
1408
|
+
try {
|
|
1409
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1410
|
+
}
|
|
1411
|
+
catch (e) {
|
|
1412
|
+
// ignore
|
|
1413
|
+
}
|
|
1220
1414
|
//await this.page.keyboard.press("Tab");
|
|
1221
1415
|
}
|
|
1222
1416
|
else {
|
|
@@ -1225,111 +1419,60 @@ class StableBrowser {
|
|
|
1225
1419
|
await this.waitForPageLoad();
|
|
1226
1420
|
}
|
|
1227
1421
|
}
|
|
1228
|
-
return info;
|
|
1422
|
+
return state.info;
|
|
1229
1423
|
}
|
|
1230
1424
|
catch (e) {
|
|
1231
|
-
|
|
1232
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1233
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1234
|
-
info.screenshotPath = screenshotPath;
|
|
1235
|
-
Object.assign(e, { info: info });
|
|
1236
|
-
error = e;
|
|
1237
|
-
throw e;
|
|
1425
|
+
await _commandError(state, e, this);
|
|
1238
1426
|
}
|
|
1239
1427
|
finally {
|
|
1240
|
-
|
|
1241
|
-
this._reportToWorld(world, {
|
|
1242
|
-
element_name: selectors.element_name,
|
|
1243
|
-
type: Types.FILL,
|
|
1244
|
-
screenshotId,
|
|
1245
|
-
value: _value,
|
|
1246
|
-
text: `clickType input with value: ${_value}`,
|
|
1247
|
-
result: error
|
|
1248
|
-
? {
|
|
1249
|
-
status: "FAILED",
|
|
1250
|
-
startTime,
|
|
1251
|
-
endTime,
|
|
1252
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1253
|
-
}
|
|
1254
|
-
: {
|
|
1255
|
-
status: "PASSED",
|
|
1256
|
-
startTime,
|
|
1257
|
-
endTime,
|
|
1258
|
-
},
|
|
1259
|
-
info: info,
|
|
1260
|
-
});
|
|
1428
|
+
await _commandFinally(state, this);
|
|
1261
1429
|
}
|
|
1262
1430
|
}
|
|
1263
1431
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
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
|
+
};
|
|
1274
1443
|
try {
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
await
|
|
1278
|
-
await element.fill(value);
|
|
1279
|
-
await element.dispatchEvent("change");
|
|
1444
|
+
await _preCommand(state, this);
|
|
1445
|
+
await state.element.fill(value);
|
|
1446
|
+
await state.element.dispatchEvent("change");
|
|
1280
1447
|
if (enter) {
|
|
1281
1448
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1282
1449
|
await this.page.keyboard.press("Enter");
|
|
1283
1450
|
}
|
|
1284
1451
|
await this.waitForPageLoad();
|
|
1285
|
-
return info;
|
|
1452
|
+
return state.info;
|
|
1286
1453
|
}
|
|
1287
1454
|
catch (e) {
|
|
1288
|
-
|
|
1289
|
-
this.logger.error("fill failed " + info.log);
|
|
1290
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1291
|
-
info.screenshotPath = screenshotPath;
|
|
1292
|
-
Object.assign(e, { info: info });
|
|
1293
|
-
error = e;
|
|
1294
|
-
throw e;
|
|
1455
|
+
await _commandError(state, e, this);
|
|
1295
1456
|
}
|
|
1296
1457
|
finally {
|
|
1297
|
-
|
|
1298
|
-
this._reportToWorld(world, {
|
|
1299
|
-
element_name: selectors.element_name,
|
|
1300
|
-
type: Types.FILL,
|
|
1301
|
-
screenshotId,
|
|
1302
|
-
value,
|
|
1303
|
-
text: `Fill input with value: ${value}`,
|
|
1304
|
-
result: error
|
|
1305
|
-
? {
|
|
1306
|
-
status: "FAILED",
|
|
1307
|
-
startTime,
|
|
1308
|
-
endTime,
|
|
1309
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1310
|
-
}
|
|
1311
|
-
: {
|
|
1312
|
-
status: "PASSED",
|
|
1313
|
-
startTime,
|
|
1314
|
-
endTime,
|
|
1315
|
-
},
|
|
1316
|
-
info: info,
|
|
1317
|
-
});
|
|
1458
|
+
await _commandFinally(state, this);
|
|
1318
1459
|
}
|
|
1319
1460
|
}
|
|
1320
1461
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1321
1462
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1322
1463
|
}
|
|
1323
1464
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1324
|
-
this.
|
|
1465
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1466
|
+
_validateSelectors(selectors);
|
|
1325
1467
|
let screenshotId = null;
|
|
1326
1468
|
let screenshotPath = null;
|
|
1327
1469
|
if (!info.log) {
|
|
1328
1470
|
info.log = "";
|
|
1471
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1329
1472
|
}
|
|
1330
1473
|
info.operation = "getText";
|
|
1331
1474
|
info.selectors = selectors;
|
|
1332
|
-
let element = await this._locate(selectors, info, _params);
|
|
1475
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1333
1476
|
if (climb > 0) {
|
|
1334
1477
|
const climbArray = [];
|
|
1335
1478
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1348,6 +1491,18 @@ class StableBrowser {
|
|
|
1348
1491
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1349
1492
|
try {
|
|
1350
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
|
+
// }
|
|
1351
1506
|
const elementText = await element.innerText();
|
|
1352
1507
|
return {
|
|
1353
1508
|
text: elementText,
|
|
@@ -1359,192 +1514,149 @@ class StableBrowser {
|
|
|
1359
1514
|
}
|
|
1360
1515
|
catch (e) {
|
|
1361
1516
|
//await this.closeUnexpectedPopups();
|
|
1362
|
-
this.logger.info("no innerText will use textContent");
|
|
1517
|
+
this.logger.info("no innerText, will use textContent");
|
|
1363
1518
|
const elementText = await element.textContent();
|
|
1364
1519
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1365
1520
|
}
|
|
1366
1521
|
}
|
|
1367
1522
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1368
|
-
var _a;
|
|
1369
|
-
this._validateSelectors(selectors);
|
|
1370
1523
|
if (!pattern) {
|
|
1371
1524
|
throw new Error("pattern is null");
|
|
1372
1525
|
}
|
|
1373
1526
|
if (!text) {
|
|
1374
1527
|
throw new Error("text is null");
|
|
1375
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
|
+
};
|
|
1376
1546
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1377
1547
|
if (newValue !== text) {
|
|
1378
1548
|
this.logger.info(text + "=" + newValue);
|
|
1379
1549
|
text = newValue;
|
|
1380
1550
|
}
|
|
1381
|
-
const startTime = Date.now();
|
|
1382
|
-
let error = null;
|
|
1383
|
-
let screenshotId = null;
|
|
1384
|
-
let screenshotPath = null;
|
|
1385
|
-
const info = {};
|
|
1386
|
-
info.log =
|
|
1387
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1388
|
-
info.operation = "containsPattern";
|
|
1389
|
-
info.selectors = selectors;
|
|
1390
|
-
info.value = text;
|
|
1391
|
-
info.pattern = pattern;
|
|
1392
1551
|
let foundObj = null;
|
|
1393
1552
|
try {
|
|
1394
|
-
|
|
1553
|
+
await _preCommand(state, this);
|
|
1554
|
+
state.info.pattern = pattern;
|
|
1555
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1395
1556
|
if (foundObj && foundObj.element) {
|
|
1396
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1557
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1397
1558
|
}
|
|
1398
|
-
|
|
1559
|
+
await _screenshot(state, this);
|
|
1399
1560
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1400
1561
|
pattern = pattern.replace("{text}", escapedText);
|
|
1401
1562
|
let regex = new RegExp(pattern, "im");
|
|
1402
|
-
if (!regex.test(foundObj
|
|
1403
|
-
info.foundText = foundObj
|
|
1563
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1564
|
+
state.info.foundText = foundObj?.text;
|
|
1404
1565
|
throw new Error("element doesn't contain text " + text);
|
|
1405
1566
|
}
|
|
1406
|
-
return info;
|
|
1567
|
+
return state.info;
|
|
1407
1568
|
}
|
|
1408
1569
|
catch (e) {
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1412
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1413
|
-
info.screenshotPath = screenshotPath;
|
|
1414
|
-
Object.assign(e, { info: info });
|
|
1415
|
-
error = e;
|
|
1416
|
-
throw e;
|
|
1570
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1571
|
+
await _commandError(state, e, this);
|
|
1417
1572
|
}
|
|
1418
1573
|
finally {
|
|
1419
|
-
|
|
1420
|
-
this._reportToWorld(world, {
|
|
1421
|
-
element_name: selectors.element_name,
|
|
1422
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1423
|
-
value: pattern,
|
|
1424
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1425
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1426
|
-
result: error
|
|
1427
|
-
? {
|
|
1428
|
-
status: "FAILED",
|
|
1429
|
-
startTime,
|
|
1430
|
-
endTime,
|
|
1431
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1432
|
-
}
|
|
1433
|
-
: {
|
|
1434
|
-
status: "PASSED",
|
|
1435
|
-
startTime,
|
|
1436
|
-
endTime,
|
|
1437
|
-
},
|
|
1438
|
-
info: info,
|
|
1439
|
-
});
|
|
1574
|
+
await _commandFinally(state, this);
|
|
1440
1575
|
}
|
|
1441
1576
|
}
|
|
1442
1577
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1443
|
-
|
|
1444
|
-
|
|
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
|
+
};
|
|
1445
1595
|
if (!text) {
|
|
1446
1596
|
throw new Error("text is null");
|
|
1447
1597
|
}
|
|
1448
|
-
|
|
1449
|
-
let error = null;
|
|
1450
|
-
let screenshotId = null;
|
|
1451
|
-
let screenshotPath = null;
|
|
1452
|
-
const info = {};
|
|
1453
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1454
|
-
info.operation = "containsText";
|
|
1455
|
-
info.selectors = selectors;
|
|
1598
|
+
text = unEscapeString(text);
|
|
1456
1599
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1457
1600
|
if (newValue !== text) {
|
|
1458
1601
|
this.logger.info(text + "=" + newValue);
|
|
1459
1602
|
text = newValue;
|
|
1460
1603
|
}
|
|
1461
|
-
info.value = text;
|
|
1462
1604
|
let foundObj = null;
|
|
1463
1605
|
try {
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
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
|
+
}
|
|
1476
1623
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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;
|
|
1485
1634
|
}
|
|
1486
1635
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
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
|
|
1493
1641
|
}
|
|
1494
|
-
|
|
1642
|
+
state.info.foundText = foundObj?.text;
|
|
1643
|
+
state.info.value = foundObj?.value;
|
|
1644
|
+
throw new Error("element doesn't contain text " + text);
|
|
1495
1645
|
}
|
|
1496
1646
|
catch (e) {
|
|
1497
|
-
|
|
1498
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1499
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1500
|
-
info.screenshotPath = screenshotPath;
|
|
1501
|
-
Object.assign(e, { info: info });
|
|
1502
|
-
error = e;
|
|
1647
|
+
await _commandError(state, e, this);
|
|
1503
1648
|
throw e;
|
|
1504
1649
|
}
|
|
1505
1650
|
finally {
|
|
1506
|
-
|
|
1507
|
-
this._reportToWorld(world, {
|
|
1508
|
-
element_name: selectors.element_name,
|
|
1509
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1510
|
-
text: `Verify element contains text: ${text}`,
|
|
1511
|
-
value: text,
|
|
1512
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1513
|
-
result: error
|
|
1514
|
-
? {
|
|
1515
|
-
status: "FAILED",
|
|
1516
|
-
startTime,
|
|
1517
|
-
endTime,
|
|
1518
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1519
|
-
}
|
|
1520
|
-
: {
|
|
1521
|
-
status: "PASSED",
|
|
1522
|
-
startTime,
|
|
1523
|
-
endTime,
|
|
1524
|
-
},
|
|
1525
|
-
info: info,
|
|
1526
|
-
});
|
|
1651
|
+
await _commandFinally(state, this);
|
|
1527
1652
|
}
|
|
1528
1653
|
}
|
|
1529
|
-
_getDataFile(world = null) {
|
|
1530
|
-
let dataFile = null;
|
|
1531
|
-
if (world && world.reportFolder) {
|
|
1532
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1533
|
-
}
|
|
1534
|
-
else if (this.reportFolder) {
|
|
1535
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1536
|
-
}
|
|
1537
|
-
else if (this.context && this.context.reportFolder) {
|
|
1538
|
-
dataFile = path.join(this.context.reportFolder, "data.json");
|
|
1539
|
-
}
|
|
1540
|
-
else {
|
|
1541
|
-
dataFile = "data.json";
|
|
1542
|
-
}
|
|
1543
|
-
return dataFile;
|
|
1544
|
-
}
|
|
1545
1654
|
async waitForUserInput(message, world = null) {
|
|
1546
1655
|
if (!message) {
|
|
1547
|
-
message = "Press any key to continue";
|
|
1656
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1657
|
+
}
|
|
1658
|
+
else {
|
|
1659
|
+
message = "# Wait for user input. " + message;
|
|
1548
1660
|
}
|
|
1549
1661
|
message += "\n";
|
|
1550
1662
|
const value = await new Promise((resolve) => {
|
|
@@ -1567,13 +1679,22 @@ class StableBrowser {
|
|
|
1567
1679
|
return;
|
|
1568
1680
|
}
|
|
1569
1681
|
// if data file exists, load it
|
|
1570
|
-
const dataFile =
|
|
1682
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1571
1683
|
let data = this.getTestData(world);
|
|
1572
1684
|
// merge the testData with the existing data
|
|
1573
1685
|
Object.assign(data, testData);
|
|
1574
1686
|
// save the data to the file
|
|
1575
1687
|
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
1576
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
|
+
}
|
|
1577
1698
|
_getDataFilePath(fileName) {
|
|
1578
1699
|
let dataFile = path.join(this.project_path, "data", fileName);
|
|
1579
1700
|
if (fs.existsSync(dataFile)) {
|
|
@@ -1670,7 +1791,7 @@ class StableBrowser {
|
|
|
1670
1791
|
}
|
|
1671
1792
|
}
|
|
1672
1793
|
getTestData(world = null) {
|
|
1673
|
-
const dataFile =
|
|
1794
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1674
1795
|
let data = {};
|
|
1675
1796
|
if (fs.existsSync(dataFile)) {
|
|
1676
1797
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1702,11 +1823,9 @@ class StableBrowser {
|
|
|
1702
1823
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1703
1824
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1704
1825
|
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
}
|
|
1709
|
-
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");
|
|
1710
1829
|
try {
|
|
1711
1830
|
await this.takeScreenshot(screenshotPath);
|
|
1712
1831
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1716,15 +1835,15 @@ class StableBrowser {
|
|
|
1716
1835
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1717
1836
|
// }
|
|
1718
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
|
+
}
|
|
1719
1843
|
}
|
|
1720
1844
|
catch (e) {
|
|
1721
1845
|
this.logger.info("unable to take screenshot, ignored");
|
|
1722
1846
|
}
|
|
1723
|
-
result.screenshotId = nextIndex;
|
|
1724
|
-
result.screenshotPath = screenshotPath;
|
|
1725
|
-
if (info && info.box) {
|
|
1726
|
-
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1727
|
-
}
|
|
1728
1847
|
}
|
|
1729
1848
|
else if (options && options.screenshot) {
|
|
1730
1849
|
result.screenshotPath = options.screenshotPath;
|
|
@@ -1749,7 +1868,6 @@ class StableBrowser {
|
|
|
1749
1868
|
}
|
|
1750
1869
|
async takeScreenshot(screenshotPath) {
|
|
1751
1870
|
const playContext = this.context.playContext;
|
|
1752
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1753
1871
|
// Using CDP to capture the screenshot
|
|
1754
1872
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1755
1873
|
document.body.scrollWidth,
|
|
@@ -1759,164 +1877,198 @@ class StableBrowser {
|
|
|
1759
1877
|
document.body.clientWidth,
|
|
1760
1878
|
document.documentElement.clientWidth,
|
|
1761
1879
|
])));
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
screenshotBuffer =
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
|
|
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;
|
|
1797
1928
|
}
|
|
1798
1929
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
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
|
+
};
|
|
1804
1940
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1805
|
-
const info = {};
|
|
1806
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1807
|
-
info.operation = "verify";
|
|
1808
|
-
info.selectors = selectors;
|
|
1809
1941
|
try {
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
}
|
|
1814
|
-
await this._highlightElements(element);
|
|
1815
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1816
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1817
|
-
return info;
|
|
1942
|
+
await _preCommand(state, this);
|
|
1943
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1944
|
+
return state.info;
|
|
1818
1945
|
}
|
|
1819
1946
|
catch (e) {
|
|
1820
|
-
|
|
1821
|
-
this.logger.error("verify failed " + info.log);
|
|
1822
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1823
|
-
info.screenshotPath = screenshotPath;
|
|
1824
|
-
Object.assign(e, { info: info });
|
|
1825
|
-
error = e;
|
|
1826
|
-
throw e;
|
|
1947
|
+
await _commandError(state, e, this);
|
|
1827
1948
|
}
|
|
1828
1949
|
finally {
|
|
1829
|
-
|
|
1830
|
-
this._reportToWorld(world, {
|
|
1831
|
-
element_name: selectors.element_name,
|
|
1832
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1833
|
-
text: "Verify element exists in page",
|
|
1834
|
-
screenshotId,
|
|
1835
|
-
result: error
|
|
1836
|
-
? {
|
|
1837
|
-
status: "FAILED",
|
|
1838
|
-
startTime,
|
|
1839
|
-
endTime,
|
|
1840
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1841
|
-
}
|
|
1842
|
-
: {
|
|
1843
|
-
status: "PASSED",
|
|
1844
|
-
startTime,
|
|
1845
|
-
endTime,
|
|
1846
|
-
},
|
|
1847
|
-
info: info,
|
|
1848
|
-
});
|
|
1950
|
+
await _commandFinally(state, this);
|
|
1849
1951
|
}
|
|
1850
1952
|
}
|
|
1851
1953
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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
|
+
};
|
|
1857
1968
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1858
|
-
const info = {};
|
|
1859
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1860
|
-
info.operation = "extract";
|
|
1861
|
-
info.selectors = selectors;
|
|
1862
1969
|
try {
|
|
1863
|
-
|
|
1864
|
-
await this._highlightElements(element);
|
|
1865
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1970
|
+
await _preCommand(state, this);
|
|
1866
1971
|
switch (attribute) {
|
|
1867
1972
|
case "inner_text":
|
|
1868
|
-
|
|
1973
|
+
state.value = await state.element.innerText();
|
|
1869
1974
|
break;
|
|
1870
1975
|
case "href":
|
|
1871
|
-
|
|
1976
|
+
state.value = await state.element.getAttribute("href");
|
|
1872
1977
|
break;
|
|
1873
1978
|
case "value":
|
|
1874
|
-
|
|
1979
|
+
state.value = await state.element.inputValue();
|
|
1980
|
+
break;
|
|
1981
|
+
case "text":
|
|
1982
|
+
state.value = await state.element.textContent();
|
|
1875
1983
|
break;
|
|
1876
1984
|
default:
|
|
1877
|
-
|
|
1985
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1878
1986
|
break;
|
|
1879
1987
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
this.logger.info("set test data: " + variable + "=" + info.value);
|
|
1886
|
-
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;
|
|
1887
1993
|
}
|
|
1888
1994
|
catch (e) {
|
|
1889
|
-
|
|
1890
|
-
this.logger.error("extract failed " + info.log);
|
|
1891
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1892
|
-
info.screenshotPath = screenshotPath;
|
|
1893
|
-
Object.assign(e, { info: info });
|
|
1894
|
-
error = e;
|
|
1895
|
-
throw e;
|
|
1995
|
+
await _commandError(state, e, this);
|
|
1896
1996
|
}
|
|
1897
1997
|
finally {
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
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);
|
|
1920
2072
|
}
|
|
1921
2073
|
}
|
|
1922
2074
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1937,7 +2089,7 @@ class StableBrowser {
|
|
|
1937
2089
|
if (options && options.timeout) {
|
|
1938
2090
|
timeout = options.timeout;
|
|
1939
2091
|
}
|
|
1940
|
-
const serviceUrl =
|
|
2092
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1941
2093
|
const request = {
|
|
1942
2094
|
method: "POST",
|
|
1943
2095
|
url: serviceUrl,
|
|
@@ -1993,7 +2145,8 @@ class StableBrowser {
|
|
|
1993
2145
|
catch (e) {
|
|
1994
2146
|
errorCount++;
|
|
1995
2147
|
if (errorCount > 3) {
|
|
1996
|
-
throw e;
|
|
2148
|
+
// throw e;
|
|
2149
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1997
2150
|
}
|
|
1998
2151
|
// ignore
|
|
1999
2152
|
}
|
|
@@ -2007,27 +2160,32 @@ class StableBrowser {
|
|
|
2007
2160
|
async _highlightElements(scope, css) {
|
|
2008
2161
|
try {
|
|
2009
2162
|
if (!scope) {
|
|
2163
|
+
// console.log(`Scope is not defined`);
|
|
2010
2164
|
return;
|
|
2011
2165
|
}
|
|
2012
2166
|
if (!css) {
|
|
2013
2167
|
scope
|
|
2014
2168
|
.evaluate((node) => {
|
|
2015
2169
|
if (node && node.style) {
|
|
2016
|
-
let
|
|
2017
|
-
|
|
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}`);
|
|
2018
2175
|
if (window) {
|
|
2019
2176
|
window.addEventListener("beforeunload", function (e) {
|
|
2020
|
-
node.style.
|
|
2177
|
+
node.style.outline = originalOutline;
|
|
2021
2178
|
});
|
|
2022
2179
|
}
|
|
2023
2180
|
setTimeout(function () {
|
|
2024
|
-
node.style.
|
|
2181
|
+
node.style.outline = originalOutline;
|
|
2025
2182
|
}, 2000);
|
|
2026
2183
|
}
|
|
2027
2184
|
})
|
|
2028
2185
|
.then(() => { })
|
|
2029
2186
|
.catch((e) => {
|
|
2030
2187
|
// ignore
|
|
2188
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2031
2189
|
});
|
|
2032
2190
|
}
|
|
2033
2191
|
else {
|
|
@@ -2043,17 +2201,18 @@ class StableBrowser {
|
|
|
2043
2201
|
if (!element.style) {
|
|
2044
2202
|
return;
|
|
2045
2203
|
}
|
|
2046
|
-
|
|
2204
|
+
let originalOutline = element.style.outline;
|
|
2205
|
+
element.__previousOutline = originalOutline;
|
|
2047
2206
|
// Set the new border to be red and 2px solid
|
|
2048
|
-
element.style.
|
|
2207
|
+
element.style.outline = "2px solid red";
|
|
2049
2208
|
if (window) {
|
|
2050
2209
|
window.addEventListener("beforeunload", function (e) {
|
|
2051
|
-
element.style.
|
|
2210
|
+
element.style.outline = originalOutline;
|
|
2052
2211
|
});
|
|
2053
2212
|
}
|
|
2054
2213
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2055
2214
|
setTimeout(function () {
|
|
2056
|
-
element.style.
|
|
2215
|
+
element.style.outline = originalOutline;
|
|
2057
2216
|
}, 2000);
|
|
2058
2217
|
}
|
|
2059
2218
|
return;
|
|
@@ -2061,6 +2220,7 @@ class StableBrowser {
|
|
|
2061
2220
|
.then(() => { })
|
|
2062
2221
|
.catch((e) => {
|
|
2063
2222
|
// ignore
|
|
2223
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2064
2224
|
});
|
|
2065
2225
|
}
|
|
2066
2226
|
}
|
|
@@ -2068,6 +2228,54 @@ class StableBrowser {
|
|
|
2068
2228
|
console.debug(error);
|
|
2069
2229
|
}
|
|
2070
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
|
+
// }
|
|
2071
2279
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2072
2280
|
const startTime = Date.now();
|
|
2073
2281
|
let error = null;
|
|
@@ -2104,20 +2312,22 @@ class StableBrowser {
|
|
|
2104
2312
|
info.screenshotPath = screenshotPath;
|
|
2105
2313
|
Object.assign(e, { info: info });
|
|
2106
2314
|
error = e;
|
|
2107
|
-
throw e;
|
|
2315
|
+
// throw e;
|
|
2316
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2108
2317
|
}
|
|
2109
2318
|
finally {
|
|
2110
2319
|
const endTime = Date.now();
|
|
2111
|
-
|
|
2320
|
+
_reportToWorld(world, {
|
|
2112
2321
|
type: Types.VERIFY_PAGE_PATH,
|
|
2113
2322
|
text: "Verify page path",
|
|
2323
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2114
2324
|
screenshotId,
|
|
2115
2325
|
result: error
|
|
2116
2326
|
? {
|
|
2117
2327
|
status: "FAILED",
|
|
2118
2328
|
startTime,
|
|
2119
2329
|
endTime,
|
|
2120
|
-
message: error
|
|
2330
|
+
message: error?.message,
|
|
2121
2331
|
}
|
|
2122
2332
|
: {
|
|
2123
2333
|
status: "PASSED",
|
|
@@ -2128,94 +2338,58 @@ class StableBrowser {
|
|
|
2128
2338
|
});
|
|
2129
2339
|
}
|
|
2130
2340
|
}
|
|
2131
|
-
async
|
|
2341
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2132
2342
|
const startTime = Date.now();
|
|
2133
|
-
const timeout = this._getLoadTimeout(options);
|
|
2134
2343
|
let error = null;
|
|
2135
2344
|
let screenshotId = null;
|
|
2136
2345
|
let screenshotPath = null;
|
|
2137
2346
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2138
2347
|
const info = {};
|
|
2139
|
-
info.log = "***** verify
|
|
2140
|
-
info.operation = "
|
|
2141
|
-
const newValue = await this._replaceWithLocalData(
|
|
2142
|
-
if (newValue !==
|
|
2143
|
-
this.logger.info(
|
|
2144
|
-
|
|
2145
|
-
}
|
|
2146
|
-
info.
|
|
2147
|
-
let dateAlternatives = findDateAlternatives(text);
|
|
2148
|
-
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;
|
|
2149
2356
|
try {
|
|
2150
|
-
|
|
2151
|
-
const
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2156
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
|
|
2157
|
-
result.frame = frames[i];
|
|
2158
|
-
results.push(result);
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
else if (numberAlternatives.number) {
|
|
2162
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2163
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
|
|
2164
|
-
result.frame = frames[i];
|
|
2165
|
-
results.push(result);
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
else {
|
|
2169
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, {});
|
|
2170
|
-
result.frame = frames[i];
|
|
2171
|
-
results.push(result);
|
|
2172
|
-
}
|
|
2173
|
-
}
|
|
2174
|
-
info.results = results;
|
|
2175
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2176
|
-
if (resultWithElementsFound.length === 0) {
|
|
2177
|
-
if (Date.now() - startTime > timeout) {
|
|
2178
|
-
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}`);
|
|
2179
2362
|
}
|
|
2180
2363
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2181
2364
|
continue;
|
|
2182
2365
|
}
|
|
2183
|
-
if (resultWithElementsFound[0].randomToken) {
|
|
2184
|
-
const frame = resultWithElementsFound[0].frame;
|
|
2185
|
-
const dataAttribute = `[data-blinq-id="blinq-id-${resultWithElementsFound[0].randomToken}"]`;
|
|
2186
|
-
await this._highlightElements(frame, dataAttribute);
|
|
2187
|
-
const element = await frame.$(dataAttribute);
|
|
2188
|
-
if (element) {
|
|
2189
|
-
await this.scrollIfNeeded(element, info);
|
|
2190
|
-
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
2366
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2194
2367
|
return info;
|
|
2195
2368
|
}
|
|
2196
|
-
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2197
2369
|
}
|
|
2198
2370
|
catch (e) {
|
|
2199
2371
|
//await this.closeUnexpectedPopups();
|
|
2200
|
-
this.logger.error("verify
|
|
2372
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2201
2373
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2202
2374
|
info.screenshotPath = screenshotPath;
|
|
2203
2375
|
Object.assign(e, { info: info });
|
|
2204
2376
|
error = e;
|
|
2205
|
-
throw e;
|
|
2377
|
+
// throw e;
|
|
2378
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2206
2379
|
}
|
|
2207
2380
|
finally {
|
|
2208
2381
|
const endTime = Date.now();
|
|
2209
|
-
|
|
2210
|
-
type: Types.
|
|
2211
|
-
text: "Verify
|
|
2382
|
+
_reportToWorld(world, {
|
|
2383
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2384
|
+
text: "Verify page title",
|
|
2385
|
+
_text: "Verify the page title contains " + title,
|
|
2212
2386
|
screenshotId,
|
|
2213
2387
|
result: error
|
|
2214
2388
|
? {
|
|
2215
2389
|
status: "FAILED",
|
|
2216
2390
|
startTime,
|
|
2217
2391
|
endTime,
|
|
2218
|
-
message: error
|
|
2392
|
+
message: error?.message,
|
|
2219
2393
|
}
|
|
2220
2394
|
: {
|
|
2221
2395
|
status: "PASSED",
|
|
@@ -2226,15 +2400,317 @@ class StableBrowser {
|
|
|
2226
2400
|
});
|
|
2227
2401
|
}
|
|
2228
2402
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
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);
|
|
2233
2689
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
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
|
+
}
|
|
2236
2710
|
}
|
|
2237
|
-
|
|
2711
|
+
// state.info.results = results;
|
|
2712
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2713
|
+
return resultWithElementsFound;
|
|
2238
2714
|
}
|
|
2239
2715
|
async visualVerification(text, options = {}, world = null) {
|
|
2240
2716
|
const startTime = Date.now();
|
|
@@ -2250,14 +2726,17 @@ class StableBrowser {
|
|
|
2250
2726
|
throw new Error("TOKEN is not set");
|
|
2251
2727
|
}
|
|
2252
2728
|
try {
|
|
2253
|
-
let serviceUrl =
|
|
2729
|
+
let serviceUrl = _getServerUrl();
|
|
2254
2730
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2255
2731
|
info.screenshotPath = screenshotPath;
|
|
2256
2732
|
const screenshot = await this.takeScreenshot();
|
|
2257
|
-
|
|
2258
|
-
method: "
|
|
2733
|
+
let request = {
|
|
2734
|
+
method: "post",
|
|
2735
|
+
maxBodyLength: Infinity,
|
|
2259
2736
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2260
2737
|
headers: {
|
|
2738
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2739
|
+
"x-source": "aaa",
|
|
2261
2740
|
"Content-Type": "application/json",
|
|
2262
2741
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2263
2742
|
},
|
|
@@ -2266,7 +2745,7 @@ class StableBrowser {
|
|
|
2266
2745
|
screenshot: screenshot,
|
|
2267
2746
|
}),
|
|
2268
2747
|
};
|
|
2269
|
-
|
|
2748
|
+
const result = await axios.request(request);
|
|
2270
2749
|
if (result.data.status !== true) {
|
|
2271
2750
|
throw new Error("Visual validation failed");
|
|
2272
2751
|
}
|
|
@@ -2286,20 +2765,22 @@ class StableBrowser {
|
|
|
2286
2765
|
info.screenshotPath = screenshotPath;
|
|
2287
2766
|
Object.assign(e, { info: info });
|
|
2288
2767
|
error = e;
|
|
2289
|
-
throw e;
|
|
2768
|
+
// throw e;
|
|
2769
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2290
2770
|
}
|
|
2291
2771
|
finally {
|
|
2292
2772
|
const endTime = Date.now();
|
|
2293
|
-
|
|
2773
|
+
_reportToWorld(world, {
|
|
2294
2774
|
type: Types.VERIFY_VISUAL,
|
|
2295
2775
|
text: "Visual verification",
|
|
2776
|
+
_text: "Visual verification of " + text,
|
|
2296
2777
|
screenshotId,
|
|
2297
2778
|
result: error
|
|
2298
2779
|
? {
|
|
2299
2780
|
status: "FAILED",
|
|
2300
2781
|
startTime,
|
|
2301
2782
|
endTime,
|
|
2302
|
-
message: error
|
|
2783
|
+
message: error?.message,
|
|
2303
2784
|
}
|
|
2304
2785
|
: {
|
|
2305
2786
|
status: "PASSED",
|
|
@@ -2331,13 +2812,14 @@ class StableBrowser {
|
|
|
2331
2812
|
this.logger.info("Table data verified");
|
|
2332
2813
|
}
|
|
2333
2814
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2334
|
-
|
|
2815
|
+
_validateSelectors(selectors);
|
|
2335
2816
|
const startTime = Date.now();
|
|
2336
2817
|
let error = null;
|
|
2337
2818
|
let screenshotId = null;
|
|
2338
2819
|
let screenshotPath = null;
|
|
2339
2820
|
const info = {};
|
|
2340
2821
|
info.log = "";
|
|
2822
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2341
2823
|
info.operation = "getTableData";
|
|
2342
2824
|
info.selectors = selectors;
|
|
2343
2825
|
try {
|
|
@@ -2353,11 +2835,12 @@ class StableBrowser {
|
|
|
2353
2835
|
info.screenshotPath = screenshotPath;
|
|
2354
2836
|
Object.assign(e, { info: info });
|
|
2355
2837
|
error = e;
|
|
2356
|
-
throw e;
|
|
2838
|
+
// throw e;
|
|
2839
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2357
2840
|
}
|
|
2358
2841
|
finally {
|
|
2359
2842
|
const endTime = Date.now();
|
|
2360
|
-
|
|
2843
|
+
_reportToWorld(world, {
|
|
2361
2844
|
element_name: selectors.element_name,
|
|
2362
2845
|
type: Types.GET_TABLE_DATA,
|
|
2363
2846
|
text: "Get table data",
|
|
@@ -2367,7 +2850,7 @@ class StableBrowser {
|
|
|
2367
2850
|
status: "FAILED",
|
|
2368
2851
|
startTime,
|
|
2369
2852
|
endTime,
|
|
2370
|
-
message: error
|
|
2853
|
+
message: error?.message,
|
|
2371
2854
|
}
|
|
2372
2855
|
: {
|
|
2373
2856
|
status: "PASSED",
|
|
@@ -2379,7 +2862,7 @@ class StableBrowser {
|
|
|
2379
2862
|
}
|
|
2380
2863
|
}
|
|
2381
2864
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2382
|
-
|
|
2865
|
+
_validateSelectors(selectors);
|
|
2383
2866
|
if (!query) {
|
|
2384
2867
|
throw new Error("query is null");
|
|
2385
2868
|
}
|
|
@@ -2412,7 +2895,7 @@ class StableBrowser {
|
|
|
2412
2895
|
info.operation = "analyzeTable";
|
|
2413
2896
|
info.selectors = selectors;
|
|
2414
2897
|
info.query = query;
|
|
2415
|
-
query =
|
|
2898
|
+
query = _fixUsingParams(query, _params);
|
|
2416
2899
|
info.query_fixed = query;
|
|
2417
2900
|
info.operator = operator;
|
|
2418
2901
|
info.value = value;
|
|
@@ -2518,11 +3001,12 @@ class StableBrowser {
|
|
|
2518
3001
|
info.screenshotPath = screenshotPath;
|
|
2519
3002
|
Object.assign(e, { info: info });
|
|
2520
3003
|
error = e;
|
|
2521
|
-
throw e;
|
|
3004
|
+
// throw e;
|
|
3005
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2522
3006
|
}
|
|
2523
3007
|
finally {
|
|
2524
3008
|
const endTime = Date.now();
|
|
2525
|
-
|
|
3009
|
+
_reportToWorld(world, {
|
|
2526
3010
|
element_name: selectors.element_name,
|
|
2527
3011
|
type: Types.ANALYZE_TABLE,
|
|
2528
3012
|
text: "Analyze table",
|
|
@@ -2532,7 +3016,7 @@ class StableBrowser {
|
|
|
2532
3016
|
status: "FAILED",
|
|
2533
3017
|
startTime,
|
|
2534
3018
|
endTime,
|
|
2535
|
-
message: error
|
|
3019
|
+
message: error?.message,
|
|
2536
3020
|
}
|
|
2537
3021
|
: {
|
|
2538
3022
|
status: "PASSED",
|
|
@@ -2544,27 +3028,7 @@ class StableBrowser {
|
|
|
2544
3028
|
}
|
|
2545
3029
|
}
|
|
2546
3030
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2547
|
-
|
|
2548
|
-
return value;
|
|
2549
|
-
}
|
|
2550
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2551
|
-
let regex = /{{(.*?)}}/g;
|
|
2552
|
-
let matches = value.match(regex);
|
|
2553
|
-
if (matches) {
|
|
2554
|
-
const testData = this.getTestData(world);
|
|
2555
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2556
|
-
let match = matches[i];
|
|
2557
|
-
let key = match.substring(2, match.length - 2);
|
|
2558
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2559
|
-
if (newValue !== null) {
|
|
2560
|
-
value = value.replace(match, newValue);
|
|
2561
|
-
}
|
|
2562
|
-
}
|
|
2563
|
-
}
|
|
2564
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2565
|
-
return await decrypt(value, null, totpWait);
|
|
2566
|
-
}
|
|
2567
|
-
return value;
|
|
3031
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2568
3032
|
}
|
|
2569
3033
|
_getLoadTimeout(options) {
|
|
2570
3034
|
let timeout = 15000;
|
|
@@ -2576,6 +3040,32 @@ class StableBrowser {
|
|
|
2576
3040
|
}
|
|
2577
3041
|
return timeout;
|
|
2578
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
|
+
}
|
|
2579
3069
|
async waitForPageLoad(options = {}, world = null) {
|
|
2580
3070
|
let timeout = this._getLoadTimeout(options);
|
|
2581
3071
|
const promiseArray = [];
|
|
@@ -2615,7 +3105,7 @@ class StableBrowser {
|
|
|
2615
3105
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2616
3106
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2617
3107
|
const endTime = Date.now();
|
|
2618
|
-
|
|
3108
|
+
_reportToWorld(world, {
|
|
2619
3109
|
type: Types.GET_PAGE_STATUS,
|
|
2620
3110
|
text: "Wait for page load",
|
|
2621
3111
|
screenshotId,
|
|
@@ -2624,7 +3114,7 @@ class StableBrowser {
|
|
|
2624
3114
|
status: "FAILED",
|
|
2625
3115
|
startTime,
|
|
2626
3116
|
endTime,
|
|
2627
|
-
message: error
|
|
3117
|
+
message: error?.message,
|
|
2628
3118
|
}
|
|
2629
3119
|
: {
|
|
2630
3120
|
status: "PASSED",
|
|
@@ -2635,41 +3125,123 @@ class StableBrowser {
|
|
|
2635
3125
|
}
|
|
2636
3126
|
}
|
|
2637
3127
|
async closePage(options = {}, world = null) {
|
|
2638
|
-
const
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
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
|
+
};
|
|
2643
3141
|
try {
|
|
3142
|
+
await _preCommand(state, this);
|
|
2644
3143
|
await this.page.close();
|
|
2645
3144
|
}
|
|
2646
3145
|
catch (e) {
|
|
2647
3146
|
console.log(".");
|
|
3147
|
+
await _commandError(state, e, this);
|
|
2648
3148
|
}
|
|
2649
3149
|
finally {
|
|
2650
|
-
await
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
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;
|
|
2663
3185
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
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);
|
|
2671
3238
|
}
|
|
2672
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
|
+
}
|
|
2673
3245
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2674
3246
|
const startTime = Date.now();
|
|
2675
3247
|
let error = null;
|
|
@@ -2687,21 +3259,23 @@ class StableBrowser {
|
|
|
2687
3259
|
}
|
|
2688
3260
|
catch (e) {
|
|
2689
3261
|
console.log(".");
|
|
3262
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2690
3263
|
}
|
|
2691
3264
|
finally {
|
|
2692
3265
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2693
3266
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2694
3267
|
const endTime = Date.now();
|
|
2695
|
-
|
|
3268
|
+
_reportToWorld(world, {
|
|
2696
3269
|
type: Types.SET_VIEWPORT,
|
|
2697
3270
|
text: "set viewport size to " + width + "x" + hight,
|
|
3271
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2698
3272
|
screenshotId,
|
|
2699
3273
|
result: error
|
|
2700
3274
|
? {
|
|
2701
3275
|
status: "FAILED",
|
|
2702
3276
|
startTime,
|
|
2703
3277
|
endTime,
|
|
2704
|
-
message: error
|
|
3278
|
+
message: error?.message,
|
|
2705
3279
|
}
|
|
2706
3280
|
: {
|
|
2707
3281
|
status: "PASSED",
|
|
@@ -2723,12 +3297,13 @@ class StableBrowser {
|
|
|
2723
3297
|
}
|
|
2724
3298
|
catch (e) {
|
|
2725
3299
|
console.log(".");
|
|
3300
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2726
3301
|
}
|
|
2727
3302
|
finally {
|
|
2728
3303
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2729
3304
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2730
3305
|
const endTime = Date.now();
|
|
2731
|
-
|
|
3306
|
+
_reportToWorld(world, {
|
|
2732
3307
|
type: Types.GET_PAGE_STATUS,
|
|
2733
3308
|
text: "page relaod",
|
|
2734
3309
|
screenshotId,
|
|
@@ -2737,7 +3312,7 @@ class StableBrowser {
|
|
|
2737
3312
|
status: "FAILED",
|
|
2738
3313
|
startTime,
|
|
2739
3314
|
endTime,
|
|
2740
|
-
message: error
|
|
3315
|
+
message: error?.message,
|
|
2741
3316
|
}
|
|
2742
3317
|
: {
|
|
2743
3318
|
status: "PASSED",
|
|
@@ -2764,11 +3339,121 @@ class StableBrowser {
|
|
|
2764
3339
|
console.log("#-#");
|
|
2765
3340
|
}
|
|
2766
3341
|
}
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
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
|
+
if (!process.env.TEMP_RUN) {
|
|
3366
|
+
await getTestData(envName, world, undefined, this.featureName, this.scenarioName);
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
async afterScenario(world, scenario) { }
|
|
3370
|
+
async beforeStep(world, step) {
|
|
3371
|
+
if (!this.beforeScenarioCalled) {
|
|
3372
|
+
this.beforeScenario(world, step);
|
|
3373
|
+
}
|
|
3374
|
+
if (this.stepIndex === undefined) {
|
|
3375
|
+
this.stepIndex = 0;
|
|
3376
|
+
}
|
|
3377
|
+
else {
|
|
3378
|
+
this.stepIndex++;
|
|
3379
|
+
}
|
|
3380
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3381
|
+
this.stepName = step.pickleStep.text;
|
|
3382
|
+
this.logger.info("step: " + this.stepName);
|
|
3383
|
+
}
|
|
3384
|
+
else if (step && step.text) {
|
|
3385
|
+
this.stepName = step.text;
|
|
3386
|
+
}
|
|
3387
|
+
else {
|
|
3388
|
+
this.stepName = "step " + this.stepIndex;
|
|
3389
|
+
}
|
|
3390
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3391
|
+
if (this.context.browserObject.context) {
|
|
3392
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
if (this.initSnapshotTaken === false) {
|
|
3396
|
+
this.initSnapshotTaken = true;
|
|
3397
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3398
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3399
|
+
if (snapshot) {
|
|
3400
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
async getAriaSnapshot() {
|
|
3406
|
+
try {
|
|
3407
|
+
// find the page url
|
|
3408
|
+
const url = await this.page.url();
|
|
3409
|
+
// extract the path from the url
|
|
3410
|
+
const path = new URL(url).pathname;
|
|
3411
|
+
// get the page title
|
|
3412
|
+
const title = await this.page.title();
|
|
3413
|
+
// go over other frams
|
|
3414
|
+
const frames = this.page.frames();
|
|
3415
|
+
const snapshots = [];
|
|
3416
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3417
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3418
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3419
|
+
content.push(`- frame: ${i}`);
|
|
3420
|
+
const frame = frames[i];
|
|
3421
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3422
|
+
content.push(snapshot);
|
|
3423
|
+
}
|
|
3424
|
+
return content.join("\n");
|
|
3425
|
+
}
|
|
3426
|
+
catch (e) {
|
|
3427
|
+
console.error(e);
|
|
3428
|
+
}
|
|
3429
|
+
return null;
|
|
3430
|
+
}
|
|
3431
|
+
async afterStep(world, step) {
|
|
3432
|
+
this.stepName = null;
|
|
3433
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3434
|
+
if (this.context.browserObject.context) {
|
|
3435
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
3436
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3437
|
+
});
|
|
3438
|
+
if (world && world.attach) {
|
|
3439
|
+
await world.attach(JSON.stringify({
|
|
3440
|
+
type: "trace",
|
|
3441
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3442
|
+
}), "application/json+trace");
|
|
3443
|
+
}
|
|
3444
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
if (this.context) {
|
|
3448
|
+
this.context.examplesRow = null;
|
|
3449
|
+
}
|
|
3450
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3451
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3452
|
+
if (snapshot) {
|
|
3453
|
+
const obj = {};
|
|
3454
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3455
|
+
}
|
|
2770
3456
|
}
|
|
2771
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2772
3457
|
}
|
|
2773
3458
|
}
|
|
2774
3459
|
function createTimedPromise(promise, label) {
|
|
@@ -2776,151 +3461,5 @@ function createTimedPromise(promise, label) {
|
|
|
2776
3461
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2777
3462
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2778
3463
|
}
|
|
2779
|
-
const KEYBOARD_EVENTS = [
|
|
2780
|
-
"ALT",
|
|
2781
|
-
"AltGraph",
|
|
2782
|
-
"CapsLock",
|
|
2783
|
-
"Control",
|
|
2784
|
-
"Fn",
|
|
2785
|
-
"FnLock",
|
|
2786
|
-
"Hyper",
|
|
2787
|
-
"Meta",
|
|
2788
|
-
"NumLock",
|
|
2789
|
-
"ScrollLock",
|
|
2790
|
-
"Shift",
|
|
2791
|
-
"Super",
|
|
2792
|
-
"Symbol",
|
|
2793
|
-
"SymbolLock",
|
|
2794
|
-
"Enter",
|
|
2795
|
-
"Tab",
|
|
2796
|
-
"ArrowDown",
|
|
2797
|
-
"ArrowLeft",
|
|
2798
|
-
"ArrowRight",
|
|
2799
|
-
"ArrowUp",
|
|
2800
|
-
"End",
|
|
2801
|
-
"Home",
|
|
2802
|
-
"PageDown",
|
|
2803
|
-
"PageUp",
|
|
2804
|
-
"Backspace",
|
|
2805
|
-
"Clear",
|
|
2806
|
-
"Copy",
|
|
2807
|
-
"CrSel",
|
|
2808
|
-
"Cut",
|
|
2809
|
-
"Delete",
|
|
2810
|
-
"EraseEof",
|
|
2811
|
-
"ExSel",
|
|
2812
|
-
"Insert",
|
|
2813
|
-
"Paste",
|
|
2814
|
-
"Redo",
|
|
2815
|
-
"Undo",
|
|
2816
|
-
"Accept",
|
|
2817
|
-
"Again",
|
|
2818
|
-
"Attn",
|
|
2819
|
-
"Cancel",
|
|
2820
|
-
"ContextMenu",
|
|
2821
|
-
"Escape",
|
|
2822
|
-
"Execute",
|
|
2823
|
-
"Find",
|
|
2824
|
-
"Finish",
|
|
2825
|
-
"Help",
|
|
2826
|
-
"Pause",
|
|
2827
|
-
"Play",
|
|
2828
|
-
"Props",
|
|
2829
|
-
"Select",
|
|
2830
|
-
"ZoomIn",
|
|
2831
|
-
"ZoomOut",
|
|
2832
|
-
"BrightnessDown",
|
|
2833
|
-
"BrightnessUp",
|
|
2834
|
-
"Eject",
|
|
2835
|
-
"LogOff",
|
|
2836
|
-
"Power",
|
|
2837
|
-
"PowerOff",
|
|
2838
|
-
"PrintScreen",
|
|
2839
|
-
"Hibernate",
|
|
2840
|
-
"Standby",
|
|
2841
|
-
"WakeUp",
|
|
2842
|
-
"AllCandidates",
|
|
2843
|
-
"Alphanumeric",
|
|
2844
|
-
"CodeInput",
|
|
2845
|
-
"Compose",
|
|
2846
|
-
"Convert",
|
|
2847
|
-
"Dead",
|
|
2848
|
-
"FinalMode",
|
|
2849
|
-
"GroupFirst",
|
|
2850
|
-
"GroupLast",
|
|
2851
|
-
"GroupNext",
|
|
2852
|
-
"GroupPrevious",
|
|
2853
|
-
"ModeChange",
|
|
2854
|
-
"NextCandidate",
|
|
2855
|
-
"NonConvert",
|
|
2856
|
-
"PreviousCandidate",
|
|
2857
|
-
"Process",
|
|
2858
|
-
"SingleCandidate",
|
|
2859
|
-
"HangulMode",
|
|
2860
|
-
"HanjaMode",
|
|
2861
|
-
"JunjaMode",
|
|
2862
|
-
"Eisu",
|
|
2863
|
-
"Hankaku",
|
|
2864
|
-
"Hiragana",
|
|
2865
|
-
"HiraganaKatakana",
|
|
2866
|
-
"KanaMode",
|
|
2867
|
-
"KanjiMode",
|
|
2868
|
-
"Katakana",
|
|
2869
|
-
"Romaji",
|
|
2870
|
-
"Zenkaku",
|
|
2871
|
-
"ZenkakuHanaku",
|
|
2872
|
-
"F1",
|
|
2873
|
-
"F2",
|
|
2874
|
-
"F3",
|
|
2875
|
-
"F4",
|
|
2876
|
-
"F5",
|
|
2877
|
-
"F6",
|
|
2878
|
-
"F7",
|
|
2879
|
-
"F8",
|
|
2880
|
-
"F9",
|
|
2881
|
-
"F10",
|
|
2882
|
-
"F11",
|
|
2883
|
-
"F12",
|
|
2884
|
-
"Soft1",
|
|
2885
|
-
"Soft2",
|
|
2886
|
-
"Soft3",
|
|
2887
|
-
"Soft4",
|
|
2888
|
-
"ChannelDown",
|
|
2889
|
-
"ChannelUp",
|
|
2890
|
-
"Close",
|
|
2891
|
-
"MailForward",
|
|
2892
|
-
"MailReply",
|
|
2893
|
-
"MailSend",
|
|
2894
|
-
"MediaFastForward",
|
|
2895
|
-
"MediaPause",
|
|
2896
|
-
"MediaPlay",
|
|
2897
|
-
"MediaPlayPause",
|
|
2898
|
-
"MediaRecord",
|
|
2899
|
-
"MediaRewind",
|
|
2900
|
-
"MediaStop",
|
|
2901
|
-
"MediaTrackNext",
|
|
2902
|
-
"MediaTrackPrevious",
|
|
2903
|
-
"AudioBalanceLeft",
|
|
2904
|
-
"AudioBalanceRight",
|
|
2905
|
-
"AudioBassBoostDown",
|
|
2906
|
-
"AudioBassBoostToggle",
|
|
2907
|
-
"AudioBassBoostUp",
|
|
2908
|
-
"AudioFaderFront",
|
|
2909
|
-
"AudioFaderRear",
|
|
2910
|
-
"AudioSurroundModeNext",
|
|
2911
|
-
"AudioTrebleDown",
|
|
2912
|
-
"AudioTrebleUp",
|
|
2913
|
-
"AudioVolumeDown",
|
|
2914
|
-
"AudioVolumeMute",
|
|
2915
|
-
"AudioVolumeUp",
|
|
2916
|
-
"MicrophoneToggle",
|
|
2917
|
-
"MicrophoneVolumeDown",
|
|
2918
|
-
"MicrophoneVolumeMute",
|
|
2919
|
-
"MicrophoneVolumeUp",
|
|
2920
|
-
"TV",
|
|
2921
|
-
"TV3DMode",
|
|
2922
|
-
"TVAntennaCable",
|
|
2923
|
-
"TVAudioDescription",
|
|
2924
|
-
];
|
|
2925
3464
|
export { StableBrowser };
|
|
2926
3465
|
//# sourceMappingURL=stable_browser.js.map
|