automation_model 1.0.419-dev → 1.0.419
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 +122 -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 +1677 -1270
- 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,7 +314,7 @@ 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
|
}
|
|
@@ -262,192 +335,181 @@ class StableBrowser {
|
|
|
262
335
|
return locatorReturn;
|
|
263
336
|
}
|
|
264
337
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
265
|
-
|
|
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);
|
|
266
342
|
if (result.elementCount === 0) {
|
|
267
343
|
return;
|
|
268
344
|
}
|
|
269
|
-
let textElementCss = "[data-blinq-id
|
|
345
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
270
346
|
// css climb to parent element
|
|
271
347
|
const climbArray = [];
|
|
272
348
|
for (let i = 0; i < climb; i++) {
|
|
273
349
|
climbArray.push("..");
|
|
274
350
|
}
|
|
275
351
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
return
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
314
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
315
|
-
if (!shadowElement) {
|
|
316
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
317
|
-
continue;
|
|
318
|
-
}
|
|
319
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
320
|
-
elements = elements.concat(shadowElements);
|
|
321
|
-
}
|
|
322
|
-
let randomToken = null;
|
|
323
|
-
const foundElements = [];
|
|
324
|
-
if (regex) {
|
|
325
|
-
let regexpSearch = new RegExp(text, "im");
|
|
326
|
-
for (let i = 0; i < elements.length; i++) {
|
|
327
|
-
const element = elements[i];
|
|
328
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
329
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
330
|
-
foundElements.push(element);
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
text = text.trim();
|
|
336
|
-
for (let i = 0; i < elements.length; i++) {
|
|
337
|
-
const element = elements[i];
|
|
338
|
-
if (partial) {
|
|
339
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
340
|
-
(element.value && element.value.includes(text))) {
|
|
341
|
-
foundElements.push(element);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
346
|
-
(element.value && element.value === text)) {
|
|
347
|
-
foundElements.push(element);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
let noChildElements = [];
|
|
353
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
354
|
-
let element = foundElements[i];
|
|
355
|
-
let hasChild = false;
|
|
356
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
357
|
-
if (i === j) {
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
if (isParent(element, foundElements[j])) {
|
|
361
|
-
hasChild = true;
|
|
362
|
-
break;
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
if (!hasChild) {
|
|
366
|
-
noChildElements.push(element);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
let elementCount = 0;
|
|
370
|
-
if (noChildElements.length > 0) {
|
|
371
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
372
|
-
if (randomToken === null) {
|
|
373
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
374
|
-
}
|
|
375
|
-
let element = noChildElements[i];
|
|
376
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
377
|
-
elementCount++;
|
|
378
|
-
}
|
|
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;
|
|
379
389
|
}
|
|
380
|
-
|
|
381
|
-
}
|
|
390
|
+
tagCount++;
|
|
391
|
+
}
|
|
392
|
+
return { elementCount: tagCount, randomToken };
|
|
382
393
|
}
|
|
383
|
-
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
|
+
}
|
|
384
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
|
+
}
|
|
385
414
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
386
415
|
let locator = null;
|
|
387
416
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
388
|
-
|
|
417
|
+
const replacedText = await this._replaceWithLocalData(locatorSearch.text, this.world);
|
|
418
|
+
let locatorString = await this._locateElmentByTextClimbCss(scope, replacedText, locatorSearch.climb, locatorSearch.css, _params);
|
|
389
419
|
if (!locatorString) {
|
|
420
|
+
info.failCause.textNotFound = true;
|
|
421
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${locatorSearch.text}`;
|
|
390
422
|
return;
|
|
391
423
|
}
|
|
392
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
424
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
393
425
|
}
|
|
394
426
|
else if (locatorSearch.text) {
|
|
395
|
-
let
|
|
427
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
428
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
396
429
|
if (result.elementCount === 0) {
|
|
430
|
+
info.failCause.textNotFound = true;
|
|
431
|
+
info.failCause.lastError = `failed to locate ${formatElementName(element_name)} by text: ${text}`;
|
|
397
432
|
return;
|
|
398
433
|
}
|
|
399
|
-
locatorSearch.css = "[data-blinq-id
|
|
434
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
400
435
|
if (locatorSearch.childCss) {
|
|
401
436
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
402
437
|
}
|
|
403
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
438
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
404
439
|
}
|
|
405
440
|
else {
|
|
406
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
441
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
407
442
|
}
|
|
408
443
|
// let cssHref = false;
|
|
409
444
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
410
445
|
// cssHref = true;
|
|
411
446
|
// }
|
|
412
447
|
let count = await locator.count();
|
|
448
|
+
if (count > 0 && !info.failCause.count) {
|
|
449
|
+
info.failCause.count = count;
|
|
450
|
+
}
|
|
413
451
|
//info.log += "total elements found " + count + "\n";
|
|
414
452
|
//let visibleCount = 0;
|
|
415
453
|
let visibleLocator = null;
|
|
416
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
454
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
417
455
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
456
|
+
if (info.locatorLog) {
|
|
457
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
458
|
+
}
|
|
418
459
|
return;
|
|
419
460
|
}
|
|
461
|
+
if (info.locatorLog && count === 0) {
|
|
462
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
463
|
+
}
|
|
420
464
|
for (let j = 0; j < count; j++) {
|
|
421
465
|
let visible = await locator.nth(j).isVisible();
|
|
422
466
|
const enabled = await locator.nth(j).isEnabled();
|
|
423
467
|
if (!visibleOnly) {
|
|
424
468
|
visible = true;
|
|
425
469
|
}
|
|
426
|
-
if (visible && enabled) {
|
|
470
|
+
if (visible && (allowDisabled || enabled)) {
|
|
427
471
|
foundLocators.push(locator.nth(j));
|
|
472
|
+
if (info.locatorLog) {
|
|
473
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
474
|
+
}
|
|
428
475
|
}
|
|
429
476
|
else {
|
|
477
|
+
info.failCause.visible = visible;
|
|
478
|
+
info.failCause.enabled = enabled;
|
|
430
479
|
if (!info.printMessages) {
|
|
431
480
|
info.printMessages = {};
|
|
432
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
|
+
}
|
|
433
490
|
if (!info.printMessages[j.toString()]) {
|
|
434
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
491
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
435
492
|
info.printMessages[j.toString()] = true;
|
|
436
493
|
}
|
|
437
494
|
}
|
|
438
495
|
}
|
|
439
496
|
}
|
|
440
497
|
async closeUnexpectedPopups(info, _params) {
|
|
498
|
+
if (!info) {
|
|
499
|
+
info = {};
|
|
500
|
+
info.failCause = {};
|
|
501
|
+
info.log = "";
|
|
502
|
+
}
|
|
441
503
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
442
504
|
if (!info) {
|
|
443
505
|
info = {};
|
|
444
506
|
}
|
|
445
|
-
info.log += "scan for popup handlers" + "\n";
|
|
507
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
446
508
|
const handlerGroup = [];
|
|
447
509
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
448
510
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
449
511
|
}
|
|
450
|
-
const scopes =
|
|
512
|
+
const scopes = this.page.frames().filter((frame) => frame.url() !== "about:blank");
|
|
451
513
|
let result = null;
|
|
452
514
|
let scope = null;
|
|
453
515
|
for (let i = 0; i < scopes.length; i++) {
|
|
@@ -469,42 +531,104 @@ class StableBrowser {
|
|
|
469
531
|
}
|
|
470
532
|
if (result.foundElements.length > 0) {
|
|
471
533
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
472
|
-
|
|
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
|
+
}
|
|
473
549
|
return { rerun: true };
|
|
474
550
|
}
|
|
475
551
|
}
|
|
476
552
|
}
|
|
477
553
|
return { rerun: false };
|
|
478
554
|
}
|
|
479
|
-
async _locate(selectors, info, _params, timeout =
|
|
555
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
556
|
+
if (!timeout) {
|
|
557
|
+
timeout = 30000;
|
|
558
|
+
}
|
|
480
559
|
for (let i = 0; i < 3; i++) {
|
|
481
|
-
info.log += "attempt " + i + ":
|
|
560
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
482
561
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
483
562
|
let selector = selectors.locators[j];
|
|
484
563
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
485
564
|
}
|
|
486
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
565
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
487
566
|
if (!element.rerun) {
|
|
488
|
-
|
|
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;
|
|
489
577
|
}
|
|
490
578
|
}
|
|
491
579
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
492
580
|
}
|
|
493
|
-
async
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
581
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
582
|
+
if (!info) {
|
|
583
|
+
info = {};
|
|
584
|
+
info.failCause = {};
|
|
585
|
+
info.log = "";
|
|
586
|
+
}
|
|
587
|
+
let startTime = Date.now();
|
|
499
588
|
let scope = this.page;
|
|
589
|
+
if (selectors.frame) {
|
|
590
|
+
return selectors.frame;
|
|
591
|
+
}
|
|
500
592
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
501
|
-
|
|
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;
|
|
502
619
|
while (true) {
|
|
503
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
|
+
}
|
|
504
627
|
if (selectors.frameLocators) {
|
|
505
628
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
506
629
|
let frameLocator = selectors.frameLocators[i];
|
|
507
630
|
if (frameLocator.css) {
|
|
631
|
+
fLocator = frameLocator.css;
|
|
508
632
|
scope = scope.frameLocator(frameLocator.css);
|
|
509
633
|
frameFound = true;
|
|
510
634
|
break;
|
|
@@ -512,20 +636,55 @@ class StableBrowser {
|
|
|
512
636
|
}
|
|
513
637
|
}
|
|
514
638
|
if (!frameFound && selectors.iframe_src) {
|
|
639
|
+
fLocator = selectors.iframe_src;
|
|
515
640
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
516
641
|
}
|
|
517
642
|
if (!scope) {
|
|
518
|
-
info
|
|
519
|
-
|
|
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}"`;
|
|
520
650
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
521
651
|
}
|
|
522
652
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
523
653
|
}
|
|
524
654
|
else {
|
|
655
|
+
if (info && info.locatorLog) {
|
|
656
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
657
|
+
}
|
|
525
658
|
break;
|
|
526
659
|
}
|
|
527
660
|
}
|
|
528
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);
|
|
529
688
|
let selectorsLocators = null;
|
|
530
689
|
selectorsLocators = selectors.locators;
|
|
531
690
|
// group selectors by priority
|
|
@@ -561,17 +720,17 @@ class StableBrowser {
|
|
|
561
720
|
}
|
|
562
721
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
563
722
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
564
|
-
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);
|
|
565
724
|
if (result.foundElements.length === 0) {
|
|
566
725
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
567
|
-
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);
|
|
568
727
|
}
|
|
569
728
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
570
|
-
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);
|
|
571
730
|
}
|
|
572
731
|
else {
|
|
573
732
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
574
|
-
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);
|
|
575
734
|
}
|
|
576
735
|
}
|
|
577
736
|
let foundElements = result.foundElements;
|
|
@@ -612,24 +771,38 @@ class StableBrowser {
|
|
|
612
771
|
return maxCountElement.locator;
|
|
613
772
|
}
|
|
614
773
|
}
|
|
615
|
-
if (
|
|
774
|
+
if (Date.now() - startTime > timeout) {
|
|
616
775
|
break;
|
|
617
776
|
}
|
|
618
|
-
if (
|
|
619
|
-
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";
|
|
620
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
|
+
}
|
|
621
784
|
}
|
|
622
|
-
if (
|
|
623
|
-
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";
|
|
624
787
|
visibleOnly = false;
|
|
625
788
|
}
|
|
626
789
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
627
790
|
}
|
|
628
791
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
629
|
-
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
|
+
}
|
|
630
803
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
631
804
|
}
|
|
632
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
805
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false, element_name) {
|
|
633
806
|
let foundElements = [];
|
|
634
807
|
const result = {
|
|
635
808
|
foundElements: foundElements,
|
|
@@ -637,14 +810,15 @@ class StableBrowser {
|
|
|
637
810
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
638
811
|
let foundLocators = [];
|
|
639
812
|
try {
|
|
640
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
813
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled, element_name);
|
|
641
814
|
}
|
|
642
815
|
catch (e) {
|
|
643
|
-
this
|
|
644
|
-
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);
|
|
645
819
|
foundLocators = [];
|
|
646
820
|
try {
|
|
647
|
-
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);
|
|
648
822
|
}
|
|
649
823
|
catch (e) {
|
|
650
824
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -658,90 +832,228 @@ class StableBrowser {
|
|
|
658
832
|
});
|
|
659
833
|
result.locatorIndex = i;
|
|
660
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
|
+
}
|
|
661
872
|
}
|
|
662
873
|
return result;
|
|
663
874
|
}
|
|
664
|
-
async
|
|
665
|
-
|
|
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);
|
|
666
889
|
const startTime = Date.now();
|
|
667
|
-
|
|
668
|
-
|
|
890
|
+
let timeout = 30000;
|
|
891
|
+
if (options && options.timeout) {
|
|
892
|
+
timeout = options.timeout;
|
|
669
893
|
}
|
|
670
|
-
|
|
671
|
-
info.log = "***** click on " + selectors.element_name + " *****\n";
|
|
672
|
-
info.operation = "click";
|
|
673
|
-
info.selectors = selectors;
|
|
674
|
-
let error = null;
|
|
675
|
-
let screenshotId = null;
|
|
676
|
-
let screenshotPath = null;
|
|
677
|
-
try {
|
|
678
|
-
let element = await this._locate(selectors, info, _params);
|
|
679
|
-
await this.scrollIfNeeded(element, info);
|
|
680
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
894
|
+
while (true) {
|
|
681
895
|
try {
|
|
682
|
-
await this.
|
|
683
|
-
|
|
684
|
-
|
|
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
|
+
}
|
|
685
909
|
}
|
|
686
910
|
catch (e) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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
|
+
}
|
|
692
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);
|
|
693
988
|
await this.waitForPageLoad();
|
|
694
|
-
return info;
|
|
989
|
+
return state.info;
|
|
695
990
|
}
|
|
696
991
|
catch (e) {
|
|
697
|
-
|
|
698
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
699
|
-
info.screenshotPath = screenshotPath;
|
|
700
|
-
Object.assign(e, { info: info });
|
|
701
|
-
error = e;
|
|
702
|
-
throw e;
|
|
992
|
+
await _commandError(state, e, this);
|
|
703
993
|
}
|
|
704
994
|
finally {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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);
|
|
725
1027
|
}
|
|
1028
|
+
return found;
|
|
726
1029
|
}
|
|
727
1030
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
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
|
+
};
|
|
738
1042
|
try {
|
|
739
|
-
|
|
740
|
-
|
|
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));
|
|
741
1047
|
try {
|
|
1048
|
+
// if (world && world.screenshot && !world.screenshotPath) {
|
|
1049
|
+
// console.log(`Highlighting while running from recorder`);
|
|
742
1050
|
await this._highlightElements(element);
|
|
743
|
-
await element.setChecked(checked
|
|
1051
|
+
await state.element.setChecked(checked);
|
|
744
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);
|
|
745
1057
|
}
|
|
746
1058
|
catch (e) {
|
|
747
1059
|
if (e.message && e.message.includes("did not change its state")) {
|
|
@@ -749,179 +1061,102 @@ class StableBrowser {
|
|
|
749
1061
|
}
|
|
750
1062
|
else {
|
|
751
1063
|
//await this.closeUnexpectedPopups();
|
|
752
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
753
|
-
element = await this._locate(selectors, info, _params);
|
|
754
|
-
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 });
|
|
755
1067
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
756
1068
|
}
|
|
757
1069
|
}
|
|
758
1070
|
await this.waitForPageLoad();
|
|
759
|
-
return info;
|
|
1071
|
+
return state.info;
|
|
760
1072
|
}
|
|
761
1073
|
catch (e) {
|
|
762
|
-
|
|
763
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
764
|
-
info.screenshotPath = screenshotPath;
|
|
765
|
-
Object.assign(e, { info: info });
|
|
766
|
-
error = e;
|
|
767
|
-
throw e;
|
|
1074
|
+
await _commandError(state, e, this);
|
|
768
1075
|
}
|
|
769
1076
|
finally {
|
|
770
|
-
|
|
771
|
-
this._reportToWorld(world, {
|
|
772
|
-
element_name: selectors.element_name,
|
|
773
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
774
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
775
|
-
screenshotId,
|
|
776
|
-
result: error
|
|
777
|
-
? {
|
|
778
|
-
status: "FAILED",
|
|
779
|
-
startTime,
|
|
780
|
-
endTime,
|
|
781
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
782
|
-
}
|
|
783
|
-
: {
|
|
784
|
-
status: "PASSED",
|
|
785
|
-
startTime,
|
|
786
|
-
endTime,
|
|
787
|
-
},
|
|
788
|
-
info: info,
|
|
789
|
-
});
|
|
1077
|
+
_commandFinally(state, this);
|
|
790
1078
|
}
|
|
791
1079
|
}
|
|
792
1080
|
async hover(selectors, _params, options = {}, world = null) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
+
};
|
|
802
1092
|
try {
|
|
803
|
-
|
|
804
|
-
(
|
|
805
|
-
|
|
806
|
-
await this._highlightElements(element);
|
|
807
|
-
await element.hover({ timeout: 10000 });
|
|
808
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
809
|
-
}
|
|
810
|
-
catch (e) {
|
|
811
|
-
//await this.closeUnexpectedPopups();
|
|
812
|
-
info.log += "hover failed, will try again" + "\n";
|
|
813
|
-
element = await this._locate(selectors, info, _params);
|
|
814
|
-
await element.hover({ timeout: 10000 });
|
|
815
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
816
|
-
}
|
|
1093
|
+
await _preCommand(state, this);
|
|
1094
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
1095
|
+
await _screenshot(state, this);
|
|
817
1096
|
await this.waitForPageLoad();
|
|
818
|
-
return info;
|
|
1097
|
+
return state.info;
|
|
819
1098
|
}
|
|
820
1099
|
catch (e) {
|
|
821
|
-
|
|
822
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
823
|
-
info.screenshotPath = screenshotPath;
|
|
824
|
-
Object.assign(e, { info: info });
|
|
825
|
-
error = e;
|
|
826
|
-
throw e;
|
|
1100
|
+
await _commandError(state, e, this);
|
|
827
1101
|
}
|
|
828
1102
|
finally {
|
|
829
|
-
|
|
830
|
-
this._reportToWorld(world, {
|
|
831
|
-
element_name: selectors.element_name,
|
|
832
|
-
type: Types.HOVER,
|
|
833
|
-
text: `Hover element`,
|
|
834
|
-
screenshotId,
|
|
835
|
-
result: error
|
|
836
|
-
? {
|
|
837
|
-
status: "FAILED",
|
|
838
|
-
startTime,
|
|
839
|
-
endTime,
|
|
840
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
841
|
-
}
|
|
842
|
-
: {
|
|
843
|
-
status: "PASSED",
|
|
844
|
-
startTime,
|
|
845
|
-
endTime,
|
|
846
|
-
},
|
|
847
|
-
info: info,
|
|
848
|
-
});
|
|
1103
|
+
_commandFinally(state, this);
|
|
849
1104
|
}
|
|
850
1105
|
}
|
|
851
1106
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
852
|
-
this._validateSelectors(selectors);
|
|
853
1107
|
if (!values) {
|
|
854
1108
|
throw new Error("values is null");
|
|
855
1109
|
}
|
|
856
|
-
const
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
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
|
+
};
|
|
864
1122
|
try {
|
|
865
|
-
|
|
866
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1123
|
+
await _preCommand(state, this);
|
|
867
1124
|
try {
|
|
868
|
-
await
|
|
869
|
-
await element.selectOption(values, { timeout: 5000 });
|
|
1125
|
+
await state.element.selectOption(values);
|
|
870
1126
|
}
|
|
871
1127
|
catch (e) {
|
|
872
1128
|
//await this.closeUnexpectedPopups();
|
|
873
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
874
|
-
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 });
|
|
875
1131
|
}
|
|
876
1132
|
await this.waitForPageLoad();
|
|
877
|
-
return info;
|
|
1133
|
+
return state.info;
|
|
878
1134
|
}
|
|
879
1135
|
catch (e) {
|
|
880
|
-
|
|
881
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
882
|
-
info.screenshotPath = screenshotPath;
|
|
883
|
-
Object.assign(e, { info: info });
|
|
884
|
-
this.logger.info("click failed, will try next selector");
|
|
885
|
-
error = e;
|
|
886
|
-
throw e;
|
|
1136
|
+
await _commandError(state, e, this);
|
|
887
1137
|
}
|
|
888
1138
|
finally {
|
|
889
|
-
|
|
890
|
-
this._reportToWorld(world, {
|
|
891
|
-
element_name: selectors.element_name,
|
|
892
|
-
type: Types.SELECT,
|
|
893
|
-
text: `Select option: ${values}`,
|
|
894
|
-
value: values.toString(),
|
|
895
|
-
screenshotId,
|
|
896
|
-
result: error
|
|
897
|
-
? {
|
|
898
|
-
status: "FAILED",
|
|
899
|
-
startTime,
|
|
900
|
-
endTime,
|
|
901
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
902
|
-
}
|
|
903
|
-
: {
|
|
904
|
-
status: "PASSED",
|
|
905
|
-
startTime,
|
|
906
|
-
endTime,
|
|
907
|
-
},
|
|
908
|
-
info: info,
|
|
909
|
-
});
|
|
1139
|
+
_commandFinally(state, this);
|
|
910
1140
|
}
|
|
911
1141
|
}
|
|
912
1142
|
async type(_value, _params = null, options = {}, world = null) {
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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
|
+
};
|
|
922
1157
|
try {
|
|
923
|
-
|
|
924
|
-
const valueSegment =
|
|
1158
|
+
await _preCommand(state, this);
|
|
1159
|
+
const valueSegment = state.value.split("&&");
|
|
925
1160
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
926
1161
|
if (i > 0) {
|
|
927
1162
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -941,134 +1176,77 @@ class StableBrowser {
|
|
|
941
1176
|
await this.page.keyboard.type(value);
|
|
942
1177
|
}
|
|
943
1178
|
}
|
|
944
|
-
return info;
|
|
1179
|
+
return state.info;
|
|
945
1180
|
}
|
|
946
1181
|
catch (e) {
|
|
947
|
-
|
|
948
|
-
this.logger.error("type failed " + info.log);
|
|
949
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
950
|
-
info.screenshotPath = screenshotPath;
|
|
951
|
-
Object.assign(e, { info: info });
|
|
952
|
-
error = e;
|
|
953
|
-
throw e;
|
|
1182
|
+
await _commandError(state, e, this);
|
|
954
1183
|
}
|
|
955
1184
|
finally {
|
|
956
|
-
|
|
957
|
-
this._reportToWorld(world, {
|
|
958
|
-
type: Types.TYPE_PRESS,
|
|
959
|
-
screenshotId,
|
|
960
|
-
value: _value,
|
|
961
|
-
text: `type value: ${_value}`,
|
|
962
|
-
result: error
|
|
963
|
-
? {
|
|
964
|
-
status: "FAILED",
|
|
965
|
-
startTime,
|
|
966
|
-
endTime,
|
|
967
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
968
|
-
}
|
|
969
|
-
: {
|
|
970
|
-
status: "PASSED",
|
|
971
|
-
startTime,
|
|
972
|
-
endTime,
|
|
973
|
-
},
|
|
974
|
-
info: info,
|
|
975
|
-
});
|
|
1185
|
+
_commandFinally(state, this);
|
|
976
1186
|
}
|
|
977
1187
|
}
|
|
978
1188
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
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
|
+
};
|
|
991
1200
|
try {
|
|
992
|
-
|
|
993
|
-
let
|
|
994
|
-
await this.scrollIfNeeded(element, info);
|
|
995
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
996
|
-
await this._highlightElements(element);
|
|
1201
|
+
await _preCommand(state, this);
|
|
1202
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
997
1203
|
try {
|
|
998
|
-
await element.evaluateHandle((el, value) => {
|
|
1204
|
+
await state.element.evaluateHandle((el, value) => {
|
|
999
1205
|
el.value = value;
|
|
1000
1206
|
}, value);
|
|
1001
1207
|
}
|
|
1002
1208
|
catch (error) {
|
|
1003
1209
|
this.logger.error("setInputValue failed, will try again");
|
|
1004
|
-
|
|
1005
|
-
info.
|
|
1006
|
-
|
|
1007
|
-
await element.evaluateHandle((el, value) => {
|
|
1210
|
+
await _screenshot(state, this);
|
|
1211
|
+
Object.assign(error, { info: state.info });
|
|
1212
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1008
1213
|
el.value = value;
|
|
1009
1214
|
});
|
|
1010
1215
|
}
|
|
1011
1216
|
}
|
|
1012
1217
|
catch (e) {
|
|
1013
|
-
|
|
1014
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1015
|
-
info.screenshotPath = screenshotPath;
|
|
1016
|
-
Object.assign(e, { info: info });
|
|
1017
|
-
error = e;
|
|
1018
|
-
throw e;
|
|
1218
|
+
await _commandError(state, e, this);
|
|
1019
1219
|
}
|
|
1020
1220
|
finally {
|
|
1021
|
-
|
|
1022
|
-
this._reportToWorld(world, {
|
|
1023
|
-
element_name: selectors.element_name,
|
|
1024
|
-
type: Types.SET_INPUT,
|
|
1025
|
-
text: `Set input value`,
|
|
1026
|
-
value: value,
|
|
1027
|
-
screenshotId,
|
|
1028
|
-
result: error
|
|
1029
|
-
? {
|
|
1030
|
-
status: "FAILED",
|
|
1031
|
-
startTime,
|
|
1032
|
-
endTime,
|
|
1033
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1034
|
-
}
|
|
1035
|
-
: {
|
|
1036
|
-
status: "PASSED",
|
|
1037
|
-
startTime,
|
|
1038
|
-
endTime,
|
|
1039
|
-
},
|
|
1040
|
-
info: info,
|
|
1041
|
-
});
|
|
1221
|
+
_commandFinally(state, this);
|
|
1042
1222
|
}
|
|
1043
1223
|
}
|
|
1044
1224
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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
|
+
};
|
|
1055
1238
|
try {
|
|
1056
|
-
|
|
1057
|
-
let element = await this._locate(selectors, info, _params);
|
|
1058
|
-
//insert red border around the element
|
|
1059
|
-
await this.scrollIfNeeded(element, info);
|
|
1060
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1061
|
-
await this._highlightElements(element);
|
|
1239
|
+
await _preCommand(state, this);
|
|
1062
1240
|
try {
|
|
1063
|
-
await element
|
|
1241
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1064
1242
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1065
1243
|
if (format) {
|
|
1066
|
-
value = dayjs(value).format(format);
|
|
1067
|
-
await element.fill(value);
|
|
1244
|
+
state.value = dayjs(state.value).format(format);
|
|
1245
|
+
await state.element.fill(state.value);
|
|
1068
1246
|
}
|
|
1069
1247
|
else {
|
|
1070
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1071
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1248
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1249
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1072
1250
|
el.value = ""; // clear input
|
|
1073
1251
|
el.value = dateTimeValue;
|
|
1074
1252
|
}, dateTimeValue);
|
|
@@ -1081,20 +1259,19 @@ class StableBrowser {
|
|
|
1081
1259
|
}
|
|
1082
1260
|
catch (err) {
|
|
1083
1261
|
//await this.closeUnexpectedPopups();
|
|
1084
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1262
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1085
1263
|
this.logger.info("Trying again");
|
|
1086
|
-
|
|
1087
|
-
info.
|
|
1088
|
-
Object.assign(err, { info: info });
|
|
1264
|
+
await _screenshot(state, this);
|
|
1265
|
+
Object.assign(err, { info: state.info });
|
|
1089
1266
|
await element.click();
|
|
1090
1267
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1091
1268
|
if (format) {
|
|
1092
|
-
value = dayjs(value).format(format);
|
|
1093
|
-
await element.fill(value);
|
|
1269
|
+
state.value = dayjs(state.value).format(format);
|
|
1270
|
+
await state.element.fill(state.value);
|
|
1094
1271
|
}
|
|
1095
1272
|
else {
|
|
1096
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1097
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1273
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1274
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1098
1275
|
el.value = ""; // clear input
|
|
1099
1276
|
el.value = dateTimeValue;
|
|
1100
1277
|
}, dateTimeValue);
|
|
@@ -1107,60 +1284,40 @@ class StableBrowser {
|
|
|
1107
1284
|
}
|
|
1108
1285
|
}
|
|
1109
1286
|
catch (e) {
|
|
1110
|
-
|
|
1111
|
-
throw e;
|
|
1287
|
+
await _commandError(state, e, this);
|
|
1112
1288
|
}
|
|
1113
1289
|
finally {
|
|
1114
|
-
|
|
1115
|
-
this._reportToWorld(world, {
|
|
1116
|
-
element_name: selectors.element_name,
|
|
1117
|
-
type: Types.SET_DATE_TIME,
|
|
1118
|
-
screenshotId,
|
|
1119
|
-
value: value,
|
|
1120
|
-
text: `setDateTime input with value: ${value}`,
|
|
1121
|
-
result: error
|
|
1122
|
-
? {
|
|
1123
|
-
status: "FAILED",
|
|
1124
|
-
startTime,
|
|
1125
|
-
endTime,
|
|
1126
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1127
|
-
}
|
|
1128
|
-
: {
|
|
1129
|
-
status: "PASSED",
|
|
1130
|
-
startTime,
|
|
1131
|
-
endTime,
|
|
1132
|
-
},
|
|
1133
|
-
info: info,
|
|
1134
|
-
});
|
|
1290
|
+
_commandFinally(state, this);
|
|
1135
1291
|
}
|
|
1136
1292
|
}
|
|
1137
1293
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1138
|
-
|
|
1139
|
-
const startTime = Date.now();
|
|
1140
|
-
let error = null;
|
|
1141
|
-
let screenshotId = null;
|
|
1142
|
-
let screenshotPath = null;
|
|
1143
|
-
const info = {};
|
|
1144
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1145
|
-
info.operation = "clickType";
|
|
1146
|
-
info.selectors = selectors;
|
|
1294
|
+
_value = unEscapeString(_value);
|
|
1147
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
|
+
};
|
|
1148
1309
|
if (newValue !== _value) {
|
|
1149
1310
|
//this.logger.info(_value + "=" + newValue);
|
|
1150
1311
|
_value = newValue;
|
|
1151
1312
|
}
|
|
1152
|
-
info.value = _value;
|
|
1153
1313
|
try {
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
await this.scrollIfNeeded(element, info);
|
|
1157
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1158
|
-
await this._highlightElements(element);
|
|
1314
|
+
await _preCommand(state, this);
|
|
1315
|
+
state.info.value = _value;
|
|
1159
1316
|
if (options === null || options === undefined || !options.press) {
|
|
1160
1317
|
try {
|
|
1161
|
-
let currentValue = await element.inputValue();
|
|
1318
|
+
let currentValue = await state.element.inputValue();
|
|
1162
1319
|
if (currentValue) {
|
|
1163
|
-
await element.fill("");
|
|
1320
|
+
await state.element.fill("");
|
|
1164
1321
|
}
|
|
1165
1322
|
}
|
|
1166
1323
|
catch (e) {
|
|
@@ -1168,23 +1325,22 @@ class StableBrowser {
|
|
|
1168
1325
|
}
|
|
1169
1326
|
}
|
|
1170
1327
|
if (options === null || options === undefined || options.press) {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
catch (e) {
|
|
1175
|
-
await element.dispatchEvent("click");
|
|
1328
|
+
if (!options) {
|
|
1329
|
+
options = {};
|
|
1176
1330
|
}
|
|
1331
|
+
options.timeout = 5000;
|
|
1332
|
+
await performAction("click", state.element, options, this, state, _params);
|
|
1177
1333
|
}
|
|
1178
1334
|
else {
|
|
1179
1335
|
try {
|
|
1180
|
-
await element.focus();
|
|
1336
|
+
await state.element.focus();
|
|
1181
1337
|
}
|
|
1182
1338
|
catch (e) {
|
|
1183
|
-
await element.dispatchEvent("focus");
|
|
1339
|
+
await state.element.dispatchEvent("focus");
|
|
1184
1340
|
}
|
|
1185
1341
|
}
|
|
1186
1342
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1187
|
-
const valueSegment =
|
|
1343
|
+
const valueSegment = state.value.split("&&");
|
|
1188
1344
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1189
1345
|
if (i > 0) {
|
|
1190
1346
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1204,13 +1360,19 @@ class StableBrowser {
|
|
|
1204
1360
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1205
1361
|
}
|
|
1206
1362
|
}
|
|
1363
|
+
await _screenshot(state, this);
|
|
1207
1364
|
if (enter === true) {
|
|
1208
1365
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1209
1366
|
await this.page.keyboard.press("Enter");
|
|
1210
1367
|
await this.waitForPageLoad();
|
|
1211
1368
|
}
|
|
1212
1369
|
else if (enter === false) {
|
|
1213
|
-
|
|
1370
|
+
try {
|
|
1371
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1372
|
+
}
|
|
1373
|
+
catch (e) {
|
|
1374
|
+
// ignore
|
|
1375
|
+
}
|
|
1214
1376
|
//await this.page.keyboard.press("Tab");
|
|
1215
1377
|
}
|
|
1216
1378
|
else {
|
|
@@ -1219,111 +1381,60 @@ class StableBrowser {
|
|
|
1219
1381
|
await this.waitForPageLoad();
|
|
1220
1382
|
}
|
|
1221
1383
|
}
|
|
1222
|
-
return info;
|
|
1384
|
+
return state.info;
|
|
1223
1385
|
}
|
|
1224
1386
|
catch (e) {
|
|
1225
|
-
|
|
1226
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1227
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1228
|
-
info.screenshotPath = screenshotPath;
|
|
1229
|
-
Object.assign(e, { info: info });
|
|
1230
|
-
error = e;
|
|
1231
|
-
throw e;
|
|
1387
|
+
await _commandError(state, e, this);
|
|
1232
1388
|
}
|
|
1233
1389
|
finally {
|
|
1234
|
-
|
|
1235
|
-
this._reportToWorld(world, {
|
|
1236
|
-
element_name: selectors.element_name,
|
|
1237
|
-
type: Types.FILL,
|
|
1238
|
-
screenshotId,
|
|
1239
|
-
value: _value,
|
|
1240
|
-
text: `clickType input with value: ${_value}`,
|
|
1241
|
-
result: error
|
|
1242
|
-
? {
|
|
1243
|
-
status: "FAILED",
|
|
1244
|
-
startTime,
|
|
1245
|
-
endTime,
|
|
1246
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1247
|
-
}
|
|
1248
|
-
: {
|
|
1249
|
-
status: "PASSED",
|
|
1250
|
-
startTime,
|
|
1251
|
-
endTime,
|
|
1252
|
-
},
|
|
1253
|
-
info: info,
|
|
1254
|
-
});
|
|
1390
|
+
_commandFinally(state, this);
|
|
1255
1391
|
}
|
|
1256
1392
|
}
|
|
1257
1393
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
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
|
+
};
|
|
1268
1405
|
try {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
await
|
|
1272
|
-
await element.fill(value, { timeout: 10000 });
|
|
1273
|
-
await element.dispatchEvent("change");
|
|
1406
|
+
await _preCommand(state, this);
|
|
1407
|
+
await state.element.fill(value);
|
|
1408
|
+
await state.element.dispatchEvent("change");
|
|
1274
1409
|
if (enter) {
|
|
1275
1410
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1276
1411
|
await this.page.keyboard.press("Enter");
|
|
1277
1412
|
}
|
|
1278
1413
|
await this.waitForPageLoad();
|
|
1279
|
-
return info;
|
|
1414
|
+
return state.info;
|
|
1280
1415
|
}
|
|
1281
1416
|
catch (e) {
|
|
1282
|
-
|
|
1283
|
-
this.logger.error("fill failed " + info.log);
|
|
1284
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1285
|
-
info.screenshotPath = screenshotPath;
|
|
1286
|
-
Object.assign(e, { info: info });
|
|
1287
|
-
error = e;
|
|
1288
|
-
throw e;
|
|
1417
|
+
await _commandError(state, e, this);
|
|
1289
1418
|
}
|
|
1290
1419
|
finally {
|
|
1291
|
-
|
|
1292
|
-
this._reportToWorld(world, {
|
|
1293
|
-
element_name: selectors.element_name,
|
|
1294
|
-
type: Types.FILL,
|
|
1295
|
-
screenshotId,
|
|
1296
|
-
value,
|
|
1297
|
-
text: `Fill input with value: ${value}`,
|
|
1298
|
-
result: error
|
|
1299
|
-
? {
|
|
1300
|
-
status: "FAILED",
|
|
1301
|
-
startTime,
|
|
1302
|
-
endTime,
|
|
1303
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1304
|
-
}
|
|
1305
|
-
: {
|
|
1306
|
-
status: "PASSED",
|
|
1307
|
-
startTime,
|
|
1308
|
-
endTime,
|
|
1309
|
-
},
|
|
1310
|
-
info: info,
|
|
1311
|
-
});
|
|
1420
|
+
_commandFinally(state, this);
|
|
1312
1421
|
}
|
|
1313
1422
|
}
|
|
1314
1423
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1315
1424
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1316
1425
|
}
|
|
1317
1426
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1318
|
-
this.
|
|
1427
|
+
const timeout = this._getFindElementTimeout(options);
|
|
1428
|
+
_validateSelectors(selectors);
|
|
1319
1429
|
let screenshotId = null;
|
|
1320
1430
|
let screenshotPath = null;
|
|
1321
1431
|
if (!info.log) {
|
|
1322
1432
|
info.log = "";
|
|
1433
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1323
1434
|
}
|
|
1324
1435
|
info.operation = "getText";
|
|
1325
1436
|
info.selectors = selectors;
|
|
1326
|
-
let element = await this._locate(selectors, info, _params);
|
|
1437
|
+
let element = await this._locate(selectors, info, _params, timeout);
|
|
1327
1438
|
if (climb > 0) {
|
|
1328
1439
|
const climbArray = [];
|
|
1329
1440
|
for (let i = 0; i < climb; i++) {
|
|
@@ -1342,6 +1453,18 @@ class StableBrowser {
|
|
|
1342
1453
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1343
1454
|
try {
|
|
1344
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
|
+
// }
|
|
1345
1468
|
const elementText = await element.innerText();
|
|
1346
1469
|
return {
|
|
1347
1470
|
text: elementText,
|
|
@@ -1353,195 +1476,172 @@ class StableBrowser {
|
|
|
1353
1476
|
}
|
|
1354
1477
|
catch (e) {
|
|
1355
1478
|
//await this.closeUnexpectedPopups();
|
|
1356
|
-
this.logger.info("no innerText will use textContent");
|
|
1479
|
+
this.logger.info("no innerText, will use textContent");
|
|
1357
1480
|
const elementText = await element.textContent();
|
|
1358
1481
|
return { text: elementText, screenshotId, screenshotPath, value: value };
|
|
1359
1482
|
}
|
|
1360
1483
|
}
|
|
1361
1484
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1362
|
-
var _a;
|
|
1363
|
-
this._validateSelectors(selectors);
|
|
1364
1485
|
if (!pattern) {
|
|
1365
1486
|
throw new Error("pattern is null");
|
|
1366
1487
|
}
|
|
1367
1488
|
if (!text) {
|
|
1368
1489
|
throw new Error("text is null");
|
|
1369
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
|
+
};
|
|
1370
1508
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1371
1509
|
if (newValue !== text) {
|
|
1372
1510
|
this.logger.info(text + "=" + newValue);
|
|
1373
1511
|
text = newValue;
|
|
1374
1512
|
}
|
|
1375
|
-
const startTime = Date.now();
|
|
1376
|
-
let error = null;
|
|
1377
|
-
let screenshotId = null;
|
|
1378
|
-
let screenshotPath = null;
|
|
1379
|
-
const info = {};
|
|
1380
|
-
info.log =
|
|
1381
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1382
|
-
info.operation = "containsPattern";
|
|
1383
|
-
info.selectors = selectors;
|
|
1384
|
-
info.value = text;
|
|
1385
|
-
info.pattern = pattern;
|
|
1386
1513
|
let foundObj = null;
|
|
1387
1514
|
try {
|
|
1388
|
-
|
|
1515
|
+
await _preCommand(state, this);
|
|
1516
|
+
state.info.pattern = pattern;
|
|
1517
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1389
1518
|
if (foundObj && foundObj.element) {
|
|
1390
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1519
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1391
1520
|
}
|
|
1392
|
-
|
|
1521
|
+
await _screenshot(state, this);
|
|
1393
1522
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1394
1523
|
pattern = pattern.replace("{text}", escapedText);
|
|
1395
1524
|
let regex = new RegExp(pattern, "im");
|
|
1396
|
-
if (!regex.test(foundObj
|
|
1397
|
-
info.foundText = foundObj
|
|
1525
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1526
|
+
state.info.foundText = foundObj?.text;
|
|
1398
1527
|
throw new Error("element doesn't contain text " + text);
|
|
1399
1528
|
}
|
|
1400
|
-
return info;
|
|
1529
|
+
return state.info;
|
|
1401
1530
|
}
|
|
1402
1531
|
catch (e) {
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1406
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1407
|
-
info.screenshotPath = screenshotPath;
|
|
1408
|
-
Object.assign(e, { info: info });
|
|
1409
|
-
error = e;
|
|
1410
|
-
throw e;
|
|
1532
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1533
|
+
await _commandError(state, e, this);
|
|
1411
1534
|
}
|
|
1412
1535
|
finally {
|
|
1413
|
-
|
|
1414
|
-
this._reportToWorld(world, {
|
|
1415
|
-
element_name: selectors.element_name,
|
|
1416
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1417
|
-
value: pattern,
|
|
1418
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1419
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1420
|
-
result: error
|
|
1421
|
-
? {
|
|
1422
|
-
status: "FAILED",
|
|
1423
|
-
startTime,
|
|
1424
|
-
endTime,
|
|
1425
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1426
|
-
}
|
|
1427
|
-
: {
|
|
1428
|
-
status: "PASSED",
|
|
1429
|
-
startTime,
|
|
1430
|
-
endTime,
|
|
1431
|
-
},
|
|
1432
|
-
info: info,
|
|
1433
|
-
});
|
|
1536
|
+
_commandFinally(state, this);
|
|
1434
1537
|
}
|
|
1435
1538
|
}
|
|
1436
1539
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1437
|
-
|
|
1438
|
-
|
|
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
|
+
};
|
|
1439
1557
|
if (!text) {
|
|
1440
1558
|
throw new Error("text is null");
|
|
1441
1559
|
}
|
|
1442
|
-
|
|
1443
|
-
let error = null;
|
|
1444
|
-
let screenshotId = null;
|
|
1445
|
-
let screenshotPath = null;
|
|
1446
|
-
const info = {};
|
|
1447
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1448
|
-
info.operation = "containsText";
|
|
1449
|
-
info.selectors = selectors;
|
|
1560
|
+
text = unEscapeString(text);
|
|
1450
1561
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1451
1562
|
if (newValue !== text) {
|
|
1452
1563
|
this.logger.info(text + "=" + newValue);
|
|
1453
1564
|
text = newValue;
|
|
1454
1565
|
}
|
|
1455
|
-
info.value = text;
|
|
1456
1566
|
let foundObj = null;
|
|
1457
1567
|
try {
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
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
|
+
}
|
|
1470
1585
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
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;
|
|
1479
1596
|
}
|
|
1480
1597
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
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
|
|
1487
1603
|
}
|
|
1488
|
-
|
|
1604
|
+
state.info.foundText = foundObj?.text;
|
|
1605
|
+
state.info.value = foundObj?.value;
|
|
1606
|
+
throw new Error("element doesn't contain text " + text);
|
|
1489
1607
|
}
|
|
1490
1608
|
catch (e) {
|
|
1491
|
-
|
|
1492
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1493
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1494
|
-
info.screenshotPath = screenshotPath;
|
|
1495
|
-
Object.assign(e, { info: info });
|
|
1496
|
-
error = e;
|
|
1609
|
+
await _commandError(state, e, this);
|
|
1497
1610
|
throw e;
|
|
1498
1611
|
}
|
|
1499
1612
|
finally {
|
|
1500
|
-
|
|
1501
|
-
this._reportToWorld(world, {
|
|
1502
|
-
element_name: selectors.element_name,
|
|
1503
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1504
|
-
text: `Verify element contains text: ${text}`,
|
|
1505
|
-
value: text,
|
|
1506
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1507
|
-
result: error
|
|
1508
|
-
? {
|
|
1509
|
-
status: "FAILED",
|
|
1510
|
-
startTime,
|
|
1511
|
-
endTime,
|
|
1512
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1513
|
-
}
|
|
1514
|
-
: {
|
|
1515
|
-
status: "PASSED",
|
|
1516
|
-
startTime,
|
|
1517
|
-
endTime,
|
|
1518
|
-
},
|
|
1519
|
-
info: info,
|
|
1520
|
-
});
|
|
1613
|
+
_commandFinally(state, this);
|
|
1521
1614
|
}
|
|
1522
1615
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
dataFile = path.join(world.reportFolder, "data.json");
|
|
1527
|
-
}
|
|
1528
|
-
else if (this.reportFolder) {
|
|
1529
|
-
dataFile = path.join(this.reportFolder, "data.json");
|
|
1530
|
-
}
|
|
1531
|
-
else if (this.context && this.context.reportFolder) {
|
|
1532
|
-
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";
|
|
1533
1619
|
}
|
|
1534
1620
|
else {
|
|
1535
|
-
|
|
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}`);
|
|
1536
1636
|
}
|
|
1537
|
-
|
|
1637
|
+
this.setTestData({ userInput: value }, world);
|
|
1538
1638
|
}
|
|
1539
1639
|
setTestData(testData, world = null) {
|
|
1540
1640
|
if (!testData) {
|
|
1541
1641
|
return;
|
|
1542
1642
|
}
|
|
1543
1643
|
// if data file exists, load it
|
|
1544
|
-
const dataFile =
|
|
1644
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1545
1645
|
let data = this.getTestData(world);
|
|
1546
1646
|
// merge the testData with the existing data
|
|
1547
1647
|
Object.assign(data, testData);
|
|
@@ -1644,7 +1744,7 @@ class StableBrowser {
|
|
|
1644
1744
|
}
|
|
1645
1745
|
}
|
|
1646
1746
|
getTestData(world = null) {
|
|
1647
|
-
const dataFile =
|
|
1747
|
+
const dataFile = _getDataFile(world, this.context, this);
|
|
1648
1748
|
let data = {};
|
|
1649
1749
|
if (fs.existsSync(dataFile)) {
|
|
1650
1750
|
data = JSON.parse(fs.readFileSync(dataFile, "utf8"));
|
|
@@ -1676,11 +1776,9 @@ class StableBrowser {
|
|
|
1676
1776
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1677
1777
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1678
1778
|
}
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
}
|
|
1683
|
-
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");
|
|
1684
1782
|
try {
|
|
1685
1783
|
await this.takeScreenshot(screenshotPath);
|
|
1686
1784
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1690,15 +1788,15 @@ class StableBrowser {
|
|
|
1690
1788
|
// this.logger.info("unable to save screenshot " + screenshotPath);
|
|
1691
1789
|
// }
|
|
1692
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
|
+
}
|
|
1693
1796
|
}
|
|
1694
1797
|
catch (e) {
|
|
1695
1798
|
this.logger.info("unable to take screenshot, ignored");
|
|
1696
1799
|
}
|
|
1697
|
-
result.screenshotId = nextIndex;
|
|
1698
|
-
result.screenshotPath = screenshotPath;
|
|
1699
|
-
if (info && info.box) {
|
|
1700
|
-
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
1701
|
-
}
|
|
1702
1800
|
}
|
|
1703
1801
|
else if (options && options.screenshot) {
|
|
1704
1802
|
result.screenshotPath = options.screenshotPath;
|
|
@@ -1723,7 +1821,6 @@ class StableBrowser {
|
|
|
1723
1821
|
}
|
|
1724
1822
|
async takeScreenshot(screenshotPath) {
|
|
1725
1823
|
const playContext = this.context.playContext;
|
|
1726
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1727
1824
|
// Using CDP to capture the screenshot
|
|
1728
1825
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1729
1826
|
document.body.scrollWidth,
|
|
@@ -1733,164 +1830,192 @@ class StableBrowser {
|
|
|
1733
1830
|
document.body.clientWidth,
|
|
1734
1831
|
document.documentElement.clientWidth,
|
|
1735
1832
|
])));
|
|
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
|
-
|
|
1761
|
-
|
|
1762
|
-
screenshotBuffer =
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
}
|
|
1769
|
-
|
|
1770
|
-
|
|
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;
|
|
1771
1881
|
}
|
|
1772
1882
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
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
|
+
};
|
|
1778
1893
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1779
|
-
const info = {};
|
|
1780
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1781
|
-
info.operation = "verify";
|
|
1782
|
-
info.selectors = selectors;
|
|
1783
1894
|
try {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
}
|
|
1788
|
-
await this._highlightElements(element);
|
|
1789
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1790
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1791
|
-
return info;
|
|
1895
|
+
await _preCommand(state, this);
|
|
1896
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1897
|
+
return state.info;
|
|
1792
1898
|
}
|
|
1793
1899
|
catch (e) {
|
|
1794
|
-
|
|
1795
|
-
this.logger.error("verify failed " + info.log);
|
|
1796
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1797
|
-
info.screenshotPath = screenshotPath;
|
|
1798
|
-
Object.assign(e, { info: info });
|
|
1799
|
-
error = e;
|
|
1800
|
-
throw e;
|
|
1900
|
+
await _commandError(state, e, this);
|
|
1801
1901
|
}
|
|
1802
1902
|
finally {
|
|
1803
|
-
|
|
1804
|
-
this._reportToWorld(world, {
|
|
1805
|
-
element_name: selectors.element_name,
|
|
1806
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1807
|
-
text: "Verify element exists in page",
|
|
1808
|
-
screenshotId,
|
|
1809
|
-
result: error
|
|
1810
|
-
? {
|
|
1811
|
-
status: "FAILED",
|
|
1812
|
-
startTime,
|
|
1813
|
-
endTime,
|
|
1814
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1815
|
-
}
|
|
1816
|
-
: {
|
|
1817
|
-
status: "PASSED",
|
|
1818
|
-
startTime,
|
|
1819
|
-
endTime,
|
|
1820
|
-
},
|
|
1821
|
-
info: info,
|
|
1822
|
-
});
|
|
1903
|
+
_commandFinally(state, this);
|
|
1823
1904
|
}
|
|
1824
1905
|
}
|
|
1825
1906
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
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
|
+
};
|
|
1831
1921
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1832
|
-
const info = {};
|
|
1833
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1834
|
-
info.operation = "extract";
|
|
1835
|
-
info.selectors = selectors;
|
|
1836
1922
|
try {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
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;
|
|
1840
1975
|
switch (attribute) {
|
|
1841
|
-
case "
|
|
1842
|
-
|
|
1843
|
-
break;
|
|
1844
|
-
case "href":
|
|
1845
|
-
info.value = await element.getAttribute("href");
|
|
1976
|
+
case "innerText":
|
|
1977
|
+
val = String(await state.element.innerText());
|
|
1846
1978
|
break;
|
|
1847
1979
|
case "value":
|
|
1848
|
-
|
|
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);
|
|
1849
1991
|
break;
|
|
1850
1992
|
default:
|
|
1851
|
-
|
|
1993
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1852
1994
|
break;
|
|
1853
1995
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
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");
|
|
1857
2001
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
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;
|
|
1861
2013
|
}
|
|
1862
2014
|
catch (e) {
|
|
1863
|
-
|
|
1864
|
-
this.logger.error("extract failed " + info.log);
|
|
1865
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1866
|
-
info.screenshotPath = screenshotPath;
|
|
1867
|
-
Object.assign(e, { info: info });
|
|
1868
|
-
error = e;
|
|
1869
|
-
throw e;
|
|
2015
|
+
await _commandError(state, e, this);
|
|
1870
2016
|
}
|
|
1871
2017
|
finally {
|
|
1872
|
-
|
|
1873
|
-
this._reportToWorld(world, {
|
|
1874
|
-
element_name: selectors.element_name,
|
|
1875
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1876
|
-
variable: variable,
|
|
1877
|
-
value: info.value,
|
|
1878
|
-
text: "Extract attribute from element",
|
|
1879
|
-
screenshotId,
|
|
1880
|
-
result: error
|
|
1881
|
-
? {
|
|
1882
|
-
status: "FAILED",
|
|
1883
|
-
startTime,
|
|
1884
|
-
endTime,
|
|
1885
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1886
|
-
}
|
|
1887
|
-
: {
|
|
1888
|
-
status: "PASSED",
|
|
1889
|
-
startTime,
|
|
1890
|
-
endTime,
|
|
1891
|
-
},
|
|
1892
|
-
info: info,
|
|
1893
|
-
});
|
|
2018
|
+
_commandFinally(state, this);
|
|
1894
2019
|
}
|
|
1895
2020
|
}
|
|
1896
2021
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1911,7 +2036,7 @@ class StableBrowser {
|
|
|
1911
2036
|
if (options && options.timeout) {
|
|
1912
2037
|
timeout = options.timeout;
|
|
1913
2038
|
}
|
|
1914
|
-
const serviceUrl =
|
|
2039
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1915
2040
|
const request = {
|
|
1916
2041
|
method: "POST",
|
|
1917
2042
|
url: serviceUrl,
|
|
@@ -1967,7 +2092,8 @@ class StableBrowser {
|
|
|
1967
2092
|
catch (e) {
|
|
1968
2093
|
errorCount++;
|
|
1969
2094
|
if (errorCount > 3) {
|
|
1970
|
-
throw e;
|
|
2095
|
+
// throw e;
|
|
2096
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1971
2097
|
}
|
|
1972
2098
|
// ignore
|
|
1973
2099
|
}
|
|
@@ -1981,27 +2107,32 @@ class StableBrowser {
|
|
|
1981
2107
|
async _highlightElements(scope, css) {
|
|
1982
2108
|
try {
|
|
1983
2109
|
if (!scope) {
|
|
2110
|
+
// console.log(`Scope is not defined`);
|
|
1984
2111
|
return;
|
|
1985
2112
|
}
|
|
1986
2113
|
if (!css) {
|
|
1987
2114
|
scope
|
|
1988
2115
|
.evaluate((node) => {
|
|
1989
2116
|
if (node && node.style) {
|
|
1990
|
-
let
|
|
1991
|
-
|
|
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}`);
|
|
1992
2122
|
if (window) {
|
|
1993
2123
|
window.addEventListener("beforeunload", function (e) {
|
|
1994
|
-
node.style.
|
|
2124
|
+
node.style.outline = originalOutline;
|
|
1995
2125
|
});
|
|
1996
2126
|
}
|
|
1997
2127
|
setTimeout(function () {
|
|
1998
|
-
node.style.
|
|
2128
|
+
node.style.outline = originalOutline;
|
|
1999
2129
|
}, 2000);
|
|
2000
2130
|
}
|
|
2001
2131
|
})
|
|
2002
2132
|
.then(() => { })
|
|
2003
2133
|
.catch((e) => {
|
|
2004
2134
|
// ignore
|
|
2135
|
+
// console.error(`Could not highlight node : ${e}`);
|
|
2005
2136
|
});
|
|
2006
2137
|
}
|
|
2007
2138
|
else {
|
|
@@ -2017,17 +2148,18 @@ class StableBrowser {
|
|
|
2017
2148
|
if (!element.style) {
|
|
2018
2149
|
return;
|
|
2019
2150
|
}
|
|
2020
|
-
|
|
2151
|
+
let originalOutline = element.style.outline;
|
|
2152
|
+
element.__previousOutline = originalOutline;
|
|
2021
2153
|
// Set the new border to be red and 2px solid
|
|
2022
|
-
element.style.
|
|
2154
|
+
element.style.outline = "2px solid red";
|
|
2023
2155
|
if (window) {
|
|
2024
2156
|
window.addEventListener("beforeunload", function (e) {
|
|
2025
|
-
element.style.
|
|
2157
|
+
element.style.outline = originalOutline;
|
|
2026
2158
|
});
|
|
2027
2159
|
}
|
|
2028
2160
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2029
2161
|
setTimeout(function () {
|
|
2030
|
-
element.style.
|
|
2162
|
+
element.style.outline = originalOutline;
|
|
2031
2163
|
}, 2000);
|
|
2032
2164
|
}
|
|
2033
2165
|
return;
|
|
@@ -2035,6 +2167,7 @@ class StableBrowser {
|
|
|
2035
2167
|
.then(() => { })
|
|
2036
2168
|
.catch((e) => {
|
|
2037
2169
|
// ignore
|
|
2170
|
+
// console.error(`Could not highlight css: ${e}`);
|
|
2038
2171
|
});
|
|
2039
2172
|
}
|
|
2040
2173
|
}
|
|
@@ -2042,6 +2175,54 @@ class StableBrowser {
|
|
|
2042
2175
|
console.debug(error);
|
|
2043
2176
|
}
|
|
2044
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
|
+
// }
|
|
2045
2226
|
async verifyPagePath(pathPart, options = {}, world = null) {
|
|
2046
2227
|
const startTime = Date.now();
|
|
2047
2228
|
let error = null;
|
|
@@ -2078,20 +2259,22 @@ class StableBrowser {
|
|
|
2078
2259
|
info.screenshotPath = screenshotPath;
|
|
2079
2260
|
Object.assign(e, { info: info });
|
|
2080
2261
|
error = e;
|
|
2081
|
-
throw e;
|
|
2262
|
+
// throw e;
|
|
2263
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2082
2264
|
}
|
|
2083
2265
|
finally {
|
|
2084
2266
|
const endTime = Date.now();
|
|
2085
|
-
|
|
2267
|
+
_reportToWorld(world, {
|
|
2086
2268
|
type: Types.VERIFY_PAGE_PATH,
|
|
2087
2269
|
text: "Verify page path",
|
|
2270
|
+
_text: "Verify the page path contains " + pathPart,
|
|
2088
2271
|
screenshotId,
|
|
2089
2272
|
result: error
|
|
2090
2273
|
? {
|
|
2091
2274
|
status: "FAILED",
|
|
2092
2275
|
startTime,
|
|
2093
2276
|
endTime,
|
|
2094
|
-
message: error
|
|
2277
|
+
message: error?.message,
|
|
2095
2278
|
}
|
|
2096
2279
|
: {
|
|
2097
2280
|
status: "PASSED",
|
|
@@ -2102,113 +2285,317 @@ class StableBrowser {
|
|
|
2102
2285
|
});
|
|
2103
2286
|
}
|
|
2104
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
|
+
}
|
|
2105
2317
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2106
|
-
|
|
2107
|
-
const
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
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);
|
|
2111
2336
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2112
|
-
const info = {};
|
|
2113
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2114
|
-
info.operation = "verifyTextExistInPage";
|
|
2115
2337
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2116
2338
|
if (newValue !== text) {
|
|
2117
2339
|
this.logger.info(text + "=" + newValue);
|
|
2118
2340
|
text = newValue;
|
|
2119
2341
|
}
|
|
2120
|
-
info.text = text;
|
|
2121
2342
|
let dateAlternatives = findDateAlternatives(text);
|
|
2122
2343
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2123
2344
|
try {
|
|
2345
|
+
await _preCommand(state, this);
|
|
2346
|
+
state.info.text = text;
|
|
2124
2347
|
while (true) {
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
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`);
|
|
2134
2360
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
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);
|
|
2140
2390
|
}
|
|
2141
2391
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
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
|
|
2147
2447
|
}
|
|
2148
|
-
info.results = results;
|
|
2149
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2150
2448
|
if (resultWithElementsFound.length === 0) {
|
|
2151
|
-
|
|
2152
|
-
|
|
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`);
|
|
2153
2512
|
}
|
|
2154
2513
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2155
2514
|
continue;
|
|
2156
2515
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
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
|
+
}
|
|
2165
2561
|
}
|
|
2166
2562
|
}
|
|
2167
|
-
|
|
2168
|
-
|
|
2563
|
+
catch (error) {
|
|
2564
|
+
console.error(error);
|
|
2565
|
+
}
|
|
2169
2566
|
}
|
|
2170
2567
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2171
2568
|
}
|
|
2172
2569
|
catch (e) {
|
|
2173
|
-
|
|
2174
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2175
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2176
|
-
info.screenshotPath = screenshotPath;
|
|
2177
|
-
Object.assign(e, { info: info });
|
|
2178
|
-
error = e;
|
|
2179
|
-
throw e;
|
|
2570
|
+
await _commandError(state, e, this);
|
|
2180
2571
|
}
|
|
2181
2572
|
finally {
|
|
2182
|
-
|
|
2183
|
-
this._reportToWorld(world, {
|
|
2184
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2185
|
-
text: "Verify text exists in page",
|
|
2186
|
-
screenshotId,
|
|
2187
|
-
result: error
|
|
2188
|
-
? {
|
|
2189
|
-
status: "FAILED",
|
|
2190
|
-
startTime,
|
|
2191
|
-
endTime,
|
|
2192
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2193
|
-
}
|
|
2194
|
-
: {
|
|
2195
|
-
status: "PASSED",
|
|
2196
|
-
startTime,
|
|
2197
|
-
endTime,
|
|
2198
|
-
},
|
|
2199
|
-
info: info,
|
|
2200
|
-
});
|
|
2573
|
+
_commandFinally(state, this);
|
|
2201
2574
|
}
|
|
2202
2575
|
}
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
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
|
+
}
|
|
2210
2595
|
}
|
|
2211
|
-
|
|
2596
|
+
// state.info.results = results;
|
|
2597
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2598
|
+
return resultWithElementsFound;
|
|
2212
2599
|
}
|
|
2213
2600
|
async visualVerification(text, options = {}, world = null) {
|
|
2214
2601
|
const startTime = Date.now();
|
|
@@ -2224,14 +2611,17 @@ class StableBrowser {
|
|
|
2224
2611
|
throw new Error("TOKEN is not set");
|
|
2225
2612
|
}
|
|
2226
2613
|
try {
|
|
2227
|
-
let serviceUrl =
|
|
2614
|
+
let serviceUrl = _getServerUrl();
|
|
2228
2615
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2229
2616
|
info.screenshotPath = screenshotPath;
|
|
2230
2617
|
const screenshot = await this.takeScreenshot();
|
|
2231
|
-
|
|
2232
|
-
method: "
|
|
2618
|
+
let request = {
|
|
2619
|
+
method: "post",
|
|
2620
|
+
maxBodyLength: Infinity,
|
|
2233
2621
|
url: `${serviceUrl}/api/runs/screenshots/validate-screenshot`,
|
|
2234
2622
|
headers: {
|
|
2623
|
+
"x-bvt-project-id": path.basename(this.project_path),
|
|
2624
|
+
"x-source": "aaa",
|
|
2235
2625
|
"Content-Type": "application/json",
|
|
2236
2626
|
Authorization: `Bearer ${process.env.TOKEN}`,
|
|
2237
2627
|
},
|
|
@@ -2240,7 +2630,7 @@ class StableBrowser {
|
|
|
2240
2630
|
screenshot: screenshot,
|
|
2241
2631
|
}),
|
|
2242
2632
|
};
|
|
2243
|
-
|
|
2633
|
+
const result = await axios.request(request);
|
|
2244
2634
|
if (result.data.status !== true) {
|
|
2245
2635
|
throw new Error("Visual validation failed");
|
|
2246
2636
|
}
|
|
@@ -2260,20 +2650,22 @@ class StableBrowser {
|
|
|
2260
2650
|
info.screenshotPath = screenshotPath;
|
|
2261
2651
|
Object.assign(e, { info: info });
|
|
2262
2652
|
error = e;
|
|
2263
|
-
throw e;
|
|
2653
|
+
// throw e;
|
|
2654
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2264
2655
|
}
|
|
2265
2656
|
finally {
|
|
2266
2657
|
const endTime = Date.now();
|
|
2267
|
-
|
|
2658
|
+
_reportToWorld(world, {
|
|
2268
2659
|
type: Types.VERIFY_VISUAL,
|
|
2269
2660
|
text: "Visual verification",
|
|
2661
|
+
_text: "Visual verification of " + text,
|
|
2270
2662
|
screenshotId,
|
|
2271
2663
|
result: error
|
|
2272
2664
|
? {
|
|
2273
2665
|
status: "FAILED",
|
|
2274
2666
|
startTime,
|
|
2275
2667
|
endTime,
|
|
2276
|
-
message: error
|
|
2668
|
+
message: error?.message,
|
|
2277
2669
|
}
|
|
2278
2670
|
: {
|
|
2279
2671
|
status: "PASSED",
|
|
@@ -2305,13 +2697,14 @@ class StableBrowser {
|
|
|
2305
2697
|
this.logger.info("Table data verified");
|
|
2306
2698
|
}
|
|
2307
2699
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2308
|
-
|
|
2700
|
+
_validateSelectors(selectors);
|
|
2309
2701
|
const startTime = Date.now();
|
|
2310
2702
|
let error = null;
|
|
2311
2703
|
let screenshotId = null;
|
|
2312
2704
|
let screenshotPath = null;
|
|
2313
2705
|
const info = {};
|
|
2314
2706
|
info.log = "";
|
|
2707
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2315
2708
|
info.operation = "getTableData";
|
|
2316
2709
|
info.selectors = selectors;
|
|
2317
2710
|
try {
|
|
@@ -2327,11 +2720,12 @@ class StableBrowser {
|
|
|
2327
2720
|
info.screenshotPath = screenshotPath;
|
|
2328
2721
|
Object.assign(e, { info: info });
|
|
2329
2722
|
error = e;
|
|
2330
|
-
throw e;
|
|
2723
|
+
// throw e;
|
|
2724
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2331
2725
|
}
|
|
2332
2726
|
finally {
|
|
2333
2727
|
const endTime = Date.now();
|
|
2334
|
-
|
|
2728
|
+
_reportToWorld(world, {
|
|
2335
2729
|
element_name: selectors.element_name,
|
|
2336
2730
|
type: Types.GET_TABLE_DATA,
|
|
2337
2731
|
text: "Get table data",
|
|
@@ -2341,7 +2735,7 @@ class StableBrowser {
|
|
|
2341
2735
|
status: "FAILED",
|
|
2342
2736
|
startTime,
|
|
2343
2737
|
endTime,
|
|
2344
|
-
message: error
|
|
2738
|
+
message: error?.message,
|
|
2345
2739
|
}
|
|
2346
2740
|
: {
|
|
2347
2741
|
status: "PASSED",
|
|
@@ -2353,7 +2747,7 @@ class StableBrowser {
|
|
|
2353
2747
|
}
|
|
2354
2748
|
}
|
|
2355
2749
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2356
|
-
|
|
2750
|
+
_validateSelectors(selectors);
|
|
2357
2751
|
if (!query) {
|
|
2358
2752
|
throw new Error("query is null");
|
|
2359
2753
|
}
|
|
@@ -2386,7 +2780,7 @@ class StableBrowser {
|
|
|
2386
2780
|
info.operation = "analyzeTable";
|
|
2387
2781
|
info.selectors = selectors;
|
|
2388
2782
|
info.query = query;
|
|
2389
|
-
query =
|
|
2783
|
+
query = _fixUsingParams(query, _params);
|
|
2390
2784
|
info.query_fixed = query;
|
|
2391
2785
|
info.operator = operator;
|
|
2392
2786
|
info.value = value;
|
|
@@ -2492,11 +2886,12 @@ class StableBrowser {
|
|
|
2492
2886
|
info.screenshotPath = screenshotPath;
|
|
2493
2887
|
Object.assign(e, { info: info });
|
|
2494
2888
|
error = e;
|
|
2495
|
-
throw e;
|
|
2889
|
+
// throw e;
|
|
2890
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2496
2891
|
}
|
|
2497
2892
|
finally {
|
|
2498
2893
|
const endTime = Date.now();
|
|
2499
|
-
|
|
2894
|
+
_reportToWorld(world, {
|
|
2500
2895
|
element_name: selectors.element_name,
|
|
2501
2896
|
type: Types.ANALYZE_TABLE,
|
|
2502
2897
|
text: "Analyze table",
|
|
@@ -2506,7 +2901,7 @@ class StableBrowser {
|
|
|
2506
2901
|
status: "FAILED",
|
|
2507
2902
|
startTime,
|
|
2508
2903
|
endTime,
|
|
2509
|
-
message: error
|
|
2904
|
+
message: error?.message,
|
|
2510
2905
|
}
|
|
2511
2906
|
: {
|
|
2512
2907
|
status: "PASSED",
|
|
@@ -2518,27 +2913,7 @@ class StableBrowser {
|
|
|
2518
2913
|
}
|
|
2519
2914
|
}
|
|
2520
2915
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2521
|
-
|
|
2522
|
-
return value;
|
|
2523
|
-
}
|
|
2524
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2525
|
-
let regex = /{{(.*?)}}/g;
|
|
2526
|
-
let matches = value.match(regex);
|
|
2527
|
-
if (matches) {
|
|
2528
|
-
const testData = this.getTestData(world);
|
|
2529
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2530
|
-
let match = matches[i];
|
|
2531
|
-
let key = match.substring(2, match.length - 2);
|
|
2532
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2533
|
-
if (newValue !== null) {
|
|
2534
|
-
value = value.replace(match, newValue);
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2539
|
-
return await decrypt(value, null, totpWait);
|
|
2540
|
-
}
|
|
2541
|
-
return value;
|
|
2916
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2542
2917
|
}
|
|
2543
2918
|
_getLoadTimeout(options) {
|
|
2544
2919
|
let timeout = 15000;
|
|
@@ -2550,6 +2925,32 @@ class StableBrowser {
|
|
|
2550
2925
|
}
|
|
2551
2926
|
return timeout;
|
|
2552
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
|
+
}
|
|
2553
2954
|
async waitForPageLoad(options = {}, world = null) {
|
|
2554
2955
|
let timeout = this._getLoadTimeout(options);
|
|
2555
2956
|
const promiseArray = [];
|
|
@@ -2575,13 +2976,13 @@ class StableBrowser {
|
|
|
2575
2976
|
}
|
|
2576
2977
|
catch (e) {
|
|
2577
2978
|
if (e.label === "networkidle") {
|
|
2578
|
-
console.log("
|
|
2979
|
+
console.log("waited for the network to be idle timeout");
|
|
2579
2980
|
}
|
|
2580
2981
|
else if (e.label === "load") {
|
|
2581
|
-
console.log("
|
|
2982
|
+
console.log("waited for the load timeout");
|
|
2582
2983
|
}
|
|
2583
2984
|
else if (e.label === "domcontentloaded") {
|
|
2584
|
-
console.log("
|
|
2985
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2585
2986
|
}
|
|
2586
2987
|
console.log(".");
|
|
2587
2988
|
}
|
|
@@ -2589,7 +2990,7 @@ class StableBrowser {
|
|
|
2589
2990
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2590
2991
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2591
2992
|
const endTime = Date.now();
|
|
2592
|
-
|
|
2993
|
+
_reportToWorld(world, {
|
|
2593
2994
|
type: Types.GET_PAGE_STATUS,
|
|
2594
2995
|
text: "Wait for page load",
|
|
2595
2996
|
screenshotId,
|
|
@@ -2598,7 +2999,7 @@ class StableBrowser {
|
|
|
2598
2999
|
status: "FAILED",
|
|
2599
3000
|
startTime,
|
|
2600
3001
|
endTime,
|
|
2601
|
-
message: error
|
|
3002
|
+
message: error?.message,
|
|
2602
3003
|
}
|
|
2603
3004
|
: {
|
|
2604
3005
|
status: "PASSED",
|
|
@@ -2609,41 +3010,123 @@ class StableBrowser {
|
|
|
2609
3010
|
}
|
|
2610
3011
|
}
|
|
2611
3012
|
async closePage(options = {}, world = null) {
|
|
2612
|
-
const
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
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
|
+
};
|
|
2617
3026
|
try {
|
|
3027
|
+
await _preCommand(state, this);
|
|
2618
3028
|
await this.page.close();
|
|
2619
3029
|
}
|
|
2620
3030
|
catch (e) {
|
|
2621
3031
|
console.log(".");
|
|
3032
|
+
await _commandError(state, e, this);
|
|
2622
3033
|
}
|
|
2623
3034
|
finally {
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
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;
|
|
2637
3070
|
}
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
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);
|
|
2645
3123
|
}
|
|
2646
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
|
+
}
|
|
2647
3130
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2648
3131
|
const startTime = Date.now();
|
|
2649
3132
|
let error = null;
|
|
@@ -2661,21 +3144,23 @@ class StableBrowser {
|
|
|
2661
3144
|
}
|
|
2662
3145
|
catch (e) {
|
|
2663
3146
|
console.log(".");
|
|
3147
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2664
3148
|
}
|
|
2665
3149
|
finally {
|
|
2666
3150
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2667
3151
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2668
3152
|
const endTime = Date.now();
|
|
2669
|
-
|
|
3153
|
+
_reportToWorld(world, {
|
|
2670
3154
|
type: Types.SET_VIEWPORT,
|
|
2671
3155
|
text: "set viewport size to " + width + "x" + hight,
|
|
3156
|
+
_text: "Set the viewport size to " + width + "x" + hight,
|
|
2672
3157
|
screenshotId,
|
|
2673
3158
|
result: error
|
|
2674
3159
|
? {
|
|
2675
3160
|
status: "FAILED",
|
|
2676
3161
|
startTime,
|
|
2677
3162
|
endTime,
|
|
2678
|
-
message: error
|
|
3163
|
+
message: error?.message,
|
|
2679
3164
|
}
|
|
2680
3165
|
: {
|
|
2681
3166
|
status: "PASSED",
|
|
@@ -2697,12 +3182,13 @@ class StableBrowser {
|
|
|
2697
3182
|
}
|
|
2698
3183
|
catch (e) {
|
|
2699
3184
|
console.log(".");
|
|
3185
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2700
3186
|
}
|
|
2701
3187
|
finally {
|
|
2702
3188
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2703
3189
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2704
3190
|
const endTime = Date.now();
|
|
2705
|
-
|
|
3191
|
+
_reportToWorld(world, {
|
|
2706
3192
|
type: Types.GET_PAGE_STATUS,
|
|
2707
3193
|
text: "page relaod",
|
|
2708
3194
|
screenshotId,
|
|
@@ -2711,7 +3197,7 @@ class StableBrowser {
|
|
|
2711
3197
|
status: "FAILED",
|
|
2712
3198
|
startTime,
|
|
2713
3199
|
endTime,
|
|
2714
|
-
message: error
|
|
3200
|
+
message: error?.message,
|
|
2715
3201
|
}
|
|
2716
3202
|
: {
|
|
2717
3203
|
status: "PASSED",
|
|
@@ -2724,40 +3210,107 @@ class StableBrowser {
|
|
|
2724
3210
|
}
|
|
2725
3211
|
async scrollIfNeeded(element, info) {
|
|
2726
3212
|
try {
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
if (rect &&
|
|
2730
|
-
rect.top >= 0 &&
|
|
2731
|
-
rect.left >= 0 &&
|
|
2732
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2733
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2734
|
-
return false;
|
|
2735
|
-
}
|
|
2736
|
-
else {
|
|
2737
|
-
node.scrollIntoView({
|
|
2738
|
-
behavior: "smooth",
|
|
2739
|
-
block: "center",
|
|
2740
|
-
inline: "center",
|
|
2741
|
-
});
|
|
2742
|
-
return true;
|
|
2743
|
-
}
|
|
3213
|
+
await element.scrollIntoViewIfNeeded({
|
|
3214
|
+
timeout: 2000,
|
|
2744
3215
|
});
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
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");
|
|
2749
3265
|
}
|
|
2750
3266
|
}
|
|
2751
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
|
+
}
|
|
2752
3290
|
catch (e) {
|
|
2753
|
-
console.
|
|
3291
|
+
console.error(e);
|
|
2754
3292
|
}
|
|
3293
|
+
return null;
|
|
2755
3294
|
}
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
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
|
+
}
|
|
2759
3313
|
}
|
|
2760
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2761
3314
|
}
|
|
2762
3315
|
}
|
|
2763
3316
|
function createTimedPromise(promise, label) {
|
|
@@ -2765,151 +3318,5 @@ function createTimedPromise(promise, label) {
|
|
|
2765
3318
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2766
3319
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2767
3320
|
}
|
|
2768
|
-
const KEYBOARD_EVENTS = [
|
|
2769
|
-
"ALT",
|
|
2770
|
-
"AltGraph",
|
|
2771
|
-
"CapsLock",
|
|
2772
|
-
"Control",
|
|
2773
|
-
"Fn",
|
|
2774
|
-
"FnLock",
|
|
2775
|
-
"Hyper",
|
|
2776
|
-
"Meta",
|
|
2777
|
-
"NumLock",
|
|
2778
|
-
"ScrollLock",
|
|
2779
|
-
"Shift",
|
|
2780
|
-
"Super",
|
|
2781
|
-
"Symbol",
|
|
2782
|
-
"SymbolLock",
|
|
2783
|
-
"Enter",
|
|
2784
|
-
"Tab",
|
|
2785
|
-
"ArrowDown",
|
|
2786
|
-
"ArrowLeft",
|
|
2787
|
-
"ArrowRight",
|
|
2788
|
-
"ArrowUp",
|
|
2789
|
-
"End",
|
|
2790
|
-
"Home",
|
|
2791
|
-
"PageDown",
|
|
2792
|
-
"PageUp",
|
|
2793
|
-
"Backspace",
|
|
2794
|
-
"Clear",
|
|
2795
|
-
"Copy",
|
|
2796
|
-
"CrSel",
|
|
2797
|
-
"Cut",
|
|
2798
|
-
"Delete",
|
|
2799
|
-
"EraseEof",
|
|
2800
|
-
"ExSel",
|
|
2801
|
-
"Insert",
|
|
2802
|
-
"Paste",
|
|
2803
|
-
"Redo",
|
|
2804
|
-
"Undo",
|
|
2805
|
-
"Accept",
|
|
2806
|
-
"Again",
|
|
2807
|
-
"Attn",
|
|
2808
|
-
"Cancel",
|
|
2809
|
-
"ContextMenu",
|
|
2810
|
-
"Escape",
|
|
2811
|
-
"Execute",
|
|
2812
|
-
"Find",
|
|
2813
|
-
"Finish",
|
|
2814
|
-
"Help",
|
|
2815
|
-
"Pause",
|
|
2816
|
-
"Play",
|
|
2817
|
-
"Props",
|
|
2818
|
-
"Select",
|
|
2819
|
-
"ZoomIn",
|
|
2820
|
-
"ZoomOut",
|
|
2821
|
-
"BrightnessDown",
|
|
2822
|
-
"BrightnessUp",
|
|
2823
|
-
"Eject",
|
|
2824
|
-
"LogOff",
|
|
2825
|
-
"Power",
|
|
2826
|
-
"PowerOff",
|
|
2827
|
-
"PrintScreen",
|
|
2828
|
-
"Hibernate",
|
|
2829
|
-
"Standby",
|
|
2830
|
-
"WakeUp",
|
|
2831
|
-
"AllCandidates",
|
|
2832
|
-
"Alphanumeric",
|
|
2833
|
-
"CodeInput",
|
|
2834
|
-
"Compose",
|
|
2835
|
-
"Convert",
|
|
2836
|
-
"Dead",
|
|
2837
|
-
"FinalMode",
|
|
2838
|
-
"GroupFirst",
|
|
2839
|
-
"GroupLast",
|
|
2840
|
-
"GroupNext",
|
|
2841
|
-
"GroupPrevious",
|
|
2842
|
-
"ModeChange",
|
|
2843
|
-
"NextCandidate",
|
|
2844
|
-
"NonConvert",
|
|
2845
|
-
"PreviousCandidate",
|
|
2846
|
-
"Process",
|
|
2847
|
-
"SingleCandidate",
|
|
2848
|
-
"HangulMode",
|
|
2849
|
-
"HanjaMode",
|
|
2850
|
-
"JunjaMode",
|
|
2851
|
-
"Eisu",
|
|
2852
|
-
"Hankaku",
|
|
2853
|
-
"Hiragana",
|
|
2854
|
-
"HiraganaKatakana",
|
|
2855
|
-
"KanaMode",
|
|
2856
|
-
"KanjiMode",
|
|
2857
|
-
"Katakana",
|
|
2858
|
-
"Romaji",
|
|
2859
|
-
"Zenkaku",
|
|
2860
|
-
"ZenkakuHanaku",
|
|
2861
|
-
"F1",
|
|
2862
|
-
"F2",
|
|
2863
|
-
"F3",
|
|
2864
|
-
"F4",
|
|
2865
|
-
"F5",
|
|
2866
|
-
"F6",
|
|
2867
|
-
"F7",
|
|
2868
|
-
"F8",
|
|
2869
|
-
"F9",
|
|
2870
|
-
"F10",
|
|
2871
|
-
"F11",
|
|
2872
|
-
"F12",
|
|
2873
|
-
"Soft1",
|
|
2874
|
-
"Soft2",
|
|
2875
|
-
"Soft3",
|
|
2876
|
-
"Soft4",
|
|
2877
|
-
"ChannelDown",
|
|
2878
|
-
"ChannelUp",
|
|
2879
|
-
"Close",
|
|
2880
|
-
"MailForward",
|
|
2881
|
-
"MailReply",
|
|
2882
|
-
"MailSend",
|
|
2883
|
-
"MediaFastForward",
|
|
2884
|
-
"MediaPause",
|
|
2885
|
-
"MediaPlay",
|
|
2886
|
-
"MediaPlayPause",
|
|
2887
|
-
"MediaRecord",
|
|
2888
|
-
"MediaRewind",
|
|
2889
|
-
"MediaStop",
|
|
2890
|
-
"MediaTrackNext",
|
|
2891
|
-
"MediaTrackPrevious",
|
|
2892
|
-
"AudioBalanceLeft",
|
|
2893
|
-
"AudioBalanceRight",
|
|
2894
|
-
"AudioBassBoostDown",
|
|
2895
|
-
"AudioBassBoostToggle",
|
|
2896
|
-
"AudioBassBoostUp",
|
|
2897
|
-
"AudioFaderFront",
|
|
2898
|
-
"AudioFaderRear",
|
|
2899
|
-
"AudioSurroundModeNext",
|
|
2900
|
-
"AudioTrebleDown",
|
|
2901
|
-
"AudioTrebleUp",
|
|
2902
|
-
"AudioVolumeDown",
|
|
2903
|
-
"AudioVolumeMute",
|
|
2904
|
-
"AudioVolumeUp",
|
|
2905
|
-
"MicrophoneToggle",
|
|
2906
|
-
"MicrophoneVolumeDown",
|
|
2907
|
-
"MicrophoneVolumeMute",
|
|
2908
|
-
"MicrophoneVolumeUp",
|
|
2909
|
-
"TV",
|
|
2910
|
-
"TV3DMode",
|
|
2911
|
-
"TVAntennaCable",
|
|
2912
|
-
"TVAudioDescription",
|
|
2913
|
-
];
|
|
2914
3321
|
export { StableBrowser };
|
|
2915
3322
|
//# sourceMappingURL=stable_browser.js.map
|