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