automation_model 1.0.426-dev → 1.0.426
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/lib/api.d.ts +43 -1
- package/lib/api.js +242 -41
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +5 -2
- package/lib/auto_page.js +145 -46
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +7 -3
- package/lib/browser_manager.js +154 -49
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +6 -0
- package/lib/command_common.js +183 -0
- package/lib/command_common.js.map +1 -0
- package/lib/environment.d.ts +3 -0
- package/lib/environment.js +5 -2
- package/lib/environment.js.map +1 -1
- package/lib/error-messages.d.ts +6 -0
- package/lib/error-messages.js +206 -0
- package/lib/error-messages.js.map +1 -0
- package/lib/generation_scripts.d.ts +4 -0
- package/lib/generation_scripts.js +2 -0
- package/lib/generation_scripts.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +5 -2
- package/lib/init_browser.js +124 -7
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.d.ts +7 -0
- package/lib/locate_element.js +215 -0
- package/lib/locate_element.js.map +1 -0
- package/lib/locator.d.ts +36 -0
- package/lib/locator.js +165 -0
- package/lib/locator.js.map +1 -1
- package/lib/locator_log.d.ts +26 -0
- package/lib/locator_log.js +69 -0
- package/lib/locator_log.js.map +1 -0
- package/lib/network.d.ts +3 -0
- package/lib/network.js +183 -0
- package/lib/network.js.map +1 -0
- package/lib/scripts/axe.mini.js +12 -0
- package/lib/stable_browser.d.ts +100 -36
- package/lib/stable_browser.js +1761 -1244
- package/lib/stable_browser.js.map +1 -1
- package/lib/table.d.ts +13 -0
- package/lib/table.js +187 -0
- package/lib/table.js.map +1 -0
- package/lib/table_helper.d.ts +19 -0
- package/lib/table_helper.js +116 -0
- package/lib/table_helper.js.map +1 -0
- package/lib/test_context.d.ts +6 -0
- package/lib/test_context.js +14 -10
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +22 -2
- package/lib/utils.js +652 -11
- package/lib/utils.js.map +1 -1
- package/package.json +15 -10
package/lib/stable_browser.js
CHANGED
|
@@ -2,20 +2,29 @@
|
|
|
2
2
|
import { expect } from "@playwright/test";
|
|
3
3
|
import dayjs from "dayjs";
|
|
4
4
|
import fs from "fs";
|
|
5
|
+
import { Jimp } from "jimp";
|
|
5
6
|
import path from "path";
|
|
6
7
|
import reg_parser from "regex-parser";
|
|
7
|
-
import sharp from "sharp";
|
|
8
8
|
import { findDateAlternatives, findNumberAlternatives } from "./analyze_helper.js";
|
|
9
9
|
import { getDateTimeValue } from "./date_time.js";
|
|
10
10
|
import drawRectangle from "./drawRect.js";
|
|
11
11
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
12
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
13
|
-
import
|
|
14
|
-
import { decrypt } from "./utils.js";
|
|
13
|
+
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, } from "./utils.js";
|
|
15
14
|
import csv from "csv-parser";
|
|
16
15
|
import { Readable } from "node:stream";
|
|
17
|
-
|
|
16
|
+
import readline from "readline";
|
|
17
|
+
import { getContext, refreshBrowser } from "./init_browser.js";
|
|
18
|
+
import { locate_element } from "./locate_element.js";
|
|
19
|
+
import { randomUUID } from "crypto";
|
|
20
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot, _reportToWorld, } from "./command_common.js";
|
|
21
|
+
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
22
|
+
import { LocatorLog } from "./locator_log.js";
|
|
23
|
+
import axios from "axios";
|
|
24
|
+
import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
25
|
+
export const Types = {
|
|
18
26
|
CLICK: "click_element",
|
|
27
|
+
WAIT_ELEMENT: "wait_element",
|
|
19
28
|
NAVIGATE: "navigate",
|
|
20
29
|
FILL: "fill_element",
|
|
21
30
|
EXECUTE: "execute_page_method",
|
|
@@ -25,6 +34,8 @@ const Types = {
|
|
|
25
34
|
GET_PAGE_STATUS: "get_page_status",
|
|
26
35
|
CLICK_ROW_ACTION: "click_row_action",
|
|
27
36
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
37
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
38
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
28
39
|
ANALYZE_TABLE: "analyze_table",
|
|
29
40
|
SELECT: "select_combobox",
|
|
30
41
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -35,21 +46,40 @@ const Types = {
|
|
|
35
46
|
UNCHECK: "uncheck_element",
|
|
36
47
|
EXTRACT: "extract_attribute",
|
|
37
48
|
CLOSE_PAGE: "close_page",
|
|
49
|
+
TABLE_OPERATION: "table_operation",
|
|
38
50
|
SET_DATE_TIME: "set_date_time",
|
|
39
51
|
SET_VIEWPORT: "set_viewport",
|
|
40
52
|
VERIFY_VISUAL: "verify_visual",
|
|
41
53
|
LOAD_DATA: "load_data",
|
|
42
54
|
SET_INPUT: "set_input",
|
|
55
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
56
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
57
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
58
|
+
};
|
|
59
|
+
export const apps = {};
|
|
60
|
+
const formatElementName = (elementName) => {
|
|
61
|
+
return elementName ? JSON.stringify(elementName) : "element";
|
|
43
62
|
};
|
|
44
63
|
class StableBrowser {
|
|
45
|
-
|
|
64
|
+
browser;
|
|
65
|
+
page;
|
|
66
|
+
logger;
|
|
67
|
+
context;
|
|
68
|
+
world;
|
|
69
|
+
project_path = null;
|
|
70
|
+
webLogFile = null;
|
|
71
|
+
networkLogger = null;
|
|
72
|
+
configuration = null;
|
|
73
|
+
appName = "main";
|
|
74
|
+
tags = null;
|
|
75
|
+
isRecording = false;
|
|
76
|
+
initSnapshotTaken = false;
|
|
77
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
46
78
|
this.browser = browser;
|
|
47
79
|
this.page = page;
|
|
48
80
|
this.logger = logger;
|
|
49
81
|
this.context = context;
|
|
50
|
-
this.
|
|
51
|
-
this.webLogFile = null;
|
|
52
|
-
this.configuration = null;
|
|
82
|
+
this.world = world;
|
|
53
83
|
if (!this.logger) {
|
|
54
84
|
this.logger = console;
|
|
55
85
|
}
|
|
@@ -74,17 +104,32 @@ class StableBrowser {
|
|
|
74
104
|
catch (e) {
|
|
75
105
|
this.logger.error("unable to read ai_config.json");
|
|
76
106
|
}
|
|
77
|
-
const logFolder = path.join(this.project_path, "logs", "web");
|
|
78
|
-
this.webLogFile = this.getWebLogFile(logFolder);
|
|
79
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
80
|
-
this.registerRequestListener();
|
|
81
|
-
context.pages = [this.page];
|
|
82
107
|
context.pageLoading = { status: false };
|
|
108
|
+
context.pages = [this.page];
|
|
109
|
+
const logFolder = path.join(this.project_path, "logs", "web");
|
|
110
|
+
this.world = world;
|
|
111
|
+
this.registerEventListeners(this.context);
|
|
112
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
113
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
114
|
+
}
|
|
115
|
+
registerEventListeners(context) {
|
|
116
|
+
this.registerConsoleLogListener(this.page, context);
|
|
117
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
118
|
+
if (!context.pageLoading) {
|
|
119
|
+
context.pageLoading = { status: false };
|
|
120
|
+
}
|
|
83
121
|
context.playContext.on("page", async function (page) {
|
|
122
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
123
|
+
console.log("close unexpected popups");
|
|
124
|
+
await page.close();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
84
127
|
context.pageLoading.status = true;
|
|
85
128
|
this.page = page;
|
|
86
129
|
context.page = page;
|
|
87
130
|
context.pages.push(page);
|
|
131
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
132
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
88
133
|
page.on("close", async () => {
|
|
89
134
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
90
135
|
this.context.pages.pop();
|
|
@@ -109,117 +154,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();
|
|
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);
|
|
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);
|
|
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,198 @@ 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();
|
|
1954
|
+
break;
|
|
1955
|
+
case "text":
|
|
1956
|
+
state.value = await state.element.textContent();
|
|
1854
1957
|
break;
|
|
1855
1958
|
default:
|
|
1856
|
-
|
|
1959
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1857
1960
|
break;
|
|
1858
1961
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
this.logger.info("set test data: " + variable + "=" + info.value);
|
|
1865
|
-
return info;
|
|
1962
|
+
state.info.value = state.value;
|
|
1963
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1964
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1965
|
+
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1966
|
+
return state.info;
|
|
1866
1967
|
}
|
|
1867
1968
|
catch (e) {
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1969
|
+
await _commandError(state, e, this);
|
|
1970
|
+
}
|
|
1971
|
+
finally {
|
|
1972
|
+
_commandFinally(state, this);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1976
|
+
const state = {
|
|
1977
|
+
selectors,
|
|
1978
|
+
_params,
|
|
1979
|
+
attribute,
|
|
1980
|
+
value,
|
|
1981
|
+
options,
|
|
1982
|
+
world,
|
|
1983
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1984
|
+
highlight: true,
|
|
1985
|
+
screenshot: true,
|
|
1986
|
+
text: `Verify element attribute`,
|
|
1987
|
+
_text: `Verify attribute ${attribute} from ${selectors.element_name} is ${value}`,
|
|
1988
|
+
operation: "verifyAttribute",
|
|
1989
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1990
|
+
allowDisabled: true,
|
|
1991
|
+
};
|
|
1992
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1993
|
+
let val;
|
|
1994
|
+
let expectedValue;
|
|
1995
|
+
try {
|
|
1996
|
+
await _preCommand(state, this);
|
|
1997
|
+
expectedValue = await replaceWithLocalTestData(state.value, world);
|
|
1998
|
+
state.info.expectedValue = expectedValue;
|
|
1999
|
+
switch (attribute) {
|
|
2000
|
+
case "innerText":
|
|
2001
|
+
val = String(await state.element.innerText());
|
|
2002
|
+
break;
|
|
2003
|
+
case "text":
|
|
2004
|
+
val = String(await state.element.textContent());
|
|
2005
|
+
break;
|
|
2006
|
+
case "value":
|
|
2007
|
+
val = String(await state.element.inputValue());
|
|
2008
|
+
break;
|
|
2009
|
+
case "checked":
|
|
2010
|
+
val = String(await state.element.isChecked());
|
|
2011
|
+
break;
|
|
2012
|
+
case "disabled":
|
|
2013
|
+
val = String(await state.element.isDisabled());
|
|
2014
|
+
break;
|
|
2015
|
+
case "readOnly":
|
|
2016
|
+
const isEditable = await state.element.isEditable();
|
|
2017
|
+
val = String(!isEditable);
|
|
2018
|
+
break;
|
|
2019
|
+
default:
|
|
2020
|
+
val = String(await state.element.getAttribute(attribute));
|
|
2021
|
+
break;
|
|
2022
|
+
}
|
|
2023
|
+
state.info.value = val;
|
|
2024
|
+
let regex;
|
|
2025
|
+
if (expectedValue.startsWith("/") && expectedValue.endsWith("/")) {
|
|
2026
|
+
const patternBody = expectedValue.slice(1, -1);
|
|
2027
|
+
regex = new RegExp(patternBody, "g");
|
|
2028
|
+
}
|
|
2029
|
+
else {
|
|
2030
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2031
|
+
regex = new RegExp(escapedPattern, "g");
|
|
2032
|
+
}
|
|
2033
|
+
if (!val.match(regex)) {
|
|
2034
|
+
let errorMessage = `The ${attribute} attribute has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2035
|
+
state.info.failCause.assertionFailed = true;
|
|
2036
|
+
state.info.failCause.lastError = errorMessage;
|
|
2037
|
+
throw new Error(errorMessage);
|
|
2038
|
+
}
|
|
2039
|
+
return state.info;
|
|
2040
|
+
}
|
|
2041
|
+
catch (e) {
|
|
2042
|
+
await _commandError(state, e, this);
|
|
1875
2043
|
}
|
|
1876
2044
|
finally {
|
|
1877
|
-
|
|
1878
|
-
this._reportToWorld(world, {
|
|
1879
|
-
element_name: selectors.element_name,
|
|
1880
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1881
|
-
variable: variable,
|
|
1882
|
-
value: info.value,
|
|
1883
|
-
text: "Extract attribute from element",
|
|
1884
|
-
screenshotId,
|
|
1885
|
-
result: error
|
|
1886
|
-
? {
|
|
1887
|
-
status: "FAILED",
|
|
1888
|
-
startTime,
|
|
1889
|
-
endTime,
|
|
1890
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1891
|
-
}
|
|
1892
|
-
: {
|
|
1893
|
-
status: "PASSED",
|
|
1894
|
-
startTime,
|
|
1895
|
-
endTime,
|
|
1896
|
-
},
|
|
1897
|
-
info: info,
|
|
1898
|
-
});
|
|
2045
|
+
_commandFinally(state, this);
|
|
1899
2046
|
}
|
|
1900
2047
|
}
|
|
1901
2048
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1916,7 +2063,7 @@ class StableBrowser {
|
|
|
1916
2063
|
if (options && options.timeout) {
|
|
1917
2064
|
timeout = options.timeout;
|
|
1918
2065
|
}
|
|
1919
|
-
const serviceUrl =
|
|
2066
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1920
2067
|
const request = {
|
|
1921
2068
|
method: "POST",
|
|
1922
2069
|
url: serviceUrl,
|
|
@@ -1972,7 +2119,8 @@ class StableBrowser {
|
|
|
1972
2119
|
catch (e) {
|
|
1973
2120
|
errorCount++;
|
|
1974
2121
|
if (errorCount > 3) {
|
|
1975
|
-
throw e;
|
|
2122
|
+
// throw e;
|
|
2123
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1976
2124
|
}
|
|
1977
2125
|
// ignore
|
|
1978
2126
|
}
|
|
@@ -1986,27 +2134,32 @@ class StableBrowser {
|
|
|
1986
2134
|
async _highlightElements(scope, css) {
|
|
1987
2135
|
try {
|
|
1988
2136
|
if (!scope) {
|
|
2137
|
+
// console.log(`Scope is not defined`);
|
|
1989
2138
|
return;
|
|
1990
2139
|
}
|
|
1991
2140
|
if (!css) {
|
|
1992
2141
|
scope
|
|
1993
2142
|
.evaluate((node) => {
|
|
1994
2143
|
if (node && node.style) {
|
|
1995
|
-
let
|
|
1996
|
-
|
|
2144
|
+
let originalOutline = node.style.outline;
|
|
2145
|
+
// console.log(`Original outline was: ${originalOutline}`);
|
|
2146
|
+
// node.__previousOutline = originalOutline;
|
|
2147
|
+
node.style.outline = "2px solid red";
|
|
2148
|
+
// console.log(`New outline is: ${node.style.outline}`);
|
|
1997
2149
|
if (window) {
|
|
1998
2150
|
window.addEventListener("beforeunload", function (e) {
|
|
1999
|
-
node.style.
|
|
2151
|
+
node.style.outline = originalOutline;
|
|
2000
2152
|
});
|
|
2001
2153
|
}
|
|
2002
2154
|
setTimeout(function () {
|
|
2003
|
-
node.style.
|
|
2155
|
+
node.style.outline = originalOutline;
|
|
2004
2156
|
}, 2000);
|
|
2005
2157
|
}
|
|
2006
2158
|
})
|
|
2007
2159
|
.then(() => { })
|
|
2008
2160
|
.catch((e) => {
|
|
2009
2161
|
// ignore
|
|
2162
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2010
2163
|
});
|
|
2011
2164
|
}
|
|
2012
2165
|
else {
|
|
@@ -2022,17 +2175,18 @@ class StableBrowser {
|
|
|
2022
2175
|
if (!element.style) {
|
|
2023
2176
|
return;
|
|
2024
2177
|
}
|
|
2025
|
-
|
|
2178
|
+
let originalOutline = element.style.outline;
|
|
2179
|
+
element.__previousOutline = originalOutline;
|
|
2026
2180
|
// Set the new border to be red and 2px solid
|
|
2027
|
-
element.style.
|
|
2181
|
+
element.style.outline = "2px solid red";
|
|
2028
2182
|
if (window) {
|
|
2029
2183
|
window.addEventListener("beforeunload", function (e) {
|
|
2030
|
-
element.style.
|
|
2184
|
+
element.style.outline = originalOutline;
|
|
2031
2185
|
});
|
|
2032
2186
|
}
|
|
2033
2187
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2034
2188
|
setTimeout(function () {
|
|
2035
|
-
element.style.
|
|
2189
|
+
element.style.outline = originalOutline;
|
|
2036
2190
|
}, 2000);
|
|
2037
2191
|
}
|
|
2038
2192
|
return;
|
|
@@ -2040,6 +2194,7 @@ class StableBrowser {
|
|
|
2040
2194
|
.then(() => { })
|
|
2041
2195
|
.catch((e) => {
|
|
2042
2196
|
// ignore
|
|
2197
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2043
2198
|
});
|
|
2044
2199
|
}
|
|
2045
2200
|
}
|
|
@@ -2047,6 +2202,54 @@ class StableBrowser {
|
|
|
2047
2202
|
console.debug(error);
|
|
2048
2203
|
}
|
|
2049
2204
|
}
|
|
2205
|
+
// async _unhighlightElements(scope, css) {
|
|
2206
|
+
// try {
|
|
2207
|
+
// if (!scope) {
|
|
2208
|
+
// return;
|
|
2209
|
+
// }
|
|
2210
|
+
// if (!css) {
|
|
2211
|
+
// scope
|
|
2212
|
+
// .evaluate((node) => {
|
|
2213
|
+
// if (node && node.style) {
|
|
2214
|
+
// if (!node.__previousOutline) {
|
|
2215
|
+
// node.style.outline = "";
|
|
2216
|
+
// } else {
|
|
2217
|
+
// node.style.outline = node.__previousOutline;
|
|
2218
|
+
// }
|
|
2219
|
+
// }
|
|
2220
|
+
// })
|
|
2221
|
+
// .then(() => {})
|
|
2222
|
+
// .catch((e) => {
|
|
2223
|
+
// // console.log(`Error while unhighlighting node ${JSON.stringify(scope)}: ${e}`);
|
|
2224
|
+
// });
|
|
2225
|
+
// } else {
|
|
2226
|
+
// scope
|
|
2227
|
+
// .evaluate(([css]) => {
|
|
2228
|
+
// if (!css) {
|
|
2229
|
+
// return;
|
|
2230
|
+
// }
|
|
2231
|
+
// let elements = Array.from(document.querySelectorAll(css));
|
|
2232
|
+
// for (i = 0; i < elements.length; i++) {
|
|
2233
|
+
// let element = elements[i];
|
|
2234
|
+
// if (!element.style) {
|
|
2235
|
+
// return;
|
|
2236
|
+
// }
|
|
2237
|
+
// if (!element.__previousOutline) {
|
|
2238
|
+
// element.style.outline = "";
|
|
2239
|
+
// } else {
|
|
2240
|
+
// element.style.outline = element.__previousOutline;
|
|
2241
|
+
// }
|
|
2242
|
+
// }
|
|
2243
|
+
// })
|
|
2244
|
+
// .then(() => {})
|
|
2245
|
+
// .catch((e) => {
|
|
2246
|
+
// // console.error(`Error while unhighlighting element in css: ${e}`);
|
|
2247
|
+
// });
|
|
2248
|
+
// }
|
|
2249
|
+
// } catch (error) {
|
|
2250
|
+
// // console.debug(error);
|
|
2251
|
+
// }
|
|
2252
|
+
// }
|
|
2050
2253
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2051
2254
|
const startTime = Date.now();
|
|
2052
2255
|
let error = null;
|
|
@@ -2083,20 +2286,22 @@ class StableBrowser {
|
|
|
2083
2286
|
info.screenshotPath = screenshotPath;
|
|
2084
2287
|
Object.assign(e, { info: info });
|
|
2085
2288
|
error = e;
|
|
2086
|
-
throw e;
|
|
2289
|
+
// throw e;
|
|
2290
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2087
2291
|
}
|
|
2088
2292
|
finally {
|
|
2089
2293
|
const endTime = Date.now();
|
|
2090
|
-
|
|
2294
|
+
_reportToWorld(world, {
|
|
2091
2295
|
type: Types.VERIFY_PAGE_PATH,
|
|
2092
2296
|
text: "Verify page path",
|
|
2297
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2093
2298
|
screenshotId,
|
|
2094
2299
|
result: error
|
|
2095
2300
|
? {
|
|
2096
2301
|
status: "FAILED",
|
|
2097
2302
|
startTime,
|
|
2098
2303
|
endTime,
|
|
2099
|
-
message: error
|
|
2304
|
+
message: error?.message,
|
|
2100
2305
|
}
|
|
2101
2306
|
: {
|
|
2102
2307
|
status: "PASSED",
|
|
@@ -2107,94 +2312,58 @@ class StableBrowser {
|
|
|
2107
2312
|
});
|
|
2108
2313
|
}
|
|
2109
2314
|
}
|
|
2110
|
-
async
|
|
2315
|
+
async verifyPageTitle(title, options = {}, world = null) {
|
|
2111
2316
|
const startTime = Date.now();
|
|
2112
|
-
const timeout = this._getLoadTimeout(options);
|
|
2113
2317
|
let error = null;
|
|
2114
2318
|
let screenshotId = null;
|
|
2115
2319
|
let screenshotPath = null;
|
|
2116
2320
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2117
2321
|
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);
|
|
2322
|
+
info.log = "***** verify page title " + title + " *****\n";
|
|
2323
|
+
info.operation = "verifyPageTitle";
|
|
2324
|
+
const newValue = await this._replaceWithLocalData(title, world);
|
|
2325
|
+
if (newValue !== title) {
|
|
2326
|
+
this.logger.info(title + "=" + newValue);
|
|
2327
|
+
title = newValue;
|
|
2328
|
+
}
|
|
2329
|
+
info.title = title;
|
|
2128
2330
|
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`);
|
|
2331
|
+
for (let i = 0; i < 30; i++) {
|
|
2332
|
+
const foundTitle = await this.page.title();
|
|
2333
|
+
if (!foundTitle.includes(title)) {
|
|
2334
|
+
if (i === 29) {
|
|
2335
|
+
throw new Error(`url ${foundTitle} doesn't contain ${title}`);
|
|
2158
2336
|
}
|
|
2159
2337
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2160
2338
|
continue;
|
|
2161
2339
|
}
|
|
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
2340
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2173
2341
|
return info;
|
|
2174
2342
|
}
|
|
2175
|
-
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2176
2343
|
}
|
|
2177
2344
|
catch (e) {
|
|
2178
2345
|
//await this.closeUnexpectedPopups();
|
|
2179
|
-
this.logger.error("verify
|
|
2346
|
+
this.logger.error("verify page title failed " + info.log);
|
|
2180
2347
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2181
2348
|
info.screenshotPath = screenshotPath;
|
|
2182
2349
|
Object.assign(e, { info: info });
|
|
2183
2350
|
error = e;
|
|
2184
|
-
throw e;
|
|
2351
|
+
// throw e;
|
|
2352
|
+
await _commandError({ text: "verifyPageTitle", operation: "verifyPageTitle", title, info, throwError: true }, e, this);
|
|
2185
2353
|
}
|
|
2186
2354
|
finally {
|
|
2187
2355
|
const endTime = Date.now();
|
|
2188
|
-
|
|
2189
|
-
type: Types.
|
|
2190
|
-
text: "Verify
|
|
2356
|
+
_reportToWorld(world, {
|
|
2357
|
+
type: Types.VERIFY_PAGE_PATH,
|
|
2358
|
+
text: "Verify page title",
|
|
2359
|
+
_text: "Verify the page title contains " + title,
|
|
2191
2360
|
screenshotId,
|
|
2192
2361
|
result: error
|
|
2193
2362
|
? {
|
|
2194
2363
|
status: "FAILED",
|
|
2195
2364
|
startTime,
|
|
2196
2365
|
endTime,
|
|
2197
|
-
message: error
|
|
2366
|
+
message: error?.message,
|
|
2198
2367
|
}
|
|
2199
2368
|
: {
|
|
2200
2369
|
status: "PASSED",
|
|
@@ -2205,15 +2374,317 @@ class StableBrowser {
|
|
|
2205
2374
|
});
|
|
2206
2375
|
}
|
|
2207
2376
|
}
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2377
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state, partial = true, ignoreCase = false) {
|
|
2378
|
+
const frames = this.page.frames();
|
|
2379
|
+
let results = [];
|
|
2380
|
+
// let ignoreCase = false;
|
|
2381
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2382
|
+
if (dateAlternatives.date) {
|
|
2383
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2384
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2385
|
+
result.frame = frames[i];
|
|
2386
|
+
results.push(result);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
else if (numberAlternatives.number) {
|
|
2390
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2391
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2392
|
+
result.frame = frames[i];
|
|
2393
|
+
results.push(result);
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
else {
|
|
2397
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, partial, ignoreCase, {});
|
|
2398
|
+
result.frame = frames[i];
|
|
2399
|
+
results.push(result);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
state.info.results = results;
|
|
2403
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2404
|
+
return resultWithElementsFound;
|
|
2405
|
+
}
|
|
2406
|
+
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2407
|
+
text = unEscapeString(text);
|
|
2408
|
+
const state = {
|
|
2409
|
+
text_search: text,
|
|
2410
|
+
options,
|
|
2411
|
+
world,
|
|
2412
|
+
locate: false,
|
|
2413
|
+
scroll: false,
|
|
2414
|
+
highlight: false,
|
|
2415
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2416
|
+
text: `Verify the text '${text}' exists in page`,
|
|
2417
|
+
_text: `Verify the text '${text}' exists in page`,
|
|
2418
|
+
operation: "verifyTextExistInPage",
|
|
2419
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2420
|
+
};
|
|
2421
|
+
if (testForRegex(text)) {
|
|
2422
|
+
text = text.replace(/\\"/g, '"');
|
|
2423
|
+
}
|
|
2424
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2425
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2426
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2427
|
+
if (newValue !== text) {
|
|
2428
|
+
this.logger.info(text + "=" + newValue);
|
|
2429
|
+
text = newValue;
|
|
2430
|
+
}
|
|
2431
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2432
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2433
|
+
try {
|
|
2434
|
+
await _preCommand(state, this);
|
|
2435
|
+
state.info.text = text;
|
|
2436
|
+
while (true) {
|
|
2437
|
+
let resultWithElementsFound = {
|
|
2438
|
+
length: 0,
|
|
2439
|
+
};
|
|
2440
|
+
try {
|
|
2441
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2442
|
+
}
|
|
2443
|
+
catch (error) {
|
|
2444
|
+
// ignore
|
|
2445
|
+
}
|
|
2446
|
+
if (resultWithElementsFound.length === 0) {
|
|
2447
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2448
|
+
throw new Error(`Text ${text} not found in page`);
|
|
2449
|
+
}
|
|
2450
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2451
|
+
continue;
|
|
2452
|
+
}
|
|
2453
|
+
try {
|
|
2454
|
+
if (resultWithElementsFound[0].randomToken) {
|
|
2455
|
+
const frame = resultWithElementsFound[0].frame;
|
|
2456
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2457
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2458
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2459
|
+
// console.log(`Highlighting for verify text is found while running from recorder`);
|
|
2460
|
+
// this._highlightElements(frame, dataAttribute).then(async () => {
|
|
2461
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2462
|
+
// this._unhighlightElements(frame, dataAttribute)
|
|
2463
|
+
// .then(async () => {
|
|
2464
|
+
// console.log(`Unhighlighted frame dataAttribute successfully`);
|
|
2465
|
+
// })
|
|
2466
|
+
// .catch(
|
|
2467
|
+
// (e) => {}
|
|
2468
|
+
// console.error(e)
|
|
2469
|
+
// );
|
|
2470
|
+
// });
|
|
2471
|
+
// }
|
|
2472
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2473
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2474
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2475
|
+
if (element) {
|
|
2476
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2477
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2478
|
+
// await _screenshot(state, this, element);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
await _screenshot(state, this);
|
|
2482
|
+
return state.info;
|
|
2483
|
+
}
|
|
2484
|
+
catch (error) {
|
|
2485
|
+
console.error(error);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2489
|
+
}
|
|
2490
|
+
catch (e) {
|
|
2491
|
+
await _commandError(state, e, this);
|
|
2492
|
+
}
|
|
2493
|
+
finally {
|
|
2494
|
+
_commandFinally(state, this);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2498
|
+
text = unEscapeString(text);
|
|
2499
|
+
const state = {
|
|
2500
|
+
text_search: text,
|
|
2501
|
+
options,
|
|
2502
|
+
world,
|
|
2503
|
+
locate: false,
|
|
2504
|
+
scroll: false,
|
|
2505
|
+
highlight: false,
|
|
2506
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2507
|
+
text: `Verify the text '${text}' does not exist in page`,
|
|
2508
|
+
_text: `Verify the text '${text}' does not exist in page`,
|
|
2509
|
+
operation: "verifyTextNotExistInPage",
|
|
2510
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2511
|
+
};
|
|
2512
|
+
if (testForRegex(text)) {
|
|
2513
|
+
text = text.replace(/\\"/g, '"');
|
|
2514
|
+
}
|
|
2515
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2516
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2517
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2518
|
+
if (newValue !== text) {
|
|
2519
|
+
this.logger.info(text + "=" + newValue);
|
|
2520
|
+
text = newValue;
|
|
2521
|
+
}
|
|
2522
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2523
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2524
|
+
try {
|
|
2525
|
+
await _preCommand(state, this);
|
|
2526
|
+
state.info.text = text;
|
|
2527
|
+
let resultWithElementsFound = {
|
|
2528
|
+
length: null, // initial cannot be 0
|
|
2529
|
+
};
|
|
2530
|
+
while (true) {
|
|
2531
|
+
try {
|
|
2532
|
+
resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2533
|
+
}
|
|
2534
|
+
catch (error) {
|
|
2535
|
+
// ignore
|
|
2536
|
+
}
|
|
2537
|
+
if (resultWithElementsFound.length === 0) {
|
|
2538
|
+
await _screenshot(state, this);
|
|
2539
|
+
return state.info;
|
|
2540
|
+
}
|
|
2541
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2542
|
+
throw new Error(`Text ${text} found in page`);
|
|
2543
|
+
}
|
|
2544
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
catch (e) {
|
|
2548
|
+
await _commandError(state, e, this);
|
|
2549
|
+
}
|
|
2550
|
+
finally {
|
|
2551
|
+
_commandFinally(state, this);
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2555
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2556
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2557
|
+
const state = {
|
|
2558
|
+
text_search: textToVerify,
|
|
2559
|
+
options,
|
|
2560
|
+
world,
|
|
2561
|
+
locate: false,
|
|
2562
|
+
scroll: false,
|
|
2563
|
+
highlight: false,
|
|
2564
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2565
|
+
text: `Verify text with relation to another text`,
|
|
2566
|
+
_text: "Search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found",
|
|
2567
|
+
operation: "verify_text_with_relation",
|
|
2568
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2569
|
+
};
|
|
2570
|
+
const timeout = this._getFindElementTimeout(options);
|
|
2571
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2572
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2573
|
+
if (newValue !== textAnchor) {
|
|
2574
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2575
|
+
textAnchor = newValue;
|
|
2576
|
+
}
|
|
2577
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2578
|
+
if (newValue !== textToVerify) {
|
|
2579
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2580
|
+
textToVerify = newValue;
|
|
2581
|
+
}
|
|
2582
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2583
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2584
|
+
let foundAncore = false;
|
|
2585
|
+
try {
|
|
2586
|
+
await _preCommand(state, this);
|
|
2587
|
+
state.info.text = textToVerify;
|
|
2588
|
+
let resultWithElementsFound = {
|
|
2589
|
+
length: 0,
|
|
2590
|
+
};
|
|
2591
|
+
while (true) {
|
|
2592
|
+
try {
|
|
2593
|
+
resultWithElementsFound = await this.findTextInAllFrames(findDateAlternatives(textAnchor), findNumberAlternatives(textAnchor), textAnchor, state, false);
|
|
2594
|
+
}
|
|
2595
|
+
catch (error) {
|
|
2596
|
+
// ignore
|
|
2597
|
+
}
|
|
2598
|
+
if (resultWithElementsFound.length === 0) {
|
|
2599
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2600
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2601
|
+
}
|
|
2602
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2603
|
+
continue;
|
|
2604
|
+
}
|
|
2605
|
+
try {
|
|
2606
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2607
|
+
foundAncore = true;
|
|
2608
|
+
const result = resultWithElementsFound[i];
|
|
2609
|
+
const token = result.randomToken;
|
|
2610
|
+
const frame = result.frame;
|
|
2611
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2612
|
+
const climbArray1 = [];
|
|
2613
|
+
for (let i = 0; i < climb; i++) {
|
|
2614
|
+
climbArray1.push("..");
|
|
2615
|
+
}
|
|
2616
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2617
|
+
css = css + " >> " + climbXpath;
|
|
2618
|
+
const count = await frame.locator(css).count();
|
|
2619
|
+
for (let j = 0; j < count; j++) {
|
|
2620
|
+
const continer = await frame.locator(css).nth(j);
|
|
2621
|
+
const result = await this._locateElementByText(continer, textToVerify, "*:not(script, style, head)", false, true, true, {});
|
|
2622
|
+
if (result.elementCount > 0) {
|
|
2623
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2624
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2625
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2626
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
2627
|
+
// console.log(`Highlighting for vtrt while running from recorder`);
|
|
2628
|
+
// this._highlightElements(frame, dataAttribute)
|
|
2629
|
+
// .then(async () => {
|
|
2630
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2631
|
+
// this._unhighlightElements(frame, dataAttribute).then(
|
|
2632
|
+
// () => {}
|
|
2633
|
+
// console.log(`Unhighlighting vrtr in recorder is successful`)
|
|
2634
|
+
// );
|
|
2635
|
+
// })
|
|
2636
|
+
// .catch(e);
|
|
2637
|
+
// }
|
|
2638
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2639
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2640
|
+
// await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2641
|
+
// await this._unhighlightElements(frame, dataAttribute);
|
|
2642
|
+
if (element) {
|
|
2643
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2644
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2645
|
+
}
|
|
2646
|
+
await _screenshot(state, this);
|
|
2647
|
+
return state.info;
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
catch (error) {
|
|
2653
|
+
console.error(error);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2212
2657
|
}
|
|
2213
|
-
|
|
2214
|
-
|
|
2658
|
+
catch (e) {
|
|
2659
|
+
await _commandError(state, e, this);
|
|
2660
|
+
}
|
|
2661
|
+
finally {
|
|
2662
|
+
_commandFinally(state, this);
|
|
2663
|
+
}
|
|
2664
|
+
}
|
|
2665
|
+
async findRelatedTextInAllFrames(textAnchor, climb, textToVerify, params = {}, options = {}, world = null) {
|
|
2666
|
+
const frames = this.page.frames();
|
|
2667
|
+
let results = [];
|
|
2668
|
+
let ignoreCase = false;
|
|
2669
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2670
|
+
const result = await this._locateElementByText(frames[i], textAnchor, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2671
|
+
result.frame = frames[i];
|
|
2672
|
+
const climbArray = [];
|
|
2673
|
+
for (let i = 0; i < climb; i++) {
|
|
2674
|
+
climbArray.push("..");
|
|
2675
|
+
}
|
|
2676
|
+
let climbXpath = "xpath=" + climbArray.join("/");
|
|
2677
|
+
const newLocator = `[data-blinq-id-${result.randomToken}] ${climb > 0 ? ">> " + climbXpath : ""} >> internal:text=${testForRegex(textToVerify) ? textToVerify : unEscapeString(textToVerify)}`;
|
|
2678
|
+
const count = await frames[i].locator(newLocator).count();
|
|
2679
|
+
if (count > 0) {
|
|
2680
|
+
result.elementCount = count;
|
|
2681
|
+
result.locator = newLocator;
|
|
2682
|
+
results.push(result);
|
|
2683
|
+
}
|
|
2215
2684
|
}
|
|
2216
|
-
|
|
2685
|
+
// state.info.results = results;
|
|
2686
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2687
|
+
return resultWithElementsFound;
|
|
2217
2688
|
}
|
|
2218
2689
|
async visualVerification(text, options = {}, world = null) {
|
|
2219
2690
|
const startTime = Date.now();
|
|
@@ -2229,14 +2700,17 @@ class StableBrowser {
|
|
|
2229
2700
|
throw new Error("TOKEN is not set");
|
|
2230
2701
|
}
|
|
2231
2702
|
try {
|
|
2232
|
-
let serviceUrl =
|
|
2703
|
+
let serviceUrl = _getServerUrl();
|
|
2233
2704
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2234
2705
|
info.screenshotPath = screenshotPath;
|
|
2235
2706
|
const screenshot = await this.takeScreenshot();
|
|
2236
|
-
|
|
2237
|
-
method: "
|
|
2707
|
+
let request = {
|
|
2708
|
+
method: "post",
|
|
2709
|
+
maxBodyLength: Infinity,
|
|
2238
2710
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2239
2711
|
headers: {
|
|
2712
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2713
|
+
"x-source": "aaa",
|
|
2240
2714
|
"Content-Type": "application/json",
|
|
2241
2715
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2242
2716
|
},
|
|
@@ -2245,7 +2719,7 @@ class StableBrowser {
|
|
|
2245
2719
|
screenshot: screenshot,
|
|
2246
2720
|
}),
|
|
2247
2721
|
};
|
|
2248
|
-
|
|
2722
|
+
const result = await axios.request(request);
|
|
2249
2723
|
if (result.data.status !== true) {
|
|
2250
2724
|
throw new Error("Visual validation failed");
|
|
2251
2725
|
}
|
|
@@ -2265,20 +2739,22 @@ class StableBrowser {
|
|
|
2265
2739
|
info.screenshotPath = screenshotPath;
|
|
2266
2740
|
Object.assign(e, { info: info });
|
|
2267
2741
|
error = e;
|
|
2268
|
-
throw e;
|
|
2742
|
+
// throw e;
|
|
2743
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2269
2744
|
}
|
|
2270
2745
|
finally {
|
|
2271
2746
|
const endTime = Date.now();
|
|
2272
|
-
|
|
2747
|
+
_reportToWorld(world, {
|
|
2273
2748
|
type: Types.VERIFY_VISUAL,
|
|
2274
2749
|
text: "Visual verification",
|
|
2750
|
+
_text: "Visual verification of " + text,
|
|
2275
2751
|
screenshotId,
|
|
2276
2752
|
result: error
|
|
2277
2753
|
? {
|
|
2278
2754
|
status: "FAILED",
|
|
2279
2755
|
startTime,
|
|
2280
2756
|
endTime,
|
|
2281
|
-
message: error
|
|
2757
|
+
message: error?.message,
|
|
2282
2758
|
}
|
|
2283
2759
|
: {
|
|
2284
2760
|
status: "PASSED",
|
|
@@ -2310,13 +2786,14 @@ class StableBrowser {
|
|
|
2310
2786
|
this.logger.info("Table data verified");
|
|
2311
2787
|
}
|
|
2312
2788
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2313
|
-
|
|
2789
|
+
_validateSelectors(selectors);
|
|
2314
2790
|
const startTime = Date.now();
|
|
2315
2791
|
let error = null;
|
|
2316
2792
|
let screenshotId = null;
|
|
2317
2793
|
let screenshotPath = null;
|
|
2318
2794
|
const info = {};
|
|
2319
2795
|
info.log = "";
|
|
2796
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2320
2797
|
info.operation = "getTableData";
|
|
2321
2798
|
info.selectors = selectors;
|
|
2322
2799
|
try {
|
|
@@ -2332,11 +2809,12 @@ class StableBrowser {
|
|
|
2332
2809
|
info.screenshotPath = screenshotPath;
|
|
2333
2810
|
Object.assign(e, { info: info });
|
|
2334
2811
|
error = e;
|
|
2335
|
-
throw e;
|
|
2812
|
+
// throw e;
|
|
2813
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2336
2814
|
}
|
|
2337
2815
|
finally {
|
|
2338
2816
|
const endTime = Date.now();
|
|
2339
|
-
|
|
2817
|
+
_reportToWorld(world, {
|
|
2340
2818
|
element_name: selectors.element_name,
|
|
2341
2819
|
type: Types.GET_TABLE_DATA,
|
|
2342
2820
|
text: "Get table data",
|
|
@@ -2346,7 +2824,7 @@ class StableBrowser {
|
|
|
2346
2824
|
status: "FAILED",
|
|
2347
2825
|
startTime,
|
|
2348
2826
|
endTime,
|
|
2349
|
-
message: error
|
|
2827
|
+
message: error?.message,
|
|
2350
2828
|
}
|
|
2351
2829
|
: {
|
|
2352
2830
|
status: "PASSED",
|
|
@@ -2358,7 +2836,7 @@ class StableBrowser {
|
|
|
2358
2836
|
}
|
|
2359
2837
|
}
|
|
2360
2838
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2361
|
-
|
|
2839
|
+
_validateSelectors(selectors);
|
|
2362
2840
|
if (!query) {
|
|
2363
2841
|
throw new Error("query is null");
|
|
2364
2842
|
}
|
|
@@ -2391,7 +2869,7 @@ class StableBrowser {
|
|
|
2391
2869
|
info.operation = "analyzeTable";
|
|
2392
2870
|
info.selectors = selectors;
|
|
2393
2871
|
info.query = query;
|
|
2394
|
-
query =
|
|
2872
|
+
query = _fixUsingParams(query, _params);
|
|
2395
2873
|
info.query_fixed = query;
|
|
2396
2874
|
info.operator = operator;
|
|
2397
2875
|
info.value = value;
|
|
@@ -2497,11 +2975,12 @@ class StableBrowser {
|
|
|
2497
2975
|
info.screenshotPath = screenshotPath;
|
|
2498
2976
|
Object.assign(e, { info: info });
|
|
2499
2977
|
error = e;
|
|
2500
|
-
throw e;
|
|
2978
|
+
// throw e;
|
|
2979
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2501
2980
|
}
|
|
2502
2981
|
finally {
|
|
2503
2982
|
const endTime = Date.now();
|
|
2504
|
-
|
|
2983
|
+
_reportToWorld(world, {
|
|
2505
2984
|
element_name: selectors.element_name,
|
|
2506
2985
|
type: Types.ANALYZE_TABLE,
|
|
2507
2986
|
text: "Analyze table",
|
|
@@ -2511,7 +2990,7 @@ class StableBrowser {
|
|
|
2511
2990
|
status: "FAILED",
|
|
2512
2991
|
startTime,
|
|
2513
2992
|
endTime,
|
|
2514
|
-
message: error
|
|
2993
|
+
message: error?.message,
|
|
2515
2994
|
}
|
|
2516
2995
|
: {
|
|
2517
2996
|
status: "PASSED",
|
|
@@ -2523,27 +3002,7 @@ class StableBrowser {
|
|
|
2523
3002
|
}
|
|
2524
3003
|
}
|
|
2525
3004
|
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;
|
|
3005
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2547
3006
|
}
|
|
2548
3007
|
_getLoadTimeout(options) {
|
|
2549
3008
|
let timeout = 15000;
|
|
@@ -2555,6 +3014,32 @@ class StableBrowser {
|
|
|
2555
3014
|
}
|
|
2556
3015
|
return timeout;
|
|
2557
3016
|
}
|
|
3017
|
+
_getFindElementTimeout(options) {
|
|
3018
|
+
if (options && options.timeout) {
|
|
3019
|
+
return options.timeout;
|
|
3020
|
+
}
|
|
3021
|
+
if (this.configuration.find_element_timeout) {
|
|
3022
|
+
return this.configuration.find_element_timeout;
|
|
3023
|
+
}
|
|
3024
|
+
return 30000;
|
|
3025
|
+
}
|
|
3026
|
+
async saveStoreState(path = null, world = null) {
|
|
3027
|
+
const storageState = await this.page.context().storageState();
|
|
3028
|
+
//const testDataFile = _getDataFile(world, this.context, this);
|
|
3029
|
+
if (path) {
|
|
3030
|
+
// save { storageState: storageState } into the path
|
|
3031
|
+
fs.writeFileSync(path, JSON.stringify({ storageState: storageState }, null, 2));
|
|
3032
|
+
}
|
|
3033
|
+
else {
|
|
3034
|
+
await this.setTestData({ storageState: storageState }, world);
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
async restoreSaveState(path = null, world = null) {
|
|
3038
|
+
await refreshBrowser(this, path, world);
|
|
3039
|
+
this.registerEventListeners(this.context);
|
|
3040
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
3041
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
3042
|
+
}
|
|
2558
3043
|
async waitForPageLoad(options = {}, world = null) {
|
|
2559
3044
|
let timeout = this._getLoadTimeout(options);
|
|
2560
3045
|
const promiseArray = [];
|
|
@@ -2580,13 +3065,13 @@ class StableBrowser {
|
|
|
2580
3065
|
}
|
|
2581
3066
|
catch (e) {
|
|
2582
3067
|
if (e.label === "networkidle") {
|
|
2583
|
-
console.log("
|
|
3068
|
+
console.log("waited for the network to be idle timeout");
|
|
2584
3069
|
}
|
|
2585
3070
|
else if (e.label === "load") {
|
|
2586
|
-
console.log("
|
|
3071
|
+
console.log("waited for the load timeout");
|
|
2587
3072
|
}
|
|
2588
3073
|
else if (e.label === "domcontentloaded") {
|
|
2589
|
-
console.log("
|
|
3074
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2590
3075
|
}
|
|
2591
3076
|
console.log(".");
|
|
2592
3077
|
}
|
|
@@ -2594,7 +3079,7 @@ class StableBrowser {
|
|
|
2594
3079
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2595
3080
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2596
3081
|
const endTime = Date.now();
|
|
2597
|
-
|
|
3082
|
+
_reportToWorld(world, {
|
|
2598
3083
|
type: Types.GET_PAGE_STATUS,
|
|
2599
3084
|
text: "Wait for page load",
|
|
2600
3085
|
screenshotId,
|
|
@@ -2603,7 +3088,7 @@ class StableBrowser {
|
|
|
2603
3088
|
status: "FAILED",
|
|
2604
3089
|
startTime,
|
|
2605
3090
|
endTime,
|
|
2606
|
-
message: error
|
|
3091
|
+
message: error?.message,
|
|
2607
3092
|
}
|
|
2608
3093
|
: {
|
|
2609
3094
|
status: "PASSED",
|
|
@@ -2614,41 +3099,123 @@ class StableBrowser {
|
|
|
2614
3099
|
}
|
|
2615
3100
|
}
|
|
2616
3101
|
async closePage(options = {}, world = null) {
|
|
2617
|
-
const
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
3102
|
+
const state = {
|
|
3103
|
+
options,
|
|
3104
|
+
world,
|
|
3105
|
+
locate: false,
|
|
3106
|
+
scroll: false,
|
|
3107
|
+
highlight: false,
|
|
3108
|
+
type: Types.CLOSE_PAGE,
|
|
3109
|
+
text: `Close page`,
|
|
3110
|
+
_text: `Close the page`,
|
|
3111
|
+
operation: "closePage",
|
|
3112
|
+
log: "***** close page *****\n",
|
|
3113
|
+
throwError: false,
|
|
3114
|
+
};
|
|
2622
3115
|
try {
|
|
3116
|
+
await _preCommand(state, this);
|
|
2623
3117
|
await this.page.close();
|
|
2624
3118
|
}
|
|
2625
3119
|
catch (e) {
|
|
2626
3120
|
console.log(".");
|
|
3121
|
+
await _commandError(state, e, this);
|
|
2627
3122
|
}
|
|
2628
3123
|
finally {
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
3124
|
+
_commandFinally(state, this);
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
async tableCellOperation(headerText, rowText, options, _params, world = null) {
|
|
3128
|
+
let operation = null;
|
|
3129
|
+
if (!options || !options.operation) {
|
|
3130
|
+
throw new Error("operation is not defined");
|
|
3131
|
+
}
|
|
3132
|
+
operation = options.operation;
|
|
3133
|
+
// validate operation is one of the supported operations
|
|
3134
|
+
if (operation != "click" && operation != "hover+click") {
|
|
3135
|
+
throw new Error("operation is not supported");
|
|
3136
|
+
}
|
|
3137
|
+
const state = {
|
|
3138
|
+
options,
|
|
3139
|
+
world,
|
|
3140
|
+
locate: false,
|
|
3141
|
+
scroll: false,
|
|
3142
|
+
highlight: false,
|
|
3143
|
+
type: Types.TABLE_OPERATION,
|
|
3144
|
+
text: `Table operation`,
|
|
3145
|
+
_text: `Table ${operation} operation`,
|
|
3146
|
+
operation: operation,
|
|
3147
|
+
log: "***** Table operation *****\n",
|
|
3148
|
+
};
|
|
3149
|
+
const timeout = this._getFindElementTimeout(options);
|
|
3150
|
+
try {
|
|
3151
|
+
await _preCommand(state, this);
|
|
3152
|
+
const start = Date.now();
|
|
3153
|
+
let cellArea = null;
|
|
3154
|
+
while (true) {
|
|
3155
|
+
try {
|
|
3156
|
+
cellArea = await _findCellArea(headerText, rowText, this, state);
|
|
3157
|
+
if (cellArea) {
|
|
3158
|
+
break;
|
|
2642
3159
|
}
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
3160
|
+
}
|
|
3161
|
+
catch (e) {
|
|
3162
|
+
// ignore
|
|
3163
|
+
}
|
|
3164
|
+
if (Date.now() - start > timeout) {
|
|
3165
|
+
throw new Error(`Cell not found in table`);
|
|
3166
|
+
}
|
|
3167
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3168
|
+
}
|
|
3169
|
+
switch (operation) {
|
|
3170
|
+
case "click":
|
|
3171
|
+
if (!options.css) {
|
|
3172
|
+
// will click in the center of the cell
|
|
3173
|
+
let xOffset = 0;
|
|
3174
|
+
let yOffset = 0;
|
|
3175
|
+
if (options.xOffset) {
|
|
3176
|
+
xOffset = options.xOffset;
|
|
3177
|
+
}
|
|
3178
|
+
if (options.yOffset) {
|
|
3179
|
+
yOffset = options.yOffset;
|
|
3180
|
+
}
|
|
3181
|
+
await this.page.mouse.click(cellArea.x + cellArea.width / 2 + xOffset, cellArea.y + cellArea.height / 2 + yOffset);
|
|
3182
|
+
}
|
|
3183
|
+
else {
|
|
3184
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3185
|
+
if (results.length === 0) {
|
|
3186
|
+
throw new Error(`Element not found in cell area`);
|
|
3187
|
+
}
|
|
3188
|
+
state.element = results[0];
|
|
3189
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
3190
|
+
}
|
|
3191
|
+
break;
|
|
3192
|
+
case "hover+click":
|
|
3193
|
+
if (!options.css) {
|
|
3194
|
+
throw new Error("css is not defined");
|
|
3195
|
+
}
|
|
3196
|
+
const results = await findElementsInArea(options.css, cellArea, this, options);
|
|
3197
|
+
if (results.length === 0) {
|
|
3198
|
+
throw new Error(`Element not found in cell area`);
|
|
3199
|
+
}
|
|
3200
|
+
state.element = results[0];
|
|
3201
|
+
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3202
|
+
break;
|
|
3203
|
+
default:
|
|
3204
|
+
throw new Error("operation is not supported");
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
catch (e) {
|
|
3208
|
+
await _commandError(state, e, this);
|
|
3209
|
+
}
|
|
3210
|
+
finally {
|
|
3211
|
+
_commandFinally(state, this);
|
|
2650
3212
|
}
|
|
2651
3213
|
}
|
|
3214
|
+
saveTestDataAsGlobal(options, world) {
|
|
3215
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
3216
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
3217
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
3218
|
+
}
|
|
2652
3219
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2653
3220
|
const startTime = Date.now();
|
|
2654
3221
|
let error = null;
|
|
@@ -2666,21 +3233,23 @@ class StableBrowser {
|
|
|
2666
3233
|
}
|
|
2667
3234
|
catch (e) {
|
|
2668
3235
|
console.log(".");
|
|
3236
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2669
3237
|
}
|
|
2670
3238
|
finally {
|
|
2671
3239
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2672
3240
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2673
3241
|
const endTime = Date.now();
|
|
2674
|
-
|
|
3242
|
+
_reportToWorld(world, {
|
|
2675
3243
|
type: Types.SET_VIEWPORT,
|
|
2676
3244
|
text: "set viewport size to " + width + "x" + hight,
|
|
3245
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2677
3246
|
screenshotId,
|
|
2678
3247
|
result: error
|
|
2679
3248
|
? {
|
|
2680
3249
|
status: "FAILED",
|
|
2681
3250
|
startTime,
|
|
2682
3251
|
endTime,
|
|
2683
|
-
message: error
|
|
3252
|
+
message: error?.message,
|
|
2684
3253
|
}
|
|
2685
3254
|
: {
|
|
2686
3255
|
status: "PASSED",
|
|
@@ -2702,12 +3271,13 @@ class StableBrowser {
|
|
|
2702
3271
|
}
|
|
2703
3272
|
catch (e) {
|
|
2704
3273
|
console.log(".");
|
|
3274
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2705
3275
|
}
|
|
2706
3276
|
finally {
|
|
2707
3277
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2708
3278
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2709
3279
|
const endTime = Date.now();
|
|
2710
|
-
|
|
3280
|
+
_reportToWorld(world, {
|
|
2711
3281
|
type: Types.GET_PAGE_STATUS,
|
|
2712
3282
|
text: "page relaod",
|
|
2713
3283
|
screenshotId,
|
|
@@ -2716,7 +3286,7 @@ class StableBrowser {
|
|
|
2716
3286
|
status: "FAILED",
|
|
2717
3287
|
startTime,
|
|
2718
3288
|
endTime,
|
|
2719
|
-
message: error
|
|
3289
|
+
message: error?.message,
|
|
2720
3290
|
}
|
|
2721
3291
|
: {
|
|
2722
3292
|
status: "PASSED",
|
|
@@ -2729,21 +3299,114 @@ class StableBrowser {
|
|
|
2729
3299
|
}
|
|
2730
3300
|
async scrollIfNeeded(element, info) {
|
|
2731
3301
|
try {
|
|
2732
|
-
await element.scrollIntoViewIfNeeded(
|
|
3302
|
+
await element.scrollIntoViewIfNeeded({
|
|
3303
|
+
timeout: 2000,
|
|
3304
|
+
});
|
|
2733
3305
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2734
3306
|
if (info) {
|
|
2735
|
-
info.box = await element.boundingBox(
|
|
3307
|
+
info.box = await element.boundingBox({
|
|
3308
|
+
timeout: 1000,
|
|
3309
|
+
});
|
|
2736
3310
|
}
|
|
2737
3311
|
}
|
|
2738
3312
|
catch (e) {
|
|
2739
|
-
console.log("
|
|
3313
|
+
console.log("#-#");
|
|
2740
3314
|
}
|
|
2741
3315
|
}
|
|
2742
|
-
|
|
2743
|
-
if (
|
|
2744
|
-
|
|
3316
|
+
async beforeStep(world, step) {
|
|
3317
|
+
if (this.stepIndex === undefined) {
|
|
3318
|
+
this.stepIndex = 0;
|
|
3319
|
+
}
|
|
3320
|
+
else {
|
|
3321
|
+
this.stepIndex++;
|
|
3322
|
+
}
|
|
3323
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
3324
|
+
this.stepName = step.pickleStep.text;
|
|
3325
|
+
this.logger.info("step: " + this.stepName);
|
|
3326
|
+
}
|
|
3327
|
+
else if (step && step.text) {
|
|
3328
|
+
this.stepName = step.text;
|
|
3329
|
+
}
|
|
3330
|
+
else {
|
|
3331
|
+
this.stepName = "step " + this.stepIndex;
|
|
3332
|
+
}
|
|
3333
|
+
if (this.context) {
|
|
3334
|
+
this.context.examplesRow = extractStepExampleParameters(step);
|
|
3335
|
+
}
|
|
3336
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3337
|
+
if (this.context.browserObject.context) {
|
|
3338
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
3342
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
3343
|
+
// check if @global_test_data tag is present
|
|
3344
|
+
if (this.tags.includes("@global_test_data")) {
|
|
3345
|
+
this.saveTestDataAsGlobal({}, world);
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
if (this.initSnapshotTaken === false) {
|
|
3349
|
+
this.initSnapshotTaken = true;
|
|
3350
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3351
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3352
|
+
if (snapshot) {
|
|
3353
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
async getAriaSnapshot() {
|
|
3359
|
+
try {
|
|
3360
|
+
// find the page url
|
|
3361
|
+
const url = await this.page.url();
|
|
3362
|
+
// extract the path from the url
|
|
3363
|
+
const path = new URL(url).pathname;
|
|
3364
|
+
// get the page title
|
|
3365
|
+
const title = await this.page.title();
|
|
3366
|
+
// go over other frams
|
|
3367
|
+
const frames = this.page.frames();
|
|
3368
|
+
const snapshots = [];
|
|
3369
|
+
const content = [`- path: ${path}`, `- title: ${title}`];
|
|
3370
|
+
const timeout = this.configuration.ariaSnapshotTimeout ? this.configuration.ariaSnapshotTimeout : 3000;
|
|
3371
|
+
for (let i = 0; i < frames.length; i++) {
|
|
3372
|
+
content.push(`- frame: ${i}`);
|
|
3373
|
+
const frame = frames[i];
|
|
3374
|
+
const snapshot = await frame.locator("body").ariaSnapshot({ timeout });
|
|
3375
|
+
content.push(snapshot);
|
|
3376
|
+
}
|
|
3377
|
+
return content.join("\n");
|
|
3378
|
+
}
|
|
3379
|
+
catch (e) {
|
|
3380
|
+
console.error(e);
|
|
3381
|
+
}
|
|
3382
|
+
return null;
|
|
3383
|
+
}
|
|
3384
|
+
async afterStep(world, step) {
|
|
3385
|
+
this.stepName = null;
|
|
3386
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
3387
|
+
if (this.context.browserObject.context) {
|
|
3388
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
3389
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
3390
|
+
});
|
|
3391
|
+
if (world && world.attach) {
|
|
3392
|
+
await world.attach(JSON.stringify({
|
|
3393
|
+
type: "trace",
|
|
3394
|
+
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
3395
|
+
}), "application/json+trace");
|
|
3396
|
+
}
|
|
3397
|
+
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
if (this.context) {
|
|
3401
|
+
this.context.examplesRow = null;
|
|
3402
|
+
}
|
|
3403
|
+
if (world && world.attach && !process.env.DISABLE_SNAPSHOT) {
|
|
3404
|
+
const snapshot = await this.getAriaSnapshot();
|
|
3405
|
+
if (snapshot) {
|
|
3406
|
+
const obj = {};
|
|
3407
|
+
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-after");
|
|
3408
|
+
}
|
|
2745
3409
|
}
|
|
2746
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2747
3410
|
}
|
|
2748
3411
|
}
|
|
2749
3412
|
function createTimedPromise(promise, label) {
|
|
@@ -2751,151 +3414,5 @@ function createTimedPromise(promise, label) {
|
|
|
2751
3414
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2752
3415
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2753
3416
|
}
|
|
2754
|
-
const KEYBOARD_EVENTS = [
|
|
2755
|
-
"ALT",
|
|
2756
|
-
"AltGraph",
|
|
2757
|
-
"CapsLock",
|
|
2758
|
-
"Control",
|
|
2759
|
-
"Fn",
|
|
2760
|
-
"FnLock",
|
|
2761
|
-
"Hyper",
|
|
2762
|
-
"Meta",
|
|
2763
|
-
"NumLock",
|
|
2764
|
-
"ScrollLock",
|
|
2765
|
-
"Shift",
|
|
2766
|
-
"Super",
|
|
2767
|
-
"Symbol",
|
|
2768
|
-
"SymbolLock",
|
|
2769
|
-
"Enter",
|
|
2770
|
-
"Tab",
|
|
2771
|
-
"ArrowDown",
|
|
2772
|
-
"ArrowLeft",
|
|
2773
|
-
"ArrowRight",
|
|
2774
|
-
"ArrowUp",
|
|
2775
|
-
"End",
|
|
2776
|
-
"Home",
|
|
2777
|
-
"PageDown",
|
|
2778
|
-
"PageUp",
|
|
2779
|
-
"Backspace",
|
|
2780
|
-
"Clear",
|
|
2781
|
-
"Copy",
|
|
2782
|
-
"CrSel",
|
|
2783
|
-
"Cut",
|
|
2784
|
-
"Delete",
|
|
2785
|
-
"EraseEof",
|
|
2786
|
-
"ExSel",
|
|
2787
|
-
"Insert",
|
|
2788
|
-
"Paste",
|
|
2789
|
-
"Redo",
|
|
2790
|
-
"Undo",
|
|
2791
|
-
"Accept",
|
|
2792
|
-
"Again",
|
|
2793
|
-
"Attn",
|
|
2794
|
-
"Cancel",
|
|
2795
|
-
"ContextMenu",
|
|
2796
|
-
"Escape",
|
|
2797
|
-
"Execute",
|
|
2798
|
-
"Find",
|
|
2799
|
-
"Finish",
|
|
2800
|
-
"Help",
|
|
2801
|
-
"Pause",
|
|
2802
|
-
"Play",
|
|
2803
|
-
"Props",
|
|
2804
|
-
"Select",
|
|
2805
|
-
"ZoomIn",
|
|
2806
|
-
"ZoomOut",
|
|
2807
|
-
"BrightnessDown",
|
|
2808
|
-
"BrightnessUp",
|
|
2809
|
-
"Eject",
|
|
2810
|
-
"LogOff",
|
|
2811
|
-
"Power",
|
|
2812
|
-
"PowerOff",
|
|
2813
|
-
"PrintScreen",
|
|
2814
|
-
"Hibernate",
|
|
2815
|
-
"Standby",
|
|
2816
|
-
"WakeUp",
|
|
2817
|
-
"AllCandidates",
|
|
2818
|
-
"Alphanumeric",
|
|
2819
|
-
"CodeInput",
|
|
2820
|
-
"Compose",
|
|
2821
|
-
"Convert",
|
|
2822
|
-
"Dead",
|
|
2823
|
-
"FinalMode",
|
|
2824
|
-
"GroupFirst",
|
|
2825
|
-
"GroupLast",
|
|
2826
|
-
"GroupNext",
|
|
2827
|
-
"GroupPrevious",
|
|
2828
|
-
"ModeChange",
|
|
2829
|
-
"NextCandidate",
|
|
2830
|
-
"NonConvert",
|
|
2831
|
-
"PreviousCandidate",
|
|
2832
|
-
"Process",
|
|
2833
|
-
"SingleCandidate",
|
|
2834
|
-
"HangulMode",
|
|
2835
|
-
"HanjaMode",
|
|
2836
|
-
"JunjaMode",
|
|
2837
|
-
"Eisu",
|
|
2838
|
-
"Hankaku",
|
|
2839
|
-
"Hiragana",
|
|
2840
|
-
"HiraganaKatakana",
|
|
2841
|
-
"KanaMode",
|
|
2842
|
-
"KanjiMode",
|
|
2843
|
-
"Katakana",
|
|
2844
|
-
"Romaji",
|
|
2845
|
-
"Zenkaku",
|
|
2846
|
-
"ZenkakuHanaku",
|
|
2847
|
-
"F1",
|
|
2848
|
-
"F2",
|
|
2849
|
-
"F3",
|
|
2850
|
-
"F4",
|
|
2851
|
-
"F5",
|
|
2852
|
-
"F6",
|
|
2853
|
-
"F7",
|
|
2854
|
-
"F8",
|
|
2855
|
-
"F9",
|
|
2856
|
-
"F10",
|
|
2857
|
-
"F11",
|
|
2858
|
-
"F12",
|
|
2859
|
-
"Soft1",
|
|
2860
|
-
"Soft2",
|
|
2861
|
-
"Soft3",
|
|
2862
|
-
"Soft4",
|
|
2863
|
-
"ChannelDown",
|
|
2864
|
-
"ChannelUp",
|
|
2865
|
-
"Close",
|
|
2866
|
-
"MailForward",
|
|
2867
|
-
"MailReply",
|
|
2868
|
-
"MailSend",
|
|
2869
|
-
"MediaFastForward",
|
|
2870
|
-
"MediaPause",
|
|
2871
|
-
"MediaPlay",
|
|
2872
|
-
"MediaPlayPause",
|
|
2873
|
-
"MediaRecord",
|
|
2874
|
-
"MediaRewind",
|
|
2875
|
-
"MediaStop",
|
|
2876
|
-
"MediaTrackNext",
|
|
2877
|
-
"MediaTrackPrevious",
|
|
2878
|
-
"AudioBalanceLeft",
|
|
2879
|
-
"AudioBalanceRight",
|
|
2880
|
-
"AudioBassBoostDown",
|
|
2881
|
-
"AudioBassBoostToggle",
|
|
2882
|
-
"AudioBassBoostUp",
|
|
2883
|
-
"AudioFaderFront",
|
|
2884
|
-
"AudioFaderRear",
|
|
2885
|
-
"AudioSurroundModeNext",
|
|
2886
|
-
"AudioTrebleDown",
|
|
2887
|
-
"AudioTrebleUp",
|
|
2888
|
-
"AudioVolumeDown",
|
|
2889
|
-
"AudioVolumeMute",
|
|
2890
|
-
"AudioVolumeUp",
|
|
2891
|
-
"MicrophoneToggle",
|
|
2892
|
-
"MicrophoneVolumeDown",
|
|
2893
|
-
"MicrophoneVolumeMute",
|
|
2894
|
-
"MicrophoneVolumeUp",
|
|
2895
|
-
"TV",
|
|
2896
|
-
"TV3DMode",
|
|
2897
|
-
"TVAntennaCable",
|
|
2898
|
-
"TVAudioDescription",
|
|
2899
|
-
];
|
|
2900
3417
|
export { StableBrowser };
|
|
2901
3418
|
//# sourceMappingURL=stable_browser.js.map
|