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