automation_model 1.0.411-dev → 1.0.411
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/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 +2 -1
- package/lib/auto_page.js +43 -17
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +7 -3
- package/lib/browser_manager.js +110 -39
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +6 -0
- package/lib/command_common.js +167 -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 +200 -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 +3 -1
- package/lib/init_browser.js +67 -4
- 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 +178 -0
- package/lib/network.js.map +1 -0
- package/lib/scripts/axe.mini.js +12 -0
- package/lib/stable_browser.d.ts +84 -36
- package/lib/stable_browser.js +1207 -1238
- 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/test_context.d.ts +4 -0
- package/lib/test_context.js +12 -9
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +15 -1
- package/lib/utils.js +410 -5
- package/lib/utils.js.map +1 -1
- package/package.json +13 -8
package/lib/stable_browser.js
CHANGED
|
@@ -2,19 +2,25 @@
|
|
|
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, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, } 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 } 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
|
+
export const Types = {
|
|
18
24
|
CLICK: "click_element",
|
|
19
25
|
NAVIGATE: "navigate",
|
|
20
26
|
FILL: "fill_element",
|
|
@@ -25,6 +31,8 @@ const Types = {
|
|
|
25
31
|
GET_PAGE_STATUS: "get_page_status",
|
|
26
32
|
CLICK_ROW_ACTION: "click_row_action",
|
|
27
33
|
VERIFY_ELEMENT_CONTAINS_TEXT: "verify_element_contains_text",
|
|
34
|
+
VERIFY_PAGE_CONTAINS_TEXT: "verify_page_contains_text",
|
|
35
|
+
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
28
36
|
ANALYZE_TABLE: "analyze_table",
|
|
29
37
|
SELECT: "select_combobox",
|
|
30
38
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
@@ -40,16 +48,29 @@ const Types = {
|
|
|
40
48
|
VERIFY_VISUAL: "verify_visual",
|
|
41
49
|
LOAD_DATA: "load_data",
|
|
42
50
|
SET_INPUT: "set_input",
|
|
51
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
52
|
+
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
53
|
+
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
43
54
|
};
|
|
55
|
+
export const apps = {};
|
|
44
56
|
class StableBrowser {
|
|
45
|
-
|
|
57
|
+
browser;
|
|
58
|
+
page;
|
|
59
|
+
logger;
|
|
60
|
+
context;
|
|
61
|
+
world;
|
|
62
|
+
project_path = null;
|
|
63
|
+
webLogFile = null;
|
|
64
|
+
networkLogger = null;
|
|
65
|
+
configuration = null;
|
|
66
|
+
appName = "main";
|
|
67
|
+
tags = null;
|
|
68
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
46
69
|
this.browser = browser;
|
|
47
70
|
this.page = page;
|
|
48
71
|
this.logger = logger;
|
|
49
72
|
this.context = context;
|
|
50
|
-
this.
|
|
51
|
-
this.webLogFile = null;
|
|
52
|
-
this.configuration = null;
|
|
73
|
+
this.world = world;
|
|
53
74
|
if (!this.logger) {
|
|
54
75
|
this.logger = console;
|
|
55
76
|
}
|
|
@@ -75,23 +96,43 @@ class StableBrowser {
|
|
|
75
96
|
this.logger.error("unable to read ai_config.json");
|
|
76
97
|
}
|
|
77
98
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
78
|
-
this.
|
|
79
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
80
|
-
this.registerRequestListener();
|
|
99
|
+
this.world = world;
|
|
81
100
|
context.pages = [this.page];
|
|
82
101
|
context.pageLoading = { status: false };
|
|
102
|
+
this.registerEventListeners(this.context);
|
|
103
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
104
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
105
|
+
}
|
|
106
|
+
registerEventListeners(context) {
|
|
107
|
+
this.registerConsoleLogListener(this.page, context);
|
|
108
|
+
// this.registerRequestListener(this.page, context, this.webLogFile);
|
|
109
|
+
if (!context.pageLoading) {
|
|
110
|
+
context.pageLoading = { status: false };
|
|
111
|
+
}
|
|
83
112
|
context.playContext.on("page", async function (page) {
|
|
113
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
114
|
+
console.log("close unexpected popups");
|
|
115
|
+
await page.close();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
84
118
|
context.pageLoading.status = true;
|
|
85
119
|
this.page = page;
|
|
86
120
|
context.page = page;
|
|
87
121
|
context.pages.push(page);
|
|
122
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
123
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
88
124
|
page.on("close", async () => {
|
|
89
|
-
if (this.context && this.context.pages && this.context.pages.length >
|
|
125
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
90
126
|
this.context.pages.pop();
|
|
91
127
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
92
128
|
this.context.page = this.page;
|
|
93
|
-
|
|
94
|
-
|
|
129
|
+
try {
|
|
130
|
+
let title = await this.page.title();
|
|
131
|
+
console.log("Switched to page " + title);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error("Error on page close", error);
|
|
135
|
+
}
|
|
95
136
|
}
|
|
96
137
|
});
|
|
97
138
|
try {
|
|
@@ -104,117 +145,136 @@ class StableBrowser {
|
|
|
104
145
|
context.pageLoading.status = false;
|
|
105
146
|
}.bind(this));
|
|
106
147
|
}
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
|
|
148
|
+
async switchApp(appName) {
|
|
149
|
+
// check if the current app (this.appName) is the same as the new app
|
|
150
|
+
if (this.appName === appName) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
let navigate = false;
|
|
154
|
+
if (!apps[appName]) {
|
|
155
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
156
|
+
newContextCreated = true;
|
|
157
|
+
apps[appName] = {
|
|
158
|
+
context: newContext,
|
|
159
|
+
browser: newContext.browser,
|
|
160
|
+
page: newContext.page,
|
|
161
|
+
};
|
|
110
162
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
163
|
+
const tempContext = {};
|
|
164
|
+
_copyContext(this, tempContext);
|
|
165
|
+
_copyContext(apps[appName], this);
|
|
166
|
+
apps[this.appName] = tempContext;
|
|
167
|
+
this.appName = appName;
|
|
168
|
+
if (navigate) {
|
|
169
|
+
await this.goto(this.context.environment.baseUrl);
|
|
170
|
+
await this.waitForPageLoad();
|
|
114
171
|
}
|
|
115
|
-
const fileName = nextIndex + ".json";
|
|
116
|
-
return path.join(logFolder, fileName);
|
|
117
172
|
}
|
|
118
|
-
registerConsoleLogListener(page, context
|
|
173
|
+
registerConsoleLogListener(page, context) {
|
|
119
174
|
if (!this.context.webLogger) {
|
|
120
175
|
this.context.webLogger = [];
|
|
121
176
|
}
|
|
122
177
|
page.on("console", async (msg) => {
|
|
123
|
-
|
|
178
|
+
const obj = {
|
|
124
179
|
type: msg.type(),
|
|
125
180
|
text: msg.text(),
|
|
126
181
|
location: msg.location(),
|
|
127
182
|
time: new Date().toISOString(),
|
|
128
|
-
}
|
|
129
|
-
|
|
183
|
+
};
|
|
184
|
+
this.context.webLogger.push(obj);
|
|
185
|
+
if (msg.type() === "error") {
|
|
186
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
187
|
+
}
|
|
130
188
|
});
|
|
131
189
|
}
|
|
132
|
-
registerRequestListener() {
|
|
133
|
-
this.
|
|
190
|
+
registerRequestListener(page, context, logFile) {
|
|
191
|
+
if (!this.context.networkLogger) {
|
|
192
|
+
this.context.networkLogger = [];
|
|
193
|
+
}
|
|
194
|
+
page.on("request", async (data) => {
|
|
195
|
+
const startTime = new Date().getTime();
|
|
134
196
|
try {
|
|
135
|
-
const pageUrl = new URL(
|
|
197
|
+
const pageUrl = new URL(page.url());
|
|
136
198
|
const requestUrl = new URL(data.url());
|
|
137
199
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
138
200
|
const method = data.method();
|
|
139
|
-
if (
|
|
201
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
140
202
|
const token = await data.headerValue("Authorization");
|
|
141
203
|
if (token) {
|
|
142
|
-
|
|
204
|
+
context.authtoken = token;
|
|
143
205
|
}
|
|
144
206
|
}
|
|
145
207
|
}
|
|
208
|
+
const response = await data.response();
|
|
209
|
+
const endTime = new Date().getTime();
|
|
210
|
+
const obj = {
|
|
211
|
+
url: data.url(),
|
|
212
|
+
method: data.method(),
|
|
213
|
+
postData: data.postData(),
|
|
214
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
215
|
+
duration: endTime - startTime,
|
|
216
|
+
startTime,
|
|
217
|
+
};
|
|
218
|
+
context.networkLogger.push(obj);
|
|
219
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
146
220
|
}
|
|
147
221
|
catch (error) {
|
|
148
|
-
console.error("Error in request listener", error);
|
|
222
|
+
// console.error("Error in request listener", error);
|
|
223
|
+
context.networkLogger.push({
|
|
224
|
+
error: "not able to listen",
|
|
225
|
+
message: error.message,
|
|
226
|
+
stack: error.stack,
|
|
227
|
+
time: new Date().toISOString(),
|
|
228
|
+
});
|
|
229
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
149
230
|
}
|
|
150
231
|
});
|
|
151
232
|
}
|
|
152
233
|
// async closeUnexpectedPopups() {
|
|
153
234
|
// await closeUnexpectedPopups(this.page);
|
|
154
235
|
// }
|
|
155
|
-
async goto(url) {
|
|
236
|
+
async goto(url, world = null) {
|
|
156
237
|
if (!url.startsWith("http")) {
|
|
157
238
|
url = "https://" + url;
|
|
158
239
|
}
|
|
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;
|
|
240
|
+
const state = {
|
|
241
|
+
value: url,
|
|
242
|
+
world: world,
|
|
243
|
+
type: Types.NAVIGATE,
|
|
244
|
+
text: `Navigate Page to: ${url}`,
|
|
245
|
+
operation: "goto",
|
|
246
|
+
log: "***** navigate page to " + url + " *****\n",
|
|
247
|
+
info: {},
|
|
248
|
+
locate: false,
|
|
249
|
+
scroll: false,
|
|
250
|
+
screenshot: false,
|
|
251
|
+
highlight: false,
|
|
252
|
+
};
|
|
253
|
+
try {
|
|
254
|
+
await _preCommand(state, this);
|
|
255
|
+
await this.page.goto(url, {
|
|
256
|
+
timeout: 60000,
|
|
257
|
+
});
|
|
258
|
+
await _screenshot(state, this);
|
|
180
259
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
// remove the _ prefix
|
|
185
|
-
regValue = key.substring(1);
|
|
186
|
-
}
|
|
187
|
-
text = text.replaceAll(new RegExp("{" + regValue + "}", "g"), _params[key]);
|
|
260
|
+
catch (error) {
|
|
261
|
+
console.error("Error on goto", error);
|
|
262
|
+
_commandError(state, error, this);
|
|
188
263
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
_fixLocatorUsingParams(locator, _params) {
|
|
192
|
-
// check if not null
|
|
193
|
-
if (!locator) {
|
|
194
|
-
return locator;
|
|
264
|
+
finally {
|
|
265
|
+
_commandFinally(state, this);
|
|
195
266
|
}
|
|
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
267
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
this.scanAndManipulate(currentObj[key], _params);
|
|
268
|
+
async _getLocator(locator, scope, _params) {
|
|
269
|
+
locator = _fixLocatorUsingParams(locator, _params);
|
|
270
|
+
// locator = await this._replaceWithLocalData(locator);
|
|
271
|
+
for (let key in locator) {
|
|
272
|
+
if (typeof locator[key] !== "string")
|
|
273
|
+
continue;
|
|
274
|
+
if (locator[key].includes("{{") && locator[key].includes("}}")) {
|
|
275
|
+
locator[key] = await this._replaceWithLocalData(locator[key], this.world);
|
|
213
276
|
}
|
|
214
277
|
}
|
|
215
|
-
}
|
|
216
|
-
_getLocator(locator, scope, _params) {
|
|
217
|
-
locator = this._fixLocatorUsingParams(locator, _params);
|
|
218
278
|
let locatorReturn;
|
|
219
279
|
if (locator.role) {
|
|
220
280
|
if (locator.role[1].nameReg) {
|
|
@@ -222,11 +282,11 @@ class StableBrowser {
|
|
|
222
282
|
delete locator.role[1].nameReg;
|
|
223
283
|
}
|
|
224
284
|
// if (locator.role[1].name) {
|
|
225
|
-
// locator.role[1].name =
|
|
285
|
+
// locator.role[1].name = _fixUsingParams(locator.role[1].name, _params);
|
|
226
286
|
// }
|
|
227
287
|
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
228
288
|
}
|
|
229
|
-
if (locator.css
|
|
289
|
+
if (locator.css) {
|
|
230
290
|
locatorReturn = scope.locator(locator.css);
|
|
231
291
|
}
|
|
232
292
|
// handle role/name locators
|
|
@@ -241,209 +301,190 @@ class StableBrowser {
|
|
|
241
301
|
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
242
302
|
}
|
|
243
303
|
}
|
|
244
|
-
if (locator
|
|
245
|
-
if (locator.engine === "
|
|
246
|
-
|
|
304
|
+
if (locator?.engine) {
|
|
305
|
+
if (locator.engine === "css") {
|
|
306
|
+
locatorReturn = scope.locator(locator.selector);
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
let selector = locator.selector;
|
|
310
|
+
if (locator.engine === "internal:attr") {
|
|
311
|
+
if (!selector.startsWith("[")) {
|
|
312
|
+
selector = `[${selector}]`;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
247
316
|
}
|
|
248
|
-
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
249
317
|
}
|
|
250
318
|
if (!locatorReturn) {
|
|
251
319
|
console.error(locator);
|
|
252
320
|
throw new Error("Locator undefined");
|
|
253
321
|
}
|
|
254
|
-
else {
|
|
255
|
-
const count = locatorReturn.count();
|
|
256
|
-
if (count === 0) {
|
|
257
|
-
throw new Error("Elements not found");
|
|
258
|
-
}
|
|
259
|
-
else if (count > 1) {
|
|
260
|
-
throw new Error("Multiple elements found");
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
322
|
return locatorReturn;
|
|
264
323
|
}
|
|
265
324
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
266
|
-
|
|
325
|
+
if (css && css.locator) {
|
|
326
|
+
css = css.locator;
|
|
327
|
+
}
|
|
328
|
+
let result = await this._locateElementByText(scope, _fixUsingParams(text, _params), "*:not(script, style, head)", false, false, true, _params);
|
|
267
329
|
if (result.elementCount === 0) {
|
|
268
330
|
return;
|
|
269
331
|
}
|
|
270
|
-
let textElementCss = "[data-blinq-id
|
|
332
|
+
let textElementCss = "[data-blinq-id-" + result.randomToken + "]";
|
|
271
333
|
// css climb to parent element
|
|
272
334
|
const climbArray = [];
|
|
273
335
|
for (let i = 0; i < climb; i++) {
|
|
274
336
|
climbArray.push("..");
|
|
275
337
|
}
|
|
276
338
|
let climbXpath = "xpath=" + climbArray.join("/");
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return
|
|
282
|
-
function isParent(parent, child) {
|
|
283
|
-
let currentNode = child.parentNode;
|
|
284
|
-
while (currentNode !== null) {
|
|
285
|
-
if (currentNode === parent) {
|
|
286
|
-
return true;
|
|
287
|
-
}
|
|
288
|
-
currentNode = currentNode.parentNode;
|
|
289
|
-
}
|
|
290
|
-
return false;
|
|
291
|
-
}
|
|
292
|
-
document.isParent = isParent;
|
|
293
|
-
function collectAllShadowDomElements(element, result = []) {
|
|
294
|
-
// Check and add the element if it has a shadow root
|
|
295
|
-
if (element.shadowRoot) {
|
|
296
|
-
result.push(element);
|
|
297
|
-
// Also search within the shadow root
|
|
298
|
-
document.collectAllShadowDomElements(element.shadowRoot, result);
|
|
299
|
-
}
|
|
300
|
-
// Iterate over child nodes
|
|
301
|
-
element.childNodes.forEach((child) => {
|
|
302
|
-
// Recursively call the function for each child node
|
|
303
|
-
document.collectAllShadowDomElements(child, result);
|
|
304
|
-
});
|
|
305
|
-
return result;
|
|
306
|
-
}
|
|
307
|
-
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
308
|
-
if (!tag) {
|
|
309
|
-
tag = "*";
|
|
310
|
-
}
|
|
311
|
-
let elements = Array.from(document.querySelectorAll(tag));
|
|
312
|
-
let shadowHosts = [];
|
|
313
|
-
document.collectAllShadowDomElements(document, shadowHosts);
|
|
314
|
-
for (let i = 0; i < shadowHosts.length; i++) {
|
|
315
|
-
let shadowElement = shadowHosts[i].shadowRoot;
|
|
316
|
-
if (!shadowElement) {
|
|
317
|
-
console.log("shadowElement is null, for host " + shadowHosts[i]);
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
let shadowElements = Array.from(shadowElement.querySelectorAll(tag));
|
|
321
|
-
elements = elements.concat(shadowElements);
|
|
322
|
-
}
|
|
323
|
-
let randomToken = null;
|
|
324
|
-
const foundElements = [];
|
|
325
|
-
if (regex) {
|
|
326
|
-
let regexpSearch = new RegExp(text, "im");
|
|
327
|
-
for (let i = 0; i < elements.length; i++) {
|
|
328
|
-
const element = elements[i];
|
|
329
|
-
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
330
|
-
(element.value && regexpSearch.test(element.value))) {
|
|
331
|
-
foundElements.push(element);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
text = text.trim();
|
|
337
|
-
for (let i = 0; i < elements.length; i++) {
|
|
338
|
-
const element = elements[i];
|
|
339
|
-
if (partial) {
|
|
340
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
341
|
-
(element.value && element.value.includes(text))) {
|
|
342
|
-
foundElements.push(element);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
if ((element.innerText && element.innerText.trim() === text) ||
|
|
347
|
-
(element.value && element.value === text)) {
|
|
348
|
-
foundElements.push(element);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
let noChildElements = [];
|
|
354
|
-
for (let i = 0; i < foundElements.length; i++) {
|
|
355
|
-
let element = foundElements[i];
|
|
356
|
-
let hasChild = false;
|
|
357
|
-
for (let j = 0; j < foundElements.length; j++) {
|
|
358
|
-
if (i === j) {
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
if (isParent(element, foundElements[j])) {
|
|
362
|
-
hasChild = true;
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
if (!hasChild) {
|
|
367
|
-
noChildElements.push(element);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
let elementCount = 0;
|
|
371
|
-
if (noChildElements.length > 0) {
|
|
372
|
-
for (let i = 0; i < noChildElements.length; i++) {
|
|
373
|
-
if (randomToken === null) {
|
|
374
|
-
randomToken = Math.random().toString(36).substring(7);
|
|
375
|
-
}
|
|
376
|
-
let element = noChildElements[i];
|
|
377
|
-
element.setAttribute("data-blinq-id", "blinq-id-" + randomToken);
|
|
378
|
-
elementCount++;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
return { elementCount: elementCount, randomToken: randomToken };
|
|
382
|
-
}, [text1, tag1, regex1, partial1]);
|
|
339
|
+
let resultCss = textElementCss + " >> " + climbXpath;
|
|
340
|
+
if (css) {
|
|
341
|
+
resultCss = resultCss + " >> " + css;
|
|
342
|
+
}
|
|
343
|
+
return resultCss;
|
|
383
344
|
}
|
|
384
|
-
async
|
|
345
|
+
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, ignoreCase = true, _params) {
|
|
346
|
+
const query = _convertToRegexQuery(text1, regex1, !partial1, ignoreCase);
|
|
347
|
+
const locator = scope.locator(query);
|
|
348
|
+
const count = await locator.count();
|
|
349
|
+
if (!tag1) {
|
|
350
|
+
tag1 = "*";
|
|
351
|
+
}
|
|
352
|
+
const randomToken = Math.random().toString(36).substring(7);
|
|
353
|
+
let tagCount = 0;
|
|
354
|
+
for (let i = 0; i < count; i++) {
|
|
355
|
+
const element = locator.nth(i);
|
|
356
|
+
// check if the tag matches
|
|
357
|
+
if (!(await element.evaluate((el, [tag, randomToken]) => {
|
|
358
|
+
if (!tag.startsWith("*")) {
|
|
359
|
+
if (el.tagName.toLowerCase() !== tag) {
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (!el.setAttribute) {
|
|
364
|
+
el = el.parentElement;
|
|
365
|
+
}
|
|
366
|
+
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
367
|
+
return true;
|
|
368
|
+
}, [tag1, randomToken]))) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
tagCount++;
|
|
372
|
+
}
|
|
373
|
+
return { elementCount: tagCount, randomToken };
|
|
374
|
+
}
|
|
375
|
+
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true, allowDisabled = false) {
|
|
376
|
+
if (!info) {
|
|
377
|
+
info = {};
|
|
378
|
+
}
|
|
379
|
+
if (!info.failCause) {
|
|
380
|
+
info.failCause = {};
|
|
381
|
+
}
|
|
382
|
+
if (!info.log) {
|
|
383
|
+
info.log = "";
|
|
384
|
+
info.locatorLog = new LocatorLog(selectorHierarchy);
|
|
385
|
+
}
|
|
385
386
|
let locatorSearch = selectorHierarchy[index];
|
|
387
|
+
let originalLocatorSearch = "";
|
|
388
|
+
try {
|
|
389
|
+
originalLocatorSearch = _fixUsingParams(JSON.stringify(locatorSearch), _params);
|
|
390
|
+
locatorSearch = JSON.parse(originalLocatorSearch);
|
|
391
|
+
}
|
|
392
|
+
catch (e) {
|
|
393
|
+
console.error(e);
|
|
394
|
+
}
|
|
386
395
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
387
396
|
let locator = null;
|
|
388
397
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
389
398
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
390
399
|
if (!locatorString) {
|
|
400
|
+
info.failCause.textNotFound = true;
|
|
401
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
391
402
|
return;
|
|
392
403
|
}
|
|
393
|
-
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
404
|
+
locator = await this._getLocator({ css: locatorString }, scope, _params);
|
|
394
405
|
}
|
|
395
406
|
else if (locatorSearch.text) {
|
|
396
|
-
let
|
|
407
|
+
let text = _fixUsingParams(locatorSearch.text, _params);
|
|
408
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, true, _params);
|
|
397
409
|
if (result.elementCount === 0) {
|
|
410
|
+
info.failCause.textNotFound = true;
|
|
411
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
398
412
|
return;
|
|
399
413
|
}
|
|
400
|
-
locatorSearch.css = "[data-blinq-id
|
|
414
|
+
locatorSearch.css = "[data-blinq-id-" + result.randomToken + "]";
|
|
401
415
|
if (locatorSearch.childCss) {
|
|
402
416
|
locatorSearch.css = locatorSearch.css + " " + locatorSearch.childCss;
|
|
403
417
|
}
|
|
404
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
418
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
405
419
|
}
|
|
406
420
|
else {
|
|
407
|
-
locator = this._getLocator(locatorSearch, scope, _params);
|
|
421
|
+
locator = await this._getLocator(locatorSearch, scope, _params);
|
|
408
422
|
}
|
|
409
423
|
// let cssHref = false;
|
|
410
424
|
// if (locatorSearch.css && locatorSearch.css.includes("href=")) {
|
|
411
425
|
// cssHref = true;
|
|
412
426
|
// }
|
|
413
427
|
let count = await locator.count();
|
|
428
|
+
if (count > 0 && !info.failCause.count) {
|
|
429
|
+
info.failCause.count = count;
|
|
430
|
+
}
|
|
414
431
|
//info.log += "total elements found " + count + "\n";
|
|
415
432
|
//let visibleCount = 0;
|
|
416
433
|
let visibleLocator = null;
|
|
417
|
-
if (locatorSearch.index && locatorSearch.index < count) {
|
|
434
|
+
if (typeof locatorSearch.index === "number" && locatorSearch.index < count) {
|
|
418
435
|
foundLocators.push(locator.nth(locatorSearch.index));
|
|
436
|
+
if (info.locatorLog) {
|
|
437
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
438
|
+
}
|
|
419
439
|
return;
|
|
420
440
|
}
|
|
441
|
+
if (info.locatorLog && count === 0) {
|
|
442
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "NOT_FOUND");
|
|
443
|
+
}
|
|
421
444
|
for (let j = 0; j < count; j++) {
|
|
422
445
|
let visible = await locator.nth(j).isVisible();
|
|
423
446
|
const enabled = await locator.nth(j).isEnabled();
|
|
424
447
|
if (!visibleOnly) {
|
|
425
448
|
visible = true;
|
|
426
449
|
}
|
|
427
|
-
if (visible && enabled) {
|
|
450
|
+
if (visible && (allowDisabled || enabled)) {
|
|
428
451
|
foundLocators.push(locator.nth(j));
|
|
452
|
+
if (info.locatorLog) {
|
|
453
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND");
|
|
454
|
+
}
|
|
429
455
|
}
|
|
430
456
|
else {
|
|
457
|
+
info.failCause.visible = visible;
|
|
458
|
+
info.failCause.enabled = enabled;
|
|
431
459
|
if (!info.printMessages) {
|
|
432
460
|
info.printMessages = {};
|
|
433
461
|
}
|
|
462
|
+
if (info.locatorLog && !visible) {
|
|
463
|
+
info.failCause.lastError = "element " + originalLocatorSearch + " is not visible";
|
|
464
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_VISIBLE");
|
|
465
|
+
}
|
|
466
|
+
if (info.locatorLog && !enabled) {
|
|
467
|
+
info.failCause.lastError = "element " + originalLocatorSearch + " is disabled, ";
|
|
468
|
+
info.locatorLog.setLocatorSearchStatus(originalLocatorSearch, "FOUND_NOT_ENABLED");
|
|
469
|
+
}
|
|
434
470
|
if (!info.printMessages[j.toString()]) {
|
|
435
|
-
info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
471
|
+
//info.log += "element " + locator + " visible " + visible + " enabled " + enabled + "\n";
|
|
436
472
|
info.printMessages[j.toString()] = true;
|
|
437
473
|
}
|
|
438
474
|
}
|
|
439
475
|
}
|
|
440
476
|
}
|
|
441
477
|
async closeUnexpectedPopups(info, _params) {
|
|
478
|
+
if (!info) {
|
|
479
|
+
info = {};
|
|
480
|
+
info.failCause = {};
|
|
481
|
+
info.log = "";
|
|
482
|
+
}
|
|
442
483
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
443
484
|
if (!info) {
|
|
444
485
|
info = {};
|
|
445
486
|
}
|
|
446
|
-
info.log += "scan for popup handlers" + "\n";
|
|
487
|
+
//info.log += "scan for popup handlers" + "\n";
|
|
447
488
|
const handlerGroup = [];
|
|
448
489
|
for (let i = 0; i < this.configuration.popupHandlers.length; i++) {
|
|
449
490
|
handlerGroup.push(this.configuration.popupHandlers[i].locator);
|
|
@@ -470,42 +511,95 @@ class StableBrowser {
|
|
|
470
511
|
}
|
|
471
512
|
if (result.foundElements.length > 0) {
|
|
472
513
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
473
|
-
|
|
514
|
+
try {
|
|
515
|
+
await scope?.evaluate(() => {
|
|
516
|
+
window.__isClosingPopups = true;
|
|
517
|
+
});
|
|
518
|
+
await dialogCloseLocator.click();
|
|
519
|
+
// wait for the dialog to close
|
|
520
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
}
|
|
524
|
+
finally {
|
|
525
|
+
await scope?.evaluate(() => {
|
|
526
|
+
window.__isClosingPopups = false;
|
|
527
|
+
});
|
|
528
|
+
}
|
|
474
529
|
return { rerun: true };
|
|
475
530
|
}
|
|
476
531
|
}
|
|
477
532
|
}
|
|
478
533
|
return { rerun: false };
|
|
479
534
|
}
|
|
480
|
-
async _locate(selectors, info, _params, timeout =
|
|
535
|
+
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
536
|
+
if (!timeout) {
|
|
537
|
+
timeout = 30000;
|
|
538
|
+
}
|
|
481
539
|
for (let i = 0; i < 3; i++) {
|
|
482
|
-
info.log += "attempt " + i + ":
|
|
540
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
483
541
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
484
542
|
let selector = selectors.locators[j];
|
|
485
543
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
486
544
|
}
|
|
487
|
-
let element = await this._locate_internal(selectors, info, _params, timeout);
|
|
545
|
+
let element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
488
546
|
if (!element.rerun) {
|
|
489
547
|
return element;
|
|
490
548
|
}
|
|
491
549
|
}
|
|
492
550
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
493
551
|
}
|
|
494
|
-
async
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
552
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
553
|
+
if (!info) {
|
|
554
|
+
info = {};
|
|
555
|
+
info.failCause = {};
|
|
556
|
+
info.log = "";
|
|
557
|
+
}
|
|
558
|
+
let startTime = Date.now();
|
|
500
559
|
let scope = this.page;
|
|
560
|
+
if (selectors.frame) {
|
|
561
|
+
return selectors.frame;
|
|
562
|
+
}
|
|
501
563
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
502
|
-
|
|
564
|
+
const findFrame = async (frame, framescope) => {
|
|
565
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
566
|
+
let frameLocator = frame.selectors[i];
|
|
567
|
+
if (frameLocator.css) {
|
|
568
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
569
|
+
if (frameLocator.index) {
|
|
570
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
574
|
+
timeout: 5000,
|
|
575
|
+
});
|
|
576
|
+
framescope = testframescope;
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
catch (error) {
|
|
580
|
+
console.error("frame not found " + frameLocator.css);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (frame.children) {
|
|
585
|
+
return await findFrame(frame.children, framescope);
|
|
586
|
+
}
|
|
587
|
+
return framescope;
|
|
588
|
+
};
|
|
589
|
+
let fLocator = null;
|
|
503
590
|
while (true) {
|
|
504
591
|
let frameFound = false;
|
|
592
|
+
if (selectors.nestFrmLoc) {
|
|
593
|
+
fLocator = selectors.nestFrmLoc;
|
|
594
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
595
|
+
frameFound = true;
|
|
596
|
+
break;
|
|
597
|
+
}
|
|
505
598
|
if (selectors.frameLocators) {
|
|
506
599
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
507
600
|
let frameLocator = selectors.frameLocators[i];
|
|
508
601
|
if (frameLocator.css) {
|
|
602
|
+
fLocator = frameLocator.css;
|
|
509
603
|
scope = scope.frameLocator(frameLocator.css);
|
|
510
604
|
frameFound = true;
|
|
511
605
|
break;
|
|
@@ -513,20 +607,55 @@ class StableBrowser {
|
|
|
513
607
|
}
|
|
514
608
|
}
|
|
515
609
|
if (!frameFound && selectors.iframe_src) {
|
|
610
|
+
fLocator = selectors.iframe_src;
|
|
516
611
|
scope = this.page.frame({ url: selectors.iframe_src });
|
|
517
612
|
}
|
|
518
613
|
if (!scope) {
|
|
519
|
-
info
|
|
520
|
-
|
|
614
|
+
if (info && info.locatorLog) {
|
|
615
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "NOT_FOUND");
|
|
616
|
+
}
|
|
617
|
+
//info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
618
|
+
if (Date.now() - startTime > timeout) {
|
|
619
|
+
info.failCause.iframeNotFound = true;
|
|
620
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
521
621
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
522
622
|
}
|
|
523
623
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
524
624
|
}
|
|
525
625
|
else {
|
|
626
|
+
if (info && info.locatorLog) {
|
|
627
|
+
info.locatorLog.setLocatorSearchStatus("frame-" + fLocator, "FOUND");
|
|
628
|
+
}
|
|
526
629
|
break;
|
|
527
630
|
}
|
|
528
631
|
}
|
|
529
632
|
}
|
|
633
|
+
if (!scope) {
|
|
634
|
+
scope = this.page;
|
|
635
|
+
}
|
|
636
|
+
return scope;
|
|
637
|
+
}
|
|
638
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
639
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
640
|
+
return scope.evaluate(() => {
|
|
641
|
+
var bodyContent = document.body.innerHTML;
|
|
642
|
+
return bodyContent;
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
646
|
+
if (!info) {
|
|
647
|
+
info = {};
|
|
648
|
+
info.failCause = {};
|
|
649
|
+
info.log = "";
|
|
650
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
651
|
+
}
|
|
652
|
+
let highPriorityTimeout = 5000;
|
|
653
|
+
let visibleOnlyTimeout = 6000;
|
|
654
|
+
let startTime = Date.now();
|
|
655
|
+
let locatorsCount = 0;
|
|
656
|
+
let lazy_scroll = false;
|
|
657
|
+
//let arrayMode = Array.isArray(selectors);
|
|
658
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
530
659
|
let selectorsLocators = null;
|
|
531
660
|
selectorsLocators = selectors.locators;
|
|
532
661
|
// group selectors by priority
|
|
@@ -562,17 +691,17 @@ class StableBrowser {
|
|
|
562
691
|
}
|
|
563
692
|
// info.log += "scanning locators in priority 1" + "\n";
|
|
564
693
|
let onlyPriority3 = selectorsLocators[0].priority === 3;
|
|
565
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly);
|
|
694
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["1"], scope, _params, info, visibleOnly, allowDisabled);
|
|
566
695
|
if (result.foundElements.length === 0) {
|
|
567
696
|
// info.log += "scanning locators in priority 2" + "\n";
|
|
568
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly);
|
|
697
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["2"], scope, _params, info, visibleOnly, allowDisabled);
|
|
569
698
|
}
|
|
570
699
|
if (result.foundElements.length === 0 && onlyPriority3) {
|
|
571
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
700
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
572
701
|
}
|
|
573
702
|
else {
|
|
574
703
|
if (result.foundElements.length === 0 && !highPriorityOnly) {
|
|
575
|
-
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly);
|
|
704
|
+
result = await this._scanLocatorsGroup(locatorsByPriority["3"], scope, _params, info, visibleOnly, allowDisabled);
|
|
576
705
|
}
|
|
577
706
|
}
|
|
578
707
|
let foundElements = result.foundElements;
|
|
@@ -613,24 +742,38 @@ class StableBrowser {
|
|
|
613
742
|
return maxCountElement.locator;
|
|
614
743
|
}
|
|
615
744
|
}
|
|
616
|
-
if (
|
|
745
|
+
if (Date.now() - startTime > timeout) {
|
|
617
746
|
break;
|
|
618
747
|
}
|
|
619
|
-
if (
|
|
620
|
-
info.log += "high priority timeout, will try all elements" + "\n";
|
|
748
|
+
if (Date.now() - startTime > highPriorityTimeout) {
|
|
749
|
+
//info.log += "high priority timeout, will try all elements" + "\n";
|
|
621
750
|
highPriorityOnly = false;
|
|
751
|
+
if (this.configuration && this.configuration.load_all_lazy === true && !lazy_scroll) {
|
|
752
|
+
lazy_scroll = true;
|
|
753
|
+
await scrollPageToLoadLazyElements(this.page);
|
|
754
|
+
}
|
|
622
755
|
}
|
|
623
|
-
if (
|
|
624
|
-
info.log += "visible only timeout, will try all elements" + "\n";
|
|
756
|
+
if (Date.now() - startTime > visibleOnlyTimeout) {
|
|
757
|
+
//info.log += "visible only timeout, will try all elements" + "\n";
|
|
625
758
|
visibleOnly = false;
|
|
626
759
|
}
|
|
627
760
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
628
761
|
}
|
|
629
762
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
630
|
-
info.
|
|
763
|
+
// if (info.locatorLog) {
|
|
764
|
+
// const lines = info.locatorLog.toString().split("\n");
|
|
765
|
+
// for (let line of lines) {
|
|
766
|
+
// this.logger.debug(line);
|
|
767
|
+
// }
|
|
768
|
+
// }
|
|
769
|
+
//info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
770
|
+
info.failCause.locatorNotFound = true;
|
|
771
|
+
if (!info?.failCause?.lastError) {
|
|
772
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
773
|
+
}
|
|
631
774
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
632
775
|
}
|
|
633
|
-
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
776
|
+
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly, allowDisabled = false) {
|
|
634
777
|
let foundElements = [];
|
|
635
778
|
const result = {
|
|
636
779
|
foundElements: foundElements,
|
|
@@ -638,14 +781,15 @@ class StableBrowser {
|
|
|
638
781
|
for (let i = 0; i < locatorsGroup.length; i++) {
|
|
639
782
|
let foundLocators = [];
|
|
640
783
|
try {
|
|
641
|
-
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly);
|
|
784
|
+
await this._collectLocatorInformation(locatorsGroup, i, scope, foundLocators, _params, info, visibleOnly, allowDisabled);
|
|
642
785
|
}
|
|
643
786
|
catch (e) {
|
|
644
|
-
this
|
|
645
|
-
this.logger.debug(
|
|
787
|
+
// this call can fail it the browser is navigating
|
|
788
|
+
// this.logger.debug("unable to use locator " + JSON.stringify(locatorsGroup[i]));
|
|
789
|
+
// this.logger.debug(e);
|
|
646
790
|
foundLocators = [];
|
|
647
791
|
try {
|
|
648
|
-
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly);
|
|
792
|
+
await this._collectLocatorInformation(locatorsGroup, i, this.page, foundLocators, _params, info, visibleOnly, allowDisabled);
|
|
649
793
|
}
|
|
650
794
|
catch (e) {
|
|
651
795
|
this.logger.info("unable to use locator (second try) " + JSON.stringify(locatorsGroup[i]));
|
|
@@ -659,91 +803,168 @@ class StableBrowser {
|
|
|
659
803
|
});
|
|
660
804
|
result.locatorIndex = i;
|
|
661
805
|
}
|
|
806
|
+
if (foundLocators.length > 1) {
|
|
807
|
+
info.failCause.foundMultiple = true;
|
|
808
|
+
if (info.locatorLog) {
|
|
809
|
+
info.locatorLog.setLocatorSearchStatus(JSON.stringify(locatorsGroup[i]), "FOUND_NOT_UNIQUE");
|
|
810
|
+
}
|
|
811
|
+
}
|
|
662
812
|
}
|
|
663
813
|
return result;
|
|
664
814
|
}
|
|
665
|
-
async
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
815
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
816
|
+
const state = {
|
|
817
|
+
locate: false,
|
|
818
|
+
scroll: false,
|
|
819
|
+
highlight: false,
|
|
820
|
+
_params,
|
|
821
|
+
options,
|
|
822
|
+
world,
|
|
823
|
+
type: Types.CLICK,
|
|
824
|
+
text: "Click element",
|
|
825
|
+
operation: "simpleClick",
|
|
826
|
+
log: "***** click on " + elementDescription + " *****\n",
|
|
827
|
+
};
|
|
828
|
+
_preCommand(state, this);
|
|
829
|
+
const startTime = Date.now();
|
|
830
|
+
let timeout = 30000;
|
|
831
|
+
if (options && options.timeout) {
|
|
832
|
+
timeout = options.timeout;
|
|
833
|
+
}
|
|
834
|
+
while (true) {
|
|
835
|
+
try {
|
|
836
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
837
|
+
if (result?.elementNumber >= 0) {
|
|
838
|
+
const selectors = {
|
|
839
|
+
frame: result?.frame,
|
|
840
|
+
locators: [
|
|
841
|
+
{
|
|
842
|
+
css: result?.css,
|
|
843
|
+
},
|
|
844
|
+
],
|
|
845
|
+
};
|
|
846
|
+
await this.click(selectors, _params, options, world);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
catch (e) {
|
|
851
|
+
if (performance.now() - startTime > timeout) {
|
|
852
|
+
// throw e;
|
|
853
|
+
try {
|
|
854
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
855
|
+
}
|
|
856
|
+
finally {
|
|
857
|
+
_commandFinally(state, this);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
865
|
+
const state = {
|
|
866
|
+
locate: false,
|
|
867
|
+
scroll: false,
|
|
868
|
+
highlight: false,
|
|
869
|
+
_params,
|
|
870
|
+
options,
|
|
871
|
+
world,
|
|
872
|
+
type: Types.FILL,
|
|
873
|
+
text: "Fill element",
|
|
874
|
+
operation: "simpleClickType",
|
|
875
|
+
log: "***** click type on " + elementDescription + " *****\n",
|
|
876
|
+
};
|
|
877
|
+
_preCommand(state, this);
|
|
878
|
+
const startTime = Date.now();
|
|
879
|
+
let timeout = 30000;
|
|
880
|
+
if (options && options.timeout) {
|
|
881
|
+
timeout = options.timeout;
|
|
882
|
+
}
|
|
883
|
+
while (true) {
|
|
884
|
+
try {
|
|
885
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
886
|
+
if (result?.elementNumber >= 0) {
|
|
887
|
+
const selectors = {
|
|
888
|
+
frame: result?.frame,
|
|
889
|
+
locators: [
|
|
890
|
+
{
|
|
891
|
+
css: result?.css,
|
|
892
|
+
},
|
|
893
|
+
],
|
|
894
|
+
};
|
|
895
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
catch (e) {
|
|
900
|
+
if (performance.now() - startTime > timeout) {
|
|
901
|
+
// throw e;
|
|
902
|
+
try {
|
|
903
|
+
await _commandError(state, "timeout looking for " + elementDescription, this);
|
|
904
|
+
}
|
|
905
|
+
finally {
|
|
906
|
+
_commandFinally(state, this);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
911
|
+
}
|
|
669
912
|
}
|
|
670
913
|
async click(selectors, _params, options = {}, world = null) {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
914
|
+
const state = {
|
|
915
|
+
selectors,
|
|
916
|
+
_params,
|
|
917
|
+
options,
|
|
918
|
+
world,
|
|
919
|
+
text: "Click element",
|
|
920
|
+
type: Types.CLICK,
|
|
921
|
+
operation: "click",
|
|
922
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
923
|
+
};
|
|
680
924
|
try {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
925
|
+
await _preCommand(state, this);
|
|
926
|
+
// if (state.options && state.options.context) {
|
|
927
|
+
// state.selectors.locators[0].text = state.options.context;
|
|
928
|
+
// }
|
|
684
929
|
try {
|
|
685
|
-
await
|
|
686
|
-
await
|
|
687
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
930
|
+
await state.element.click();
|
|
931
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
688
932
|
}
|
|
689
933
|
catch (e) {
|
|
690
934
|
// await this.closeUnexpectedPopups();
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
await
|
|
694
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
935
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
936
|
+
await state.element.dispatchEvent("click");
|
|
937
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
695
938
|
}
|
|
696
939
|
await this.waitForPageLoad();
|
|
697
|
-
return info;
|
|
940
|
+
return state.info;
|
|
698
941
|
}
|
|
699
942
|
catch (e) {
|
|
700
|
-
|
|
701
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
702
|
-
info.screenshotPath = screenshotPath;
|
|
703
|
-
Object.assign(e, { info: info });
|
|
704
|
-
error = e;
|
|
705
|
-
throw e;
|
|
943
|
+
await _commandError(state, e, this);
|
|
706
944
|
}
|
|
707
945
|
finally {
|
|
708
|
-
|
|
709
|
-
this._reportToWorld(world, {
|
|
710
|
-
element_name: selectors.element_name,
|
|
711
|
-
type: Types.CLICK,
|
|
712
|
-
text: `Click element`,
|
|
713
|
-
screenshotId,
|
|
714
|
-
result: error
|
|
715
|
-
? {
|
|
716
|
-
status: "FAILED",
|
|
717
|
-
startTime,
|
|
718
|
-
endTime,
|
|
719
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
720
|
-
}
|
|
721
|
-
: {
|
|
722
|
-
status: "PASSED",
|
|
723
|
-
startTime,
|
|
724
|
-
endTime,
|
|
725
|
-
},
|
|
726
|
-
info: info,
|
|
727
|
-
});
|
|
946
|
+
_commandFinally(state, this);
|
|
728
947
|
}
|
|
729
948
|
}
|
|
730
949
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
950
|
+
const state = {
|
|
951
|
+
selectors,
|
|
952
|
+
_params,
|
|
953
|
+
options,
|
|
954
|
+
world,
|
|
955
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
956
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
957
|
+
operation: "setCheck",
|
|
958
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
959
|
+
};
|
|
741
960
|
try {
|
|
742
|
-
|
|
743
|
-
|
|
961
|
+
await _preCommand(state, this);
|
|
962
|
+
state.info.checked = checked;
|
|
963
|
+
// let element = await this._locate(selectors, info, _params);
|
|
964
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
744
965
|
try {
|
|
745
|
-
await this._highlightElements(element);
|
|
746
|
-
await element.setChecked(checked
|
|
966
|
+
// await this._highlightElements(element);
|
|
967
|
+
await state.element.setChecked(checked);
|
|
747
968
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
748
969
|
}
|
|
749
970
|
catch (e) {
|
|
@@ -752,179 +973,108 @@ class StableBrowser {
|
|
|
752
973
|
}
|
|
753
974
|
else {
|
|
754
975
|
//await this.closeUnexpectedPopups();
|
|
755
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
756
|
-
element = await this._locate(selectors, info, _params);
|
|
757
|
-
await element.setChecked(checked, { timeout: 5000, force: true });
|
|
976
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
977
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
978
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
758
979
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
759
980
|
}
|
|
760
981
|
}
|
|
761
982
|
await this.waitForPageLoad();
|
|
762
|
-
return info;
|
|
983
|
+
return state.info;
|
|
763
984
|
}
|
|
764
985
|
catch (e) {
|
|
765
|
-
|
|
766
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
767
|
-
info.screenshotPath = screenshotPath;
|
|
768
|
-
Object.assign(e, { info: info });
|
|
769
|
-
error = e;
|
|
770
|
-
throw e;
|
|
986
|
+
await _commandError(state, e, this);
|
|
771
987
|
}
|
|
772
988
|
finally {
|
|
773
|
-
|
|
774
|
-
this._reportToWorld(world, {
|
|
775
|
-
element_name: selectors.element_name,
|
|
776
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
777
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
778
|
-
screenshotId,
|
|
779
|
-
result: error
|
|
780
|
-
? {
|
|
781
|
-
status: "FAILED",
|
|
782
|
-
startTime,
|
|
783
|
-
endTime,
|
|
784
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
785
|
-
}
|
|
786
|
-
: {
|
|
787
|
-
status: "PASSED",
|
|
788
|
-
startTime,
|
|
789
|
-
endTime,
|
|
790
|
-
},
|
|
791
|
-
info: info,
|
|
792
|
-
});
|
|
989
|
+
_commandFinally(state, this);
|
|
793
990
|
}
|
|
794
991
|
}
|
|
795
992
|
async hover(selectors, _params, options = {}, world = null) {
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
993
|
+
const state = {
|
|
994
|
+
selectors,
|
|
995
|
+
_params,
|
|
996
|
+
options,
|
|
997
|
+
world,
|
|
998
|
+
type: Types.HOVER,
|
|
999
|
+
text: `Hover element`,
|
|
1000
|
+
operation: "hover",
|
|
1001
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1002
|
+
};
|
|
805
1003
|
try {
|
|
806
|
-
|
|
807
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1004
|
+
await _preCommand(state, this);
|
|
808
1005
|
try {
|
|
809
|
-
await
|
|
810
|
-
await element.hover({ timeout: 10000 });
|
|
1006
|
+
await state.element.hover();
|
|
811
1007
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
812
1008
|
}
|
|
813
1009
|
catch (e) {
|
|
814
1010
|
//await this.closeUnexpectedPopups();
|
|
815
|
-
info.log += "hover failed, will try again" + "\n";
|
|
816
|
-
element = await this._locate(selectors, info, _params);
|
|
817
|
-
await element.hover({ timeout: 10000 });
|
|
1011
|
+
state.info.log += "hover failed, will try again" + "\n";
|
|
1012
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
1013
|
+
await state.element.hover({ timeout: 10000 });
|
|
818
1014
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
819
1015
|
}
|
|
820
1016
|
await this.waitForPageLoad();
|
|
821
|
-
return info;
|
|
1017
|
+
return state.info;
|
|
822
1018
|
}
|
|
823
1019
|
catch (e) {
|
|
824
|
-
|
|
825
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
826
|
-
info.screenshotPath = screenshotPath;
|
|
827
|
-
Object.assign(e, { info: info });
|
|
828
|
-
error = e;
|
|
829
|
-
throw e;
|
|
1020
|
+
await _commandError(state, e, this);
|
|
830
1021
|
}
|
|
831
1022
|
finally {
|
|
832
|
-
|
|
833
|
-
this._reportToWorld(world, {
|
|
834
|
-
element_name: selectors.element_name,
|
|
835
|
-
type: Types.HOVER,
|
|
836
|
-
text: `Hover element`,
|
|
837
|
-
screenshotId,
|
|
838
|
-
result: error
|
|
839
|
-
? {
|
|
840
|
-
status: "FAILED",
|
|
841
|
-
startTime,
|
|
842
|
-
endTime,
|
|
843
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
844
|
-
}
|
|
845
|
-
: {
|
|
846
|
-
status: "PASSED",
|
|
847
|
-
startTime,
|
|
848
|
-
endTime,
|
|
849
|
-
},
|
|
850
|
-
info: info,
|
|
851
|
-
});
|
|
1023
|
+
_commandFinally(state, this);
|
|
852
1024
|
}
|
|
853
1025
|
}
|
|
854
1026
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
855
|
-
this._validateSelectors(selectors);
|
|
856
1027
|
if (!values) {
|
|
857
1028
|
throw new Error("values is null");
|
|
858
1029
|
}
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1030
|
+
const state = {
|
|
1031
|
+
selectors,
|
|
1032
|
+
_params,
|
|
1033
|
+
options,
|
|
1034
|
+
world,
|
|
1035
|
+
value: values.toString(),
|
|
1036
|
+
type: Types.SELECT,
|
|
1037
|
+
text: `Select option: ${values}`,
|
|
1038
|
+
operation: "selectOption",
|
|
1039
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1040
|
+
};
|
|
867
1041
|
try {
|
|
868
|
-
|
|
869
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1042
|
+
await _preCommand(state, this);
|
|
870
1043
|
try {
|
|
871
|
-
await
|
|
872
|
-
await element.selectOption(values, { timeout: 5000 });
|
|
1044
|
+
await state.element.selectOption(values);
|
|
873
1045
|
}
|
|
874
1046
|
catch (e) {
|
|
875
1047
|
//await this.closeUnexpectedPopups();
|
|
876
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
877
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1048
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1049
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
878
1050
|
}
|
|
879
1051
|
await this.waitForPageLoad();
|
|
880
|
-
return info;
|
|
1052
|
+
return state.info;
|
|
881
1053
|
}
|
|
882
1054
|
catch (e) {
|
|
883
|
-
|
|
884
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
885
|
-
info.screenshotPath = screenshotPath;
|
|
886
|
-
Object.assign(e, { info: info });
|
|
887
|
-
this.logger.info("click failed, will try next selector");
|
|
888
|
-
error = e;
|
|
889
|
-
throw e;
|
|
1055
|
+
await _commandError(state, e, this);
|
|
890
1056
|
}
|
|
891
1057
|
finally {
|
|
892
|
-
|
|
893
|
-
this._reportToWorld(world, {
|
|
894
|
-
element_name: selectors.element_name,
|
|
895
|
-
type: Types.SELECT,
|
|
896
|
-
text: `Select option: ${values}`,
|
|
897
|
-
value: values.toString(),
|
|
898
|
-
screenshotId,
|
|
899
|
-
result: error
|
|
900
|
-
? {
|
|
901
|
-
status: "FAILED",
|
|
902
|
-
startTime,
|
|
903
|
-
endTime,
|
|
904
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
905
|
-
}
|
|
906
|
-
: {
|
|
907
|
-
status: "PASSED",
|
|
908
|
-
startTime,
|
|
909
|
-
endTime,
|
|
910
|
-
},
|
|
911
|
-
info: info,
|
|
912
|
-
});
|
|
1058
|
+
_commandFinally(state, this);
|
|
913
1059
|
}
|
|
914
1060
|
}
|
|
915
1061
|
async type(_value, _params = null, options = {}, world = null) {
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1062
|
+
const state = {
|
|
1063
|
+
value: _value,
|
|
1064
|
+
_params,
|
|
1065
|
+
options,
|
|
1066
|
+
world,
|
|
1067
|
+
locate: false,
|
|
1068
|
+
scroll: false,
|
|
1069
|
+
highlight: false,
|
|
1070
|
+
type: Types.TYPE_PRESS,
|
|
1071
|
+
text: `Type value: ${_value}`,
|
|
1072
|
+
operation: "type",
|
|
1073
|
+
log: "",
|
|
1074
|
+
};
|
|
925
1075
|
try {
|
|
926
|
-
|
|
927
|
-
const valueSegment =
|
|
1076
|
+
await _preCommand(state, this);
|
|
1077
|
+
const valueSegment = state.value.split("&&");
|
|
928
1078
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
929
1079
|
if (i > 0) {
|
|
930
1080
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -944,134 +1094,76 @@ class StableBrowser {
|
|
|
944
1094
|
await this.page.keyboard.type(value);
|
|
945
1095
|
}
|
|
946
1096
|
}
|
|
947
|
-
return info;
|
|
1097
|
+
return state.info;
|
|
948
1098
|
}
|
|
949
1099
|
catch (e) {
|
|
950
|
-
|
|
951
|
-
this.logger.error("type failed " + info.log);
|
|
952
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
953
|
-
info.screenshotPath = screenshotPath;
|
|
954
|
-
Object.assign(e, { info: info });
|
|
955
|
-
error = e;
|
|
956
|
-
throw e;
|
|
1100
|
+
await _commandError(state, e, this);
|
|
957
1101
|
}
|
|
958
1102
|
finally {
|
|
959
|
-
|
|
960
|
-
this._reportToWorld(world, {
|
|
961
|
-
type: Types.TYPE_PRESS,
|
|
962
|
-
screenshotId,
|
|
963
|
-
value: _value,
|
|
964
|
-
text: `type value: ${_value}`,
|
|
965
|
-
result: error
|
|
966
|
-
? {
|
|
967
|
-
status: "FAILED",
|
|
968
|
-
startTime,
|
|
969
|
-
endTime,
|
|
970
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
971
|
-
}
|
|
972
|
-
: {
|
|
973
|
-
status: "PASSED",
|
|
974
|
-
startTime,
|
|
975
|
-
endTime,
|
|
976
|
-
},
|
|
977
|
-
info: info,
|
|
978
|
-
});
|
|
1103
|
+
_commandFinally(state, this);
|
|
979
1104
|
}
|
|
980
1105
|
}
|
|
981
1106
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
let screenshotPath = null;
|
|
1107
|
+
const state = {
|
|
1108
|
+
selectors,
|
|
1109
|
+
_params,
|
|
1110
|
+
value,
|
|
1111
|
+
options,
|
|
1112
|
+
world,
|
|
1113
|
+
type: Types.SET_INPUT,
|
|
1114
|
+
text: `Set input value`,
|
|
1115
|
+
operation: "setInputValue",
|
|
1116
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1117
|
+
};
|
|
994
1118
|
try {
|
|
995
|
-
|
|
996
|
-
let
|
|
997
|
-
await this.scrollIfNeeded(element, info);
|
|
998
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
999
|
-
await this._highlightElements(element);
|
|
1119
|
+
await _preCommand(state, this);
|
|
1120
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1000
1121
|
try {
|
|
1001
|
-
await element.evaluateHandle((el, value) => {
|
|
1122
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1002
1123
|
el.value = value;
|
|
1003
1124
|
}, value);
|
|
1004
1125
|
}
|
|
1005
1126
|
catch (error) {
|
|
1006
1127
|
this.logger.error("setInputValue failed, will try again");
|
|
1007
|
-
|
|
1008
|
-
info.
|
|
1009
|
-
|
|
1010
|
-
await element.evaluateHandle((el, value) => {
|
|
1128
|
+
await _screenshot(state, this);
|
|
1129
|
+
Object.assign(error, { info: state.info });
|
|
1130
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1011
1131
|
el.value = value;
|
|
1012
1132
|
});
|
|
1013
1133
|
}
|
|
1014
1134
|
}
|
|
1015
1135
|
catch (e) {
|
|
1016
|
-
|
|
1017
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1018
|
-
info.screenshotPath = screenshotPath;
|
|
1019
|
-
Object.assign(e, { info: info });
|
|
1020
|
-
error = e;
|
|
1021
|
-
throw e;
|
|
1136
|
+
await _commandError(state, e, this);
|
|
1022
1137
|
}
|
|
1023
1138
|
finally {
|
|
1024
|
-
|
|
1025
|
-
this._reportToWorld(world, {
|
|
1026
|
-
element_name: selectors.element_name,
|
|
1027
|
-
type: Types.SET_INPUT,
|
|
1028
|
-
text: `Set input value`,
|
|
1029
|
-
value: value,
|
|
1030
|
-
screenshotId,
|
|
1031
|
-
result: error
|
|
1032
|
-
? {
|
|
1033
|
-
status: "FAILED",
|
|
1034
|
-
startTime,
|
|
1035
|
-
endTime,
|
|
1036
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1037
|
-
}
|
|
1038
|
-
: {
|
|
1039
|
-
status: "PASSED",
|
|
1040
|
-
startTime,
|
|
1041
|
-
endTime,
|
|
1042
|
-
},
|
|
1043
|
-
info: info,
|
|
1044
|
-
});
|
|
1139
|
+
_commandFinally(state, this);
|
|
1045
1140
|
}
|
|
1046
1141
|
}
|
|
1047
1142
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1143
|
+
const state = {
|
|
1144
|
+
selectors,
|
|
1145
|
+
_params,
|
|
1146
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1147
|
+
options,
|
|
1148
|
+
world,
|
|
1149
|
+
type: Types.SET_DATE_TIME,
|
|
1150
|
+
text: `Set date time value: ${value}`,
|
|
1151
|
+
operation: "setDateTime",
|
|
1152
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1153
|
+
throwError: false,
|
|
1154
|
+
};
|
|
1058
1155
|
try {
|
|
1059
|
-
|
|
1060
|
-
let element = await this._locate(selectors, info, _params);
|
|
1061
|
-
//insert red border around the element
|
|
1062
|
-
await this.scrollIfNeeded(element, info);
|
|
1063
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1064
|
-
await this._highlightElements(element);
|
|
1156
|
+
await _preCommand(state, this);
|
|
1065
1157
|
try {
|
|
1066
|
-
await element.click();
|
|
1158
|
+
await state.element.click();
|
|
1067
1159
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1068
1160
|
if (format) {
|
|
1069
|
-
value = dayjs(value).format(format);
|
|
1070
|
-
await element.fill(value);
|
|
1161
|
+
state.value = dayjs(state.value).format(format);
|
|
1162
|
+
await state.element.fill(state.value);
|
|
1071
1163
|
}
|
|
1072
1164
|
else {
|
|
1073
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1074
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1165
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1166
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1075
1167
|
el.value = ""; // clear input
|
|
1076
1168
|
el.value = dateTimeValue;
|
|
1077
1169
|
}, dateTimeValue);
|
|
@@ -1084,20 +1176,19 @@ class StableBrowser {
|
|
|
1084
1176
|
}
|
|
1085
1177
|
catch (err) {
|
|
1086
1178
|
//await this.closeUnexpectedPopups();
|
|
1087
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1179
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1088
1180
|
this.logger.info("Trying again");
|
|
1089
|
-
|
|
1090
|
-
info.
|
|
1091
|
-
Object.assign(err, { info: info });
|
|
1181
|
+
await _screenshot(state, this);
|
|
1182
|
+
Object.assign(err, { info: state.info });
|
|
1092
1183
|
await element.click();
|
|
1093
1184
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1094
1185
|
if (format) {
|
|
1095
|
-
value = dayjs(value).format(format);
|
|
1096
|
-
await element.fill(value);
|
|
1186
|
+
state.value = dayjs(state.value).format(format);
|
|
1187
|
+
await state.element.fill(state.value);
|
|
1097
1188
|
}
|
|
1098
1189
|
else {
|
|
1099
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1100
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1190
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1191
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1101
1192
|
el.value = ""; // clear input
|
|
1102
1193
|
el.value = dateTimeValue;
|
|
1103
1194
|
}, dateTimeValue);
|
|
@@ -1110,60 +1201,39 @@ class StableBrowser {
|
|
|
1110
1201
|
}
|
|
1111
1202
|
}
|
|
1112
1203
|
catch (e) {
|
|
1113
|
-
|
|
1114
|
-
throw e;
|
|
1204
|
+
await _commandError(state, e, this);
|
|
1115
1205
|
}
|
|
1116
1206
|
finally {
|
|
1117
|
-
|
|
1118
|
-
this._reportToWorld(world, {
|
|
1119
|
-
element_name: selectors.element_name,
|
|
1120
|
-
type: Types.SET_DATE_TIME,
|
|
1121
|
-
screenshotId,
|
|
1122
|
-
value: value,
|
|
1123
|
-
text: `setDateTime input with value: ${value}`,
|
|
1124
|
-
result: error
|
|
1125
|
-
? {
|
|
1126
|
-
status: "FAILED",
|
|
1127
|
-
startTime,
|
|
1128
|
-
endTime,
|
|
1129
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1130
|
-
}
|
|
1131
|
-
: {
|
|
1132
|
-
status: "PASSED",
|
|
1133
|
-
startTime,
|
|
1134
|
-
endTime,
|
|
1135
|
-
},
|
|
1136
|
-
info: info,
|
|
1137
|
-
});
|
|
1207
|
+
_commandFinally(state, this);
|
|
1138
1208
|
}
|
|
1139
1209
|
}
|
|
1140
1210
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1141
|
-
|
|
1142
|
-
const startTime = Date.now();
|
|
1143
|
-
let error = null;
|
|
1144
|
-
let screenshotId = null;
|
|
1145
|
-
let screenshotPath = null;
|
|
1146
|
-
const info = {};
|
|
1147
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1148
|
-
info.operation = "clickType";
|
|
1149
|
-
info.selectors = selectors;
|
|
1211
|
+
_value = unEscapeString(_value);
|
|
1150
1212
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1213
|
+
const state = {
|
|
1214
|
+
selectors,
|
|
1215
|
+
_params,
|
|
1216
|
+
value: newValue,
|
|
1217
|
+
originalValue: _value,
|
|
1218
|
+
options,
|
|
1219
|
+
world,
|
|
1220
|
+
type: Types.FILL,
|
|
1221
|
+
text: `Click type input with value: ${_value}`,
|
|
1222
|
+
operation: "clickType",
|
|
1223
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1224
|
+
};
|
|
1151
1225
|
if (newValue !== _value) {
|
|
1152
1226
|
//this.logger.info(_value + "=" + newValue);
|
|
1153
1227
|
_value = newValue;
|
|
1154
1228
|
}
|
|
1155
|
-
info.value = _value;
|
|
1156
1229
|
try {
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
await this.scrollIfNeeded(element, info);
|
|
1160
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1161
|
-
await this._highlightElements(element);
|
|
1230
|
+
await _preCommand(state, this);
|
|
1231
|
+
state.info.value = _value;
|
|
1162
1232
|
if (options === null || options === undefined || !options.press) {
|
|
1163
1233
|
try {
|
|
1164
|
-
let currentValue = await element.inputValue();
|
|
1234
|
+
let currentValue = await state.element.inputValue();
|
|
1165
1235
|
if (currentValue) {
|
|
1166
|
-
await element.fill("");
|
|
1236
|
+
await state.element.fill("");
|
|
1167
1237
|
}
|
|
1168
1238
|
}
|
|
1169
1239
|
catch (e) {
|
|
@@ -1172,22 +1242,22 @@ class StableBrowser {
|
|
|
1172
1242
|
}
|
|
1173
1243
|
if (options === null || options === undefined || options.press) {
|
|
1174
1244
|
try {
|
|
1175
|
-
await element.click({ timeout: 5000 });
|
|
1245
|
+
await state.element.click({ timeout: 5000 });
|
|
1176
1246
|
}
|
|
1177
1247
|
catch (e) {
|
|
1178
|
-
await element.dispatchEvent("click");
|
|
1248
|
+
await state.element.dispatchEvent("click");
|
|
1179
1249
|
}
|
|
1180
1250
|
}
|
|
1181
1251
|
else {
|
|
1182
1252
|
try {
|
|
1183
|
-
await element.focus();
|
|
1253
|
+
await state.element.focus();
|
|
1184
1254
|
}
|
|
1185
1255
|
catch (e) {
|
|
1186
|
-
await element.dispatchEvent("focus");
|
|
1256
|
+
await state.element.dispatchEvent("focus");
|
|
1187
1257
|
}
|
|
1188
1258
|
}
|
|
1189
1259
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1190
|
-
const valueSegment =
|
|
1260
|
+
const valueSegment = state.value.split("&&");
|
|
1191
1261
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1192
1262
|
if (i > 0) {
|
|
1193
1263
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1207,13 +1277,19 @@ class StableBrowser {
|
|
|
1207
1277
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1208
1278
|
}
|
|
1209
1279
|
}
|
|
1280
|
+
await _screenshot(state, this);
|
|
1210
1281
|
if (enter === true) {
|
|
1211
1282
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1212
1283
|
await this.page.keyboard.press("Enter");
|
|
1213
1284
|
await this.waitForPageLoad();
|
|
1214
1285
|
}
|
|
1215
1286
|
else if (enter === false) {
|
|
1216
|
-
|
|
1287
|
+
try {
|
|
1288
|
+
await state.element.dispatchEvent("change", null, { timeout: 5000 });
|
|
1289
|
+
}
|
|
1290
|
+
catch (e) {
|
|
1291
|
+
// ignore
|
|
1292
|
+
}
|
|
1217
1293
|
//await this.page.keyboard.press("Tab");
|
|
1218
1294
|
}
|
|
1219
1295
|
else {
|
|
@@ -1222,107 +1298,55 @@ class StableBrowser {
|
|
|
1222
1298
|
await this.waitForPageLoad();
|
|
1223
1299
|
}
|
|
1224
1300
|
}
|
|
1225
|
-
return info;
|
|
1301
|
+
return state.info;
|
|
1226
1302
|
}
|
|
1227
1303
|
catch (e) {
|
|
1228
|
-
|
|
1229
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1230
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1231
|
-
info.screenshotPath = screenshotPath;
|
|
1232
|
-
Object.assign(e, { info: info });
|
|
1233
|
-
error = e;
|
|
1234
|
-
throw e;
|
|
1304
|
+
await _commandError(state, e, this);
|
|
1235
1305
|
}
|
|
1236
1306
|
finally {
|
|
1237
|
-
|
|
1238
|
-
this._reportToWorld(world, {
|
|
1239
|
-
element_name: selectors.element_name,
|
|
1240
|
-
type: Types.FILL,
|
|
1241
|
-
screenshotId,
|
|
1242
|
-
value: _value,
|
|
1243
|
-
text: `clickType input with value: ${_value}`,
|
|
1244
|
-
result: error
|
|
1245
|
-
? {
|
|
1246
|
-
status: "FAILED",
|
|
1247
|
-
startTime,
|
|
1248
|
-
endTime,
|
|
1249
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1250
|
-
}
|
|
1251
|
-
: {
|
|
1252
|
-
status: "PASSED",
|
|
1253
|
-
startTime,
|
|
1254
|
-
endTime,
|
|
1255
|
-
},
|
|
1256
|
-
info: info,
|
|
1257
|
-
});
|
|
1307
|
+
_commandFinally(state, this);
|
|
1258
1308
|
}
|
|
1259
1309
|
}
|
|
1260
1310
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1311
|
+
const state = {
|
|
1312
|
+
selectors,
|
|
1313
|
+
_params,
|
|
1314
|
+
value: unEscapeString(value),
|
|
1315
|
+
options,
|
|
1316
|
+
world,
|
|
1317
|
+
type: Types.FILL,
|
|
1318
|
+
text: `Fill input with value: ${value}`,
|
|
1319
|
+
operation: "fill",
|
|
1320
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1321
|
+
};
|
|
1271
1322
|
try {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
await
|
|
1275
|
-
await element.fill(value, { timeout: 10000 });
|
|
1276
|
-
await element.dispatchEvent("change");
|
|
1323
|
+
await _preCommand(state, this);
|
|
1324
|
+
await state.element.fill(value);
|
|
1325
|
+
await state.element.dispatchEvent("change");
|
|
1277
1326
|
if (enter) {
|
|
1278
1327
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1279
1328
|
await this.page.keyboard.press("Enter");
|
|
1280
1329
|
}
|
|
1281
1330
|
await this.waitForPageLoad();
|
|
1282
|
-
return info;
|
|
1331
|
+
return state.info;
|
|
1283
1332
|
}
|
|
1284
1333
|
catch (e) {
|
|
1285
|
-
|
|
1286
|
-
this.logger.error("fill failed " + info.log);
|
|
1287
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1288
|
-
info.screenshotPath = screenshotPath;
|
|
1289
|
-
Object.assign(e, { info: info });
|
|
1290
|
-
error = e;
|
|
1291
|
-
throw e;
|
|
1334
|
+
await _commandError(state, e, this);
|
|
1292
1335
|
}
|
|
1293
1336
|
finally {
|
|
1294
|
-
|
|
1295
|
-
this._reportToWorld(world, {
|
|
1296
|
-
element_name: selectors.element_name,
|
|
1297
|
-
type: Types.FILL,
|
|
1298
|
-
screenshotId,
|
|
1299
|
-
value,
|
|
1300
|
-
text: `Fill input with value: ${value}`,
|
|
1301
|
-
result: error
|
|
1302
|
-
? {
|
|
1303
|
-
status: "FAILED",
|
|
1304
|
-
startTime,
|
|
1305
|
-
endTime,
|
|
1306
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1307
|
-
}
|
|
1308
|
-
: {
|
|
1309
|
-
status: "PASSED",
|
|
1310
|
-
startTime,
|
|
1311
|
-
endTime,
|
|
1312
|
-
},
|
|
1313
|
-
info: info,
|
|
1314
|
-
});
|
|
1337
|
+
_commandFinally(state, this);
|
|
1315
1338
|
}
|
|
1316
1339
|
}
|
|
1317
1340
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1318
1341
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1319
1342
|
}
|
|
1320
1343
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1321
|
-
|
|
1344
|
+
_validateSelectors(selectors);
|
|
1322
1345
|
let screenshotId = null;
|
|
1323
1346
|
let screenshotPath = null;
|
|
1324
1347
|
if (!info.log) {
|
|
1325
1348
|
info.log = "";
|
|
1349
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
1326
1350
|
}
|
|
1327
1351
|
info.operation = "getText";
|
|
1328
1352
|
info.selectors = selectors;
|
|
@@ -1362,165 +1386,124 @@ class StableBrowser {
|
|
|
1362
1386
|
}
|
|
1363
1387
|
}
|
|
1364
1388
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1365
|
-
var _a;
|
|
1366
|
-
this._validateSelectors(selectors);
|
|
1367
1389
|
if (!pattern) {
|
|
1368
1390
|
throw new Error("pattern is null");
|
|
1369
1391
|
}
|
|
1370
1392
|
if (!text) {
|
|
1371
1393
|
throw new Error("text is null");
|
|
1372
1394
|
}
|
|
1395
|
+
const state = {
|
|
1396
|
+
selectors,
|
|
1397
|
+
_params,
|
|
1398
|
+
pattern,
|
|
1399
|
+
value: pattern,
|
|
1400
|
+
options,
|
|
1401
|
+
world,
|
|
1402
|
+
locate: false,
|
|
1403
|
+
scroll: false,
|
|
1404
|
+
screenshot: false,
|
|
1405
|
+
highlight: false,
|
|
1406
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1407
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1408
|
+
operation: "containsPattern",
|
|
1409
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1410
|
+
};
|
|
1373
1411
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1374
1412
|
if (newValue !== text) {
|
|
1375
1413
|
this.logger.info(text + "=" + newValue);
|
|
1376
1414
|
text = newValue;
|
|
1377
1415
|
}
|
|
1378
|
-
const startTime = Date.now();
|
|
1379
|
-
let error = null;
|
|
1380
|
-
let screenshotId = null;
|
|
1381
|
-
let screenshotPath = null;
|
|
1382
|
-
const info = {};
|
|
1383
|
-
info.log =
|
|
1384
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1385
|
-
info.operation = "containsPattern";
|
|
1386
|
-
info.selectors = selectors;
|
|
1387
|
-
info.value = text;
|
|
1388
|
-
info.pattern = pattern;
|
|
1389
1416
|
let foundObj = null;
|
|
1390
1417
|
try {
|
|
1391
|
-
|
|
1418
|
+
await _preCommand(state, this);
|
|
1419
|
+
state.info.pattern = pattern;
|
|
1420
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1392
1421
|
if (foundObj && foundObj.element) {
|
|
1393
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1422
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1394
1423
|
}
|
|
1395
|
-
|
|
1424
|
+
await _screenshot(state, this);
|
|
1396
1425
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1397
1426
|
pattern = pattern.replace("{text}", escapedText);
|
|
1398
1427
|
let regex = new RegExp(pattern, "im");
|
|
1399
|
-
if (!regex.test(foundObj
|
|
1400
|
-
info.foundText = foundObj
|
|
1428
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1429
|
+
state.info.foundText = foundObj?.text;
|
|
1401
1430
|
throw new Error("element doesn't contain text " + text);
|
|
1402
1431
|
}
|
|
1403
|
-
return info;
|
|
1432
|
+
return state.info;
|
|
1404
1433
|
}
|
|
1405
1434
|
catch (e) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1409
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1410
|
-
info.screenshotPath = screenshotPath;
|
|
1411
|
-
Object.assign(e, { info: info });
|
|
1412
|
-
error = e;
|
|
1413
|
-
throw e;
|
|
1435
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1436
|
+
await _commandError(state, e, this);
|
|
1414
1437
|
}
|
|
1415
1438
|
finally {
|
|
1416
|
-
|
|
1417
|
-
this._reportToWorld(world, {
|
|
1418
|
-
element_name: selectors.element_name,
|
|
1419
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1420
|
-
value: pattern,
|
|
1421
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1422
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1423
|
-
result: error
|
|
1424
|
-
? {
|
|
1425
|
-
status: "FAILED",
|
|
1426
|
-
startTime,
|
|
1427
|
-
endTime,
|
|
1428
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1429
|
-
}
|
|
1430
|
-
: {
|
|
1431
|
-
status: "PASSED",
|
|
1432
|
-
startTime,
|
|
1433
|
-
endTime,
|
|
1434
|
-
},
|
|
1435
|
-
info: info,
|
|
1436
|
-
});
|
|
1439
|
+
_commandFinally(state, this);
|
|
1437
1440
|
}
|
|
1438
1441
|
}
|
|
1439
1442
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1440
|
-
|
|
1441
|
-
|
|
1443
|
+
const state = {
|
|
1444
|
+
selectors,
|
|
1445
|
+
_params,
|
|
1446
|
+
value: text,
|
|
1447
|
+
options,
|
|
1448
|
+
world,
|
|
1449
|
+
locate: false,
|
|
1450
|
+
scroll: false,
|
|
1451
|
+
screenshot: false,
|
|
1452
|
+
highlight: false,
|
|
1453
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1454
|
+
text: `Verify element contains text: ${text}`,
|
|
1455
|
+
operation: "containsText",
|
|
1456
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1457
|
+
};
|
|
1442
1458
|
if (!text) {
|
|
1443
1459
|
throw new Error("text is null");
|
|
1444
1460
|
}
|
|
1445
|
-
|
|
1446
|
-
let error = null;
|
|
1447
|
-
let screenshotId = null;
|
|
1448
|
-
let screenshotPath = null;
|
|
1449
|
-
const info = {};
|
|
1450
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1451
|
-
info.operation = "containsText";
|
|
1452
|
-
info.selectors = selectors;
|
|
1461
|
+
text = unEscapeString(text);
|
|
1453
1462
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1454
1463
|
if (newValue !== text) {
|
|
1455
1464
|
this.logger.info(text + "=" + newValue);
|
|
1456
1465
|
text = newValue;
|
|
1457
1466
|
}
|
|
1458
|
-
info.value = text;
|
|
1459
1467
|
let foundObj = null;
|
|
1460
1468
|
try {
|
|
1461
|
-
|
|
1469
|
+
await _preCommand(state, this);
|
|
1470
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1462
1471
|
if (foundObj && foundObj.element) {
|
|
1463
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1472
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1464
1473
|
}
|
|
1465
|
-
|
|
1474
|
+
await _screenshot(state, this);
|
|
1466
1475
|
const dateAlternatives = findDateAlternatives(text);
|
|
1467
1476
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1468
1477
|
if (dateAlternatives.date) {
|
|
1469
1478
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1470
|
-
if (
|
|
1471
|
-
|
|
1472
|
-
return info;
|
|
1479
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1480
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1481
|
+
return state.info;
|
|
1473
1482
|
}
|
|
1474
1483
|
}
|
|
1475
1484
|
throw new Error("element doesn't contain text " + text);
|
|
1476
1485
|
}
|
|
1477
1486
|
else if (numberAlternatives.number) {
|
|
1478
1487
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1479
|
-
if (
|
|
1480
|
-
|
|
1481
|
-
return info;
|
|
1488
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1489
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1490
|
+
return state.info;
|
|
1482
1491
|
}
|
|
1483
|
-
}
|
|
1484
|
-
throw new Error("element doesn't contain text " + text);
|
|
1485
|
-
}
|
|
1486
|
-
else if (!
|
|
1487
|
-
info.foundText = foundObj
|
|
1488
|
-
info.value = foundObj
|
|
1489
|
-
throw new Error("element doesn't contain text " + text);
|
|
1490
|
-
}
|
|
1491
|
-
return info;
|
|
1492
|
-
}
|
|
1493
|
-
catch (e) {
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
Object.assign(e, { info: info });
|
|
1499
|
-
error = e;
|
|
1500
|
-
throw e;
|
|
1501
|
-
}
|
|
1502
|
-
finally {
|
|
1503
|
-
const endTime = Date.now();
|
|
1504
|
-
this._reportToWorld(world, {
|
|
1505
|
-
element_name: selectors.element_name,
|
|
1506
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1507
|
-
text: `Verify element contains text: ${text}`,
|
|
1508
|
-
value: text,
|
|
1509
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1510
|
-
result: error
|
|
1511
|
-
? {
|
|
1512
|
-
status: "FAILED",
|
|
1513
|
-
startTime,
|
|
1514
|
-
endTime,
|
|
1515
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1516
|
-
}
|
|
1517
|
-
: {
|
|
1518
|
-
status: "PASSED",
|
|
1519
|
-
startTime,
|
|
1520
|
-
endTime,
|
|
1521
|
-
},
|
|
1522
|
-
info: info,
|
|
1523
|
-
});
|
|
1492
|
+
}
|
|
1493
|
+
throw new Error("element doesn't contain text " + text);
|
|
1494
|
+
}
|
|
1495
|
+
else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
|
|
1496
|
+
state.info.foundText = foundObj?.text;
|
|
1497
|
+
state.info.value = foundObj?.value;
|
|
1498
|
+
throw new Error("element doesn't contain text " + text);
|
|
1499
|
+
}
|
|
1500
|
+
return state.info;
|
|
1501
|
+
}
|
|
1502
|
+
catch (e) {
|
|
1503
|
+
await _commandError(state, e, this);
|
|
1504
|
+
}
|
|
1505
|
+
finally {
|
|
1506
|
+
_commandFinally(state, this);
|
|
1524
1507
|
}
|
|
1525
1508
|
}
|
|
1526
1509
|
_getDataFile(world = null) {
|
|
@@ -1539,6 +1522,29 @@ class StableBrowser {
|
|
|
1539
1522
|
}
|
|
1540
1523
|
return dataFile;
|
|
1541
1524
|
}
|
|
1525
|
+
async waitForUserInput(message, world = null) {
|
|
1526
|
+
if (!message) {
|
|
1527
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
message = "# Wait for user input. " + message;
|
|
1531
|
+
}
|
|
1532
|
+
message += "\n";
|
|
1533
|
+
const value = await new Promise((resolve) => {
|
|
1534
|
+
const rl = readline.createInterface({
|
|
1535
|
+
input: process.stdin,
|
|
1536
|
+
output: process.stdout,
|
|
1537
|
+
});
|
|
1538
|
+
rl.question(message, (answer) => {
|
|
1539
|
+
rl.close();
|
|
1540
|
+
resolve(answer);
|
|
1541
|
+
});
|
|
1542
|
+
});
|
|
1543
|
+
if (value) {
|
|
1544
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1545
|
+
}
|
|
1546
|
+
this.setTestData({ userInput: value }, world);
|
|
1547
|
+
}
|
|
1542
1548
|
setTestData(testData, world = null) {
|
|
1543
1549
|
if (!testData) {
|
|
1544
1550
|
return;
|
|
@@ -1679,11 +1685,9 @@ class StableBrowser {
|
|
|
1679
1685
|
if (!fs.existsSync(world.screenshotPath)) {
|
|
1680
1686
|
fs.mkdirSync(world.screenshotPath, { recursive: true });
|
|
1681
1687
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
}
|
|
1686
|
-
const screenshotPath = path.join(world.screenshotPath, nextIndex + ".png");
|
|
1688
|
+
// to make sure the path doesn't start with -
|
|
1689
|
+
const uuidStr = "id_" + randomUUID();
|
|
1690
|
+
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
1687
1691
|
try {
|
|
1688
1692
|
await this.takeScreenshot(screenshotPath);
|
|
1689
1693
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
@@ -1697,7 +1701,7 @@ class StableBrowser {
|
|
|
1697
1701
|
catch (e) {
|
|
1698
1702
|
this.logger.info("unable to take screenshot, ignored");
|
|
1699
1703
|
}
|
|
1700
|
-
result.screenshotId =
|
|
1704
|
+
result.screenshotId = uuidStr;
|
|
1701
1705
|
result.screenshotPath = screenshotPath;
|
|
1702
1706
|
if (info && info.box) {
|
|
1703
1707
|
await drawRectangle(screenshotPath, info.box.x, info.box.y, info.box.width, info.box.height);
|
|
@@ -1726,7 +1730,6 @@ class StableBrowser {
|
|
|
1726
1730
|
}
|
|
1727
1731
|
async takeScreenshot(screenshotPath) {
|
|
1728
1732
|
const playContext = this.context.playContext;
|
|
1729
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1730
1733
|
// Using CDP to capture the screenshot
|
|
1731
1734
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1732
1735
|
document.body.scrollWidth,
|
|
@@ -1736,164 +1739,169 @@ class StableBrowser {
|
|
|
1736
1739
|
document.body.clientWidth,
|
|
1737
1740
|
document.documentElement.clientWidth,
|
|
1738
1741
|
])));
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
}
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1742
|
+
let screenshotBuffer = null;
|
|
1743
|
+
if (this.context.browserName === "chromium") {
|
|
1744
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1745
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1746
|
+
format: "png",
|
|
1747
|
+
// clip: {
|
|
1748
|
+
// x: 0,
|
|
1749
|
+
// y: 0,
|
|
1750
|
+
// width: viewportWidth,
|
|
1751
|
+
// height: viewportHeight,
|
|
1752
|
+
// scale: 1,
|
|
1753
|
+
// },
|
|
1754
|
+
});
|
|
1755
|
+
await client.detach();
|
|
1756
|
+
if (!screenshotPath) {
|
|
1757
|
+
return data;
|
|
1758
|
+
}
|
|
1759
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1760
|
+
}
|
|
1761
|
+
else {
|
|
1762
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1763
|
+
}
|
|
1764
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1765
|
+
// Get the image dimensions
|
|
1766
|
+
const { width, height } = image.bitmap;
|
|
1767
|
+
const resizeRatio = viewportWidth / width;
|
|
1768
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1769
|
+
if (width > viewportWidth) {
|
|
1770
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1771
|
+
await image.write(screenshotPath);
|
|
1772
|
+
}
|
|
1773
|
+
else {
|
|
1774
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1775
|
+
}
|
|
1776
|
+
return screenshotBuffer;
|
|
1774
1777
|
}
|
|
1775
1778
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1779
|
+
const state = {
|
|
1780
|
+
selectors,
|
|
1781
|
+
_params,
|
|
1782
|
+
options,
|
|
1783
|
+
world,
|
|
1784
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1785
|
+
text: `Verify element exists in page`,
|
|
1786
|
+
operation: "verifyElementExistInPage",
|
|
1787
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1788
|
+
};
|
|
1781
1789
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1782
|
-
const info = {};
|
|
1783
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1784
|
-
info.operation = "verify";
|
|
1785
|
-
info.selectors = selectors;
|
|
1786
1790
|
try {
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
}
|
|
1791
|
-
await this._highlightElements(element);
|
|
1792
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1793
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1794
|
-
return info;
|
|
1791
|
+
await _preCommand(state, this);
|
|
1792
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1793
|
+
return state.info;
|
|
1795
1794
|
}
|
|
1796
1795
|
catch (e) {
|
|
1797
|
-
|
|
1798
|
-
this.logger.error("verify failed " + info.log);
|
|
1799
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1800
|
-
info.screenshotPath = screenshotPath;
|
|
1801
|
-
Object.assign(e, { info: info });
|
|
1802
|
-
error = e;
|
|
1803
|
-
throw e;
|
|
1796
|
+
await _commandError(state, e, this);
|
|
1804
1797
|
}
|
|
1805
1798
|
finally {
|
|
1806
|
-
|
|
1807
|
-
this._reportToWorld(world, {
|
|
1808
|
-
element_name: selectors.element_name,
|
|
1809
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1810
|
-
text: "Verify element exists in page",
|
|
1811
|
-
screenshotId,
|
|
1812
|
-
result: error
|
|
1813
|
-
? {
|
|
1814
|
-
status: "FAILED",
|
|
1815
|
-
startTime,
|
|
1816
|
-
endTime,
|
|
1817
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1818
|
-
}
|
|
1819
|
-
: {
|
|
1820
|
-
status: "PASSED",
|
|
1821
|
-
startTime,
|
|
1822
|
-
endTime,
|
|
1823
|
-
},
|
|
1824
|
-
info: info,
|
|
1825
|
-
});
|
|
1799
|
+
_commandFinally(state, this);
|
|
1826
1800
|
}
|
|
1827
1801
|
}
|
|
1828
1802
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1803
|
+
const state = {
|
|
1804
|
+
selectors,
|
|
1805
|
+
_params,
|
|
1806
|
+
attribute,
|
|
1807
|
+
variable,
|
|
1808
|
+
options,
|
|
1809
|
+
world,
|
|
1810
|
+
type: Types.EXTRACT,
|
|
1811
|
+
text: `Extract attribute from element`,
|
|
1812
|
+
operation: "extractAttribute",
|
|
1813
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1814
|
+
};
|
|
1834
1815
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1835
|
-
const info = {};
|
|
1836
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1837
|
-
info.operation = "extract";
|
|
1838
|
-
info.selectors = selectors;
|
|
1839
1816
|
try {
|
|
1840
|
-
|
|
1841
|
-
await this._highlightElements(element);
|
|
1842
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1817
|
+
await _preCommand(state, this);
|
|
1843
1818
|
switch (attribute) {
|
|
1844
1819
|
case "inner_text":
|
|
1845
|
-
|
|
1820
|
+
state.value = await state.element.innerText();
|
|
1846
1821
|
break;
|
|
1847
1822
|
case "href":
|
|
1848
|
-
|
|
1823
|
+
state.value = await state.element.getAttribute("href");
|
|
1824
|
+
break;
|
|
1825
|
+
case "value":
|
|
1826
|
+
state.value = await state.element.inputValue();
|
|
1827
|
+
break;
|
|
1828
|
+
default:
|
|
1829
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1830
|
+
break;
|
|
1831
|
+
}
|
|
1832
|
+
state.info.value = state.value;
|
|
1833
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1834
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1835
|
+
return state.info;
|
|
1836
|
+
}
|
|
1837
|
+
catch (e) {
|
|
1838
|
+
await _commandError(state, e, this);
|
|
1839
|
+
}
|
|
1840
|
+
finally {
|
|
1841
|
+
_commandFinally(state, this);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
async verifyAttribute(selectors, attribute, value, _params = null, options = {}, world = null) {
|
|
1845
|
+
const state = {
|
|
1846
|
+
selectors,
|
|
1847
|
+
_params,
|
|
1848
|
+
attribute,
|
|
1849
|
+
value,
|
|
1850
|
+
options,
|
|
1851
|
+
world,
|
|
1852
|
+
type: Types.VERIFY_ATTRIBUTE,
|
|
1853
|
+
highlight: true,
|
|
1854
|
+
screenshot: true,
|
|
1855
|
+
text: `Verify element attribute`,
|
|
1856
|
+
operation: "verifyAttribute",
|
|
1857
|
+
log: "***** verify attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1858
|
+
allowDisabled: true,
|
|
1859
|
+
};
|
|
1860
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1861
|
+
let val;
|
|
1862
|
+
try {
|
|
1863
|
+
await _preCommand(state, this);
|
|
1864
|
+
switch (attribute) {
|
|
1865
|
+
case "innerText":
|
|
1866
|
+
val = String(await state.element.innerText());
|
|
1849
1867
|
break;
|
|
1850
1868
|
case "value":
|
|
1851
|
-
|
|
1869
|
+
val = String(await state.element.inputValue());
|
|
1870
|
+
break;
|
|
1871
|
+
case "checked":
|
|
1872
|
+
val = String(await state.element.isChecked());
|
|
1873
|
+
break;
|
|
1874
|
+
case "disabled":
|
|
1875
|
+
val = String(await state.element.isDisabled());
|
|
1876
|
+
break;
|
|
1877
|
+
case "readOnly":
|
|
1878
|
+
const isEditable = await state.element.isEditable();
|
|
1879
|
+
val = String(!isEditable);
|
|
1852
1880
|
break;
|
|
1853
1881
|
default:
|
|
1854
|
-
|
|
1882
|
+
val = String(await state.element.getAttribute(attribute));
|
|
1855
1883
|
break;
|
|
1856
1884
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1885
|
+
state.info.expectedValue = val;
|
|
1886
|
+
let regex;
|
|
1887
|
+
if (value.startsWith("/") && value.endsWith("/")) {
|
|
1888
|
+
const patternBody = value.slice(1, -1);
|
|
1889
|
+
regex = new RegExp(patternBody, "g");
|
|
1860
1890
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1891
|
+
else {
|
|
1892
|
+
const escapedPattern = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1893
|
+
regex = new RegExp(escapedPattern, "g");
|
|
1894
|
+
}
|
|
1895
|
+
if (!val.match(regex)) {
|
|
1896
|
+
throw new Error(`The ${attribute} attribute has a value of "${val}", but the expected value is "${value}"`);
|
|
1897
|
+
}
|
|
1898
|
+
return state.info;
|
|
1864
1899
|
}
|
|
1865
1900
|
catch (e) {
|
|
1866
|
-
|
|
1867
|
-
this.logger.error("extract failed " + info.log);
|
|
1868
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1869
|
-
info.screenshotPath = screenshotPath;
|
|
1870
|
-
Object.assign(e, { info: info });
|
|
1871
|
-
error = e;
|
|
1872
|
-
throw e;
|
|
1901
|
+
await _commandError(state, e, this);
|
|
1873
1902
|
}
|
|
1874
1903
|
finally {
|
|
1875
|
-
|
|
1876
|
-
this._reportToWorld(world, {
|
|
1877
|
-
element_name: selectors.element_name,
|
|
1878
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1879
|
-
variable: variable,
|
|
1880
|
-
value: info.value,
|
|
1881
|
-
text: "Extract attribute from element",
|
|
1882
|
-
screenshotId,
|
|
1883
|
-
result: error
|
|
1884
|
-
? {
|
|
1885
|
-
status: "FAILED",
|
|
1886
|
-
startTime,
|
|
1887
|
-
endTime,
|
|
1888
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1889
|
-
}
|
|
1890
|
-
: {
|
|
1891
|
-
status: "PASSED",
|
|
1892
|
-
startTime,
|
|
1893
|
-
endTime,
|
|
1894
|
-
},
|
|
1895
|
-
info: info,
|
|
1896
|
-
});
|
|
1904
|
+
_commandFinally(state, this);
|
|
1897
1905
|
}
|
|
1898
1906
|
}
|
|
1899
1907
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1914,7 +1922,7 @@ class StableBrowser {
|
|
|
1914
1922
|
if (options && options.timeout) {
|
|
1915
1923
|
timeout = options.timeout;
|
|
1916
1924
|
}
|
|
1917
|
-
const serviceUrl =
|
|
1925
|
+
const serviceUrl = _getServerUrl() + "/api/mail/createLinkOrCodeFromEmail";
|
|
1918
1926
|
const request = {
|
|
1919
1927
|
method: "POST",
|
|
1920
1928
|
url: serviceUrl,
|
|
@@ -1970,7 +1978,8 @@ class StableBrowser {
|
|
|
1970
1978
|
catch (e) {
|
|
1971
1979
|
errorCount++;
|
|
1972
1980
|
if (errorCount > 3) {
|
|
1973
|
-
throw e;
|
|
1981
|
+
// throw e;
|
|
1982
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1974
1983
|
}
|
|
1975
1984
|
// ignore
|
|
1976
1985
|
}
|
|
@@ -1990,15 +1999,15 @@ class StableBrowser {
|
|
|
1990
1999
|
scope
|
|
1991
2000
|
.evaluate((node) => {
|
|
1992
2001
|
if (node && node.style) {
|
|
1993
|
-
let originalBorder = node.style.
|
|
1994
|
-
node.style.
|
|
2002
|
+
let originalBorder = node.style.outline;
|
|
2003
|
+
node.style.outline = "2px solid red";
|
|
1995
2004
|
if (window) {
|
|
1996
2005
|
window.addEventListener("beforeunload", function (e) {
|
|
1997
|
-
node.style.
|
|
2006
|
+
node.style.outline = originalBorder;
|
|
1998
2007
|
});
|
|
1999
2008
|
}
|
|
2000
2009
|
setTimeout(function () {
|
|
2001
|
-
node.style.
|
|
2010
|
+
node.style.outline = originalBorder;
|
|
2002
2011
|
}, 2000);
|
|
2003
2012
|
}
|
|
2004
2013
|
})
|
|
@@ -2020,17 +2029,17 @@ class StableBrowser {
|
|
|
2020
2029
|
if (!element.style) {
|
|
2021
2030
|
return;
|
|
2022
2031
|
}
|
|
2023
|
-
var originalBorder = element.style.
|
|
2032
|
+
var originalBorder = element.style.outline;
|
|
2024
2033
|
// Set the new border to be red and 2px solid
|
|
2025
|
-
element.style.
|
|
2034
|
+
element.style.outline = "2px solid red";
|
|
2026
2035
|
if (window) {
|
|
2027
2036
|
window.addEventListener("beforeunload", function (e) {
|
|
2028
|
-
element.style.
|
|
2037
|
+
element.style.outline = originalBorder;
|
|
2029
2038
|
});
|
|
2030
2039
|
}
|
|
2031
2040
|
// Set a timeout to revert to the original border after 2 seconds
|
|
2032
2041
|
setTimeout(function () {
|
|
2033
|
-
element.style.
|
|
2042
|
+
element.style.outline = originalBorder;
|
|
2034
2043
|
}, 2000);
|
|
2035
2044
|
}
|
|
2036
2045
|
return;
|
|
@@ -2081,11 +2090,12 @@ class StableBrowser {
|
|
|
2081
2090
|
info.screenshotPath = screenshotPath;
|
|
2082
2091
|
Object.assign(e, { info: info });
|
|
2083
2092
|
error = e;
|
|
2084
|
-
throw e;
|
|
2093
|
+
// throw e;
|
|
2094
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2085
2095
|
}
|
|
2086
2096
|
finally {
|
|
2087
2097
|
const endTime = Date.now();
|
|
2088
|
-
|
|
2098
|
+
_reportToWorld(world, {
|
|
2089
2099
|
type: Types.VERIFY_PAGE_PATH,
|
|
2090
2100
|
text: "Verify page path",
|
|
2091
2101
|
screenshotId,
|
|
@@ -2094,7 +2104,7 @@ class StableBrowser {
|
|
|
2094
2104
|
status: "FAILED",
|
|
2095
2105
|
startTime,
|
|
2096
2106
|
endTime,
|
|
2097
|
-
message: error
|
|
2107
|
+
message: error?.message,
|
|
2098
2108
|
}
|
|
2099
2109
|
: {
|
|
2100
2110
|
status: "PASSED",
|
|
@@ -2105,53 +2115,65 @@ class StableBrowser {
|
|
|
2105
2115
|
});
|
|
2106
2116
|
}
|
|
2107
2117
|
}
|
|
2118
|
+
async findTextInAllFrames(dateAlternatives, numberAlternatives, text, state) {
|
|
2119
|
+
const frames = this.page.frames();
|
|
2120
|
+
let results = [];
|
|
2121
|
+
let ignoreCase = false;
|
|
2122
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2123
|
+
if (dateAlternatives.date) {
|
|
2124
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2125
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2126
|
+
result.frame = frames[i];
|
|
2127
|
+
results.push(result);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
else if (numberAlternatives.number) {
|
|
2131
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2132
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2133
|
+
result.frame = frames[i];
|
|
2134
|
+
results.push(result);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
else {
|
|
2138
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", false, true, ignoreCase, {});
|
|
2139
|
+
result.frame = frames[i];
|
|
2140
|
+
results.push(result);
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2143
|
+
state.info.results = results;
|
|
2144
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2145
|
+
return resultWithElementsFound;
|
|
2146
|
+
}
|
|
2108
2147
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2109
|
-
|
|
2148
|
+
text = unEscapeString(text);
|
|
2149
|
+
const state = {
|
|
2150
|
+
text_search: text,
|
|
2151
|
+
options,
|
|
2152
|
+
world,
|
|
2153
|
+
locate: false,
|
|
2154
|
+
scroll: false,
|
|
2155
|
+
highlight: false,
|
|
2156
|
+
type: Types.VERIFY_PAGE_CONTAINS_TEXT,
|
|
2157
|
+
text: `Verify text exists in page`,
|
|
2158
|
+
operation: "verifyTextExistInPage",
|
|
2159
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2160
|
+
};
|
|
2110
2161
|
const timeout = this._getLoadTimeout(options);
|
|
2111
|
-
let error = null;
|
|
2112
|
-
let screenshotId = null;
|
|
2113
|
-
let screenshotPath = null;
|
|
2114
2162
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2115
|
-
const info = {};
|
|
2116
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2117
|
-
info.operation = "verifyTextExistInPage";
|
|
2118
2163
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2119
2164
|
if (newValue !== text) {
|
|
2120
2165
|
this.logger.info(text + "=" + newValue);
|
|
2121
2166
|
text = newValue;
|
|
2122
2167
|
}
|
|
2123
|
-
info.text = text;
|
|
2124
2168
|
let dateAlternatives = findDateAlternatives(text);
|
|
2125
2169
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2126
2170
|
try {
|
|
2171
|
+
await _preCommand(state, this);
|
|
2172
|
+
state.info.text = text;
|
|
2127
2173
|
while (true) {
|
|
2128
|
-
const
|
|
2129
|
-
let results = [];
|
|
2130
|
-
for (let i = 0; i < frames.length; i++) {
|
|
2131
|
-
if (dateAlternatives.date) {
|
|
2132
|
-
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2133
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*", true, {});
|
|
2134
|
-
result.frame = frames[i];
|
|
2135
|
-
results.push(result);
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
else if (numberAlternatives.number) {
|
|
2139
|
-
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2140
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*", true, {});
|
|
2141
|
-
result.frame = frames[i];
|
|
2142
|
-
results.push(result);
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
else {
|
|
2146
|
-
const result = await this._locateElementByText(frames[i], text, "*", true, {});
|
|
2147
|
-
result.frame = frames[i];
|
|
2148
|
-
results.push(result);
|
|
2149
|
-
}
|
|
2150
|
-
}
|
|
2151
|
-
info.results = results;
|
|
2152
|
-
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2174
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2153
2175
|
if (resultWithElementsFound.length === 0) {
|
|
2154
|
-
if (Date.now() - startTime > timeout) {
|
|
2176
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2155
2177
|
throw new Error(`Text ${text} not found in page`);
|
|
2156
2178
|
}
|
|
2157
2179
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2159,59 +2181,153 @@ class StableBrowser {
|
|
|
2159
2181
|
}
|
|
2160
2182
|
if (resultWithElementsFound[0].randomToken) {
|
|
2161
2183
|
const frame = resultWithElementsFound[0].frame;
|
|
2162
|
-
const dataAttribute = `[data-blinq-id
|
|
2184
|
+
const dataAttribute = `[data-blinq-id-${resultWithElementsFound[0].randomToken}]`;
|
|
2163
2185
|
await this._highlightElements(frame, dataAttribute);
|
|
2164
|
-
const element = await frame
|
|
2186
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2165
2187
|
if (element) {
|
|
2166
|
-
await this.scrollIfNeeded(element, info);
|
|
2188
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2167
2189
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2168
2190
|
}
|
|
2169
2191
|
}
|
|
2170
|
-
|
|
2171
|
-
return info;
|
|
2192
|
+
await _screenshot(state, this);
|
|
2193
|
+
return state.info;
|
|
2172
2194
|
}
|
|
2173
2195
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2174
2196
|
}
|
|
2175
2197
|
catch (e) {
|
|
2176
|
-
|
|
2177
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2178
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2179
|
-
info.screenshotPath = screenshotPath;
|
|
2180
|
-
Object.assign(e, { info: info });
|
|
2181
|
-
error = e;
|
|
2182
|
-
throw e;
|
|
2198
|
+
await _commandError(state, e, this);
|
|
2183
2199
|
}
|
|
2184
2200
|
finally {
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2201
|
+
_commandFinally(state, this);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2205
|
+
text = unEscapeString(text);
|
|
2206
|
+
const state = {
|
|
2207
|
+
text_search: text,
|
|
2208
|
+
options,
|
|
2209
|
+
world,
|
|
2210
|
+
locate: false,
|
|
2211
|
+
scroll: false,
|
|
2212
|
+
highlight: false,
|
|
2213
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2214
|
+
text: `Verify text does not exist in page`,
|
|
2215
|
+
operation: "verifyTextNotExistInPage",
|
|
2216
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2217
|
+
};
|
|
2218
|
+
const timeout = this._getLoadTimeout(options);
|
|
2219
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2220
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2221
|
+
if (newValue !== text) {
|
|
2222
|
+
this.logger.info(text + "=" + newValue);
|
|
2223
|
+
text = newValue;
|
|
2224
|
+
}
|
|
2225
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2226
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2227
|
+
try {
|
|
2228
|
+
await _preCommand(state, this);
|
|
2229
|
+
state.info.text = text;
|
|
2230
|
+
while (true) {
|
|
2231
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, text, state);
|
|
2232
|
+
if (resultWithElementsFound.length === 0) {
|
|
2233
|
+
await _screenshot(state, this);
|
|
2234
|
+
return state.info;
|
|
2235
|
+
}
|
|
2236
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2237
|
+
throw new Error(`Text ${text} found in page`);
|
|
2238
|
+
}
|
|
2239
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
catch (e) {
|
|
2243
|
+
await _commandError(state, e, this);
|
|
2244
|
+
}
|
|
2245
|
+
finally {
|
|
2246
|
+
_commandFinally(state, this);
|
|
2204
2247
|
}
|
|
2205
2248
|
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2249
|
+
async verifyTextRelatedToText(textAnchor, climb, textToVerify, options = {}, world = null) {
|
|
2250
|
+
textAnchor = unEscapeString(textAnchor);
|
|
2251
|
+
textToVerify = unEscapeString(textToVerify);
|
|
2252
|
+
const state = {
|
|
2253
|
+
text_search: textToVerify,
|
|
2254
|
+
options,
|
|
2255
|
+
world,
|
|
2256
|
+
locate: false,
|
|
2257
|
+
scroll: false,
|
|
2258
|
+
highlight: false,
|
|
2259
|
+
type: Types.VERIFY_TEXT_WITH_RELATION,
|
|
2260
|
+
text: `Verify text with relation to another text`,
|
|
2261
|
+
operation: "verify_text_with_relation",
|
|
2262
|
+
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
2263
|
+
};
|
|
2264
|
+
const timeout = this._getLoadTimeout(options);
|
|
2265
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2266
|
+
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
2267
|
+
if (newValue !== textAnchor) {
|
|
2268
|
+
this.logger.info(textAnchor + "=" + newValue);
|
|
2269
|
+
textAnchor = newValue;
|
|
2270
|
+
}
|
|
2271
|
+
newValue = await this._replaceWithLocalData(textToVerify, world);
|
|
2272
|
+
if (newValue !== textToVerify) {
|
|
2273
|
+
this.logger.info(textToVerify + "=" + newValue);
|
|
2274
|
+
textToVerify = newValue;
|
|
2275
|
+
}
|
|
2276
|
+
let dateAlternatives = findDateAlternatives(textToVerify);
|
|
2277
|
+
let numberAlternatives = findNumberAlternatives(textToVerify);
|
|
2278
|
+
let foundAncore = false;
|
|
2279
|
+
try {
|
|
2280
|
+
await _preCommand(state, this);
|
|
2281
|
+
state.info.text = textToVerify;
|
|
2282
|
+
while (true) {
|
|
2283
|
+
const resultWithElementsFound = await this.findTextInAllFrames(dateAlternatives, numberAlternatives, textAnchor, state);
|
|
2284
|
+
if (resultWithElementsFound.length === 0) {
|
|
2285
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2286
|
+
throw new Error(`Text ${foundAncore ? textToVerify : textAnchor} not found in page`);
|
|
2287
|
+
}
|
|
2288
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2291
|
+
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
2292
|
+
foundAncore = true;
|
|
2293
|
+
const result = resultWithElementsFound[i];
|
|
2294
|
+
const token = result.randomToken;
|
|
2295
|
+
const frame = result.frame;
|
|
2296
|
+
let css = `[data-blinq-id-${token}]`;
|
|
2297
|
+
const climbArray1 = [];
|
|
2298
|
+
for (let i = 0; i < climb; i++) {
|
|
2299
|
+
climbArray1.push("..");
|
|
2300
|
+
}
|
|
2301
|
+
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
2302
|
+
css = css + " >> " + climbXpath;
|
|
2303
|
+
const count = await frame.locator(css).count();
|
|
2304
|
+
for (let j = 0; j < count; j++) {
|
|
2305
|
+
const continer = await frame.locator(css).nth(j);
|
|
2306
|
+
const result = await this._locateElementByText(continer, textToVerify, "*", false, true, true, {});
|
|
2307
|
+
if (result.elementCount > 0) {
|
|
2308
|
+
const dataAttribute = "[data-blinq-id-" + result.randomToken + "]";
|
|
2309
|
+
//const cssAnchor = `[data-blinq-id="blinq-id-${token}-anchor"]`;
|
|
2310
|
+
await this._highlightElements(frame, dataAttribute);
|
|
2311
|
+
//await this._highlightElements(frame, cssAnchor);
|
|
2312
|
+
const element = await frame.locator(dataAttribute).first();
|
|
2313
|
+
if (element) {
|
|
2314
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2315
|
+
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2316
|
+
}
|
|
2317
|
+
await _screenshot(state, this);
|
|
2318
|
+
return state.info;
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2210
2324
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2325
|
+
catch (e) {
|
|
2326
|
+
await _commandError(state, e, this);
|
|
2327
|
+
}
|
|
2328
|
+
finally {
|
|
2329
|
+
_commandFinally(state, this);
|
|
2213
2330
|
}
|
|
2214
|
-
return serviceUrl;
|
|
2215
2331
|
}
|
|
2216
2332
|
async visualVerification(text, options = {}, world = null) {
|
|
2217
2333
|
const startTime = Date.now();
|
|
@@ -2227,7 +2343,7 @@ class StableBrowser {
|
|
|
2227
2343
|
throw new Error("TOKEN is not set");
|
|
2228
2344
|
}
|
|
2229
2345
|
try {
|
|
2230
|
-
let serviceUrl =
|
|
2346
|
+
let serviceUrl = _getServerUrl();
|
|
2231
2347
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2232
2348
|
info.screenshotPath = screenshotPath;
|
|
2233
2349
|
const screenshot = await this.takeScreenshot();
|
|
@@ -2263,11 +2379,12 @@ class StableBrowser {
|
|
|
2263
2379
|
info.screenshotPath = screenshotPath;
|
|
2264
2380
|
Object.assign(e, { info: info });
|
|
2265
2381
|
error = e;
|
|
2266
|
-
throw e;
|
|
2382
|
+
// throw e;
|
|
2383
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2267
2384
|
}
|
|
2268
2385
|
finally {
|
|
2269
2386
|
const endTime = Date.now();
|
|
2270
|
-
|
|
2387
|
+
_reportToWorld(world, {
|
|
2271
2388
|
type: Types.VERIFY_VISUAL,
|
|
2272
2389
|
text: "Visual verification",
|
|
2273
2390
|
screenshotId,
|
|
@@ -2276,7 +2393,7 @@ class StableBrowser {
|
|
|
2276
2393
|
status: "FAILED",
|
|
2277
2394
|
startTime,
|
|
2278
2395
|
endTime,
|
|
2279
|
-
message: error
|
|
2396
|
+
message: error?.message,
|
|
2280
2397
|
}
|
|
2281
2398
|
: {
|
|
2282
2399
|
status: "PASSED",
|
|
@@ -2308,13 +2425,14 @@ class StableBrowser {
|
|
|
2308
2425
|
this.logger.info("Table data verified");
|
|
2309
2426
|
}
|
|
2310
2427
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2311
|
-
|
|
2428
|
+
_validateSelectors(selectors);
|
|
2312
2429
|
const startTime = Date.now();
|
|
2313
2430
|
let error = null;
|
|
2314
2431
|
let screenshotId = null;
|
|
2315
2432
|
let screenshotPath = null;
|
|
2316
2433
|
const info = {};
|
|
2317
2434
|
info.log = "";
|
|
2435
|
+
info.locatorLog = new LocatorLog(selectors);
|
|
2318
2436
|
info.operation = "getTableData";
|
|
2319
2437
|
info.selectors = selectors;
|
|
2320
2438
|
try {
|
|
@@ -2330,11 +2448,12 @@ class StableBrowser {
|
|
|
2330
2448
|
info.screenshotPath = screenshotPath;
|
|
2331
2449
|
Object.assign(e, { info: info });
|
|
2332
2450
|
error = e;
|
|
2333
|
-
throw e;
|
|
2451
|
+
// throw e;
|
|
2452
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2334
2453
|
}
|
|
2335
2454
|
finally {
|
|
2336
2455
|
const endTime = Date.now();
|
|
2337
|
-
|
|
2456
|
+
_reportToWorld(world, {
|
|
2338
2457
|
element_name: selectors.element_name,
|
|
2339
2458
|
type: Types.GET_TABLE_DATA,
|
|
2340
2459
|
text: "Get table data",
|
|
@@ -2344,7 +2463,7 @@ class StableBrowser {
|
|
|
2344
2463
|
status: "FAILED",
|
|
2345
2464
|
startTime,
|
|
2346
2465
|
endTime,
|
|
2347
|
-
message: error
|
|
2466
|
+
message: error?.message,
|
|
2348
2467
|
}
|
|
2349
2468
|
: {
|
|
2350
2469
|
status: "PASSED",
|
|
@@ -2356,7 +2475,7 @@ class StableBrowser {
|
|
|
2356
2475
|
}
|
|
2357
2476
|
}
|
|
2358
2477
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2359
|
-
|
|
2478
|
+
_validateSelectors(selectors);
|
|
2360
2479
|
if (!query) {
|
|
2361
2480
|
throw new Error("query is null");
|
|
2362
2481
|
}
|
|
@@ -2389,7 +2508,7 @@ class StableBrowser {
|
|
|
2389
2508
|
info.operation = "analyzeTable";
|
|
2390
2509
|
info.selectors = selectors;
|
|
2391
2510
|
info.query = query;
|
|
2392
|
-
query =
|
|
2511
|
+
query = _fixUsingParams(query, _params);
|
|
2393
2512
|
info.query_fixed = query;
|
|
2394
2513
|
info.operator = operator;
|
|
2395
2514
|
info.value = value;
|
|
@@ -2495,11 +2614,12 @@ class StableBrowser {
|
|
|
2495
2614
|
info.screenshotPath = screenshotPath;
|
|
2496
2615
|
Object.assign(e, { info: info });
|
|
2497
2616
|
error = e;
|
|
2498
|
-
throw e;
|
|
2617
|
+
// throw e;
|
|
2618
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2499
2619
|
}
|
|
2500
2620
|
finally {
|
|
2501
2621
|
const endTime = Date.now();
|
|
2502
|
-
|
|
2622
|
+
_reportToWorld(world, {
|
|
2503
2623
|
element_name: selectors.element_name,
|
|
2504
2624
|
type: Types.ANALYZE_TABLE,
|
|
2505
2625
|
text: "Analyze table",
|
|
@@ -2509,7 +2629,7 @@ class StableBrowser {
|
|
|
2509
2629
|
status: "FAILED",
|
|
2510
2630
|
startTime,
|
|
2511
2631
|
endTime,
|
|
2512
|
-
message: error
|
|
2632
|
+
message: error?.message,
|
|
2513
2633
|
}
|
|
2514
2634
|
: {
|
|
2515
2635
|
status: "PASSED",
|
|
@@ -2521,27 +2641,7 @@ class StableBrowser {
|
|
|
2521
2641
|
}
|
|
2522
2642
|
}
|
|
2523
2643
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2524
|
-
|
|
2525
|
-
return value;
|
|
2526
|
-
}
|
|
2527
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2528
|
-
let regex = /{{(.*?)}}/g;
|
|
2529
|
-
let matches = value.match(regex);
|
|
2530
|
-
if (matches) {
|
|
2531
|
-
const testData = this.getTestData(world);
|
|
2532
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2533
|
-
let match = matches[i];
|
|
2534
|
-
let key = match.substring(2, match.length - 2);
|
|
2535
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2536
|
-
if (newValue !== null) {
|
|
2537
|
-
value = value.replace(match, newValue);
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
|
-
}
|
|
2541
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2542
|
-
return await decrypt(value, null, totpWait);
|
|
2543
|
-
}
|
|
2544
|
-
return value;
|
|
2644
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2545
2645
|
}
|
|
2546
2646
|
_getLoadTimeout(options) {
|
|
2547
2647
|
let timeout = 15000;
|
|
@@ -2578,13 +2678,13 @@ class StableBrowser {
|
|
|
2578
2678
|
}
|
|
2579
2679
|
catch (e) {
|
|
2580
2680
|
if (e.label === "networkidle") {
|
|
2581
|
-
console.log("
|
|
2681
|
+
console.log("waited for the network to be idle timeout");
|
|
2582
2682
|
}
|
|
2583
2683
|
else if (e.label === "load") {
|
|
2584
|
-
console.log("
|
|
2684
|
+
console.log("waited for the load timeout");
|
|
2585
2685
|
}
|
|
2586
2686
|
else if (e.label === "domcontentloaded") {
|
|
2587
|
-
console.log("
|
|
2687
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2588
2688
|
}
|
|
2589
2689
|
console.log(".");
|
|
2590
2690
|
}
|
|
@@ -2592,7 +2692,7 @@ class StableBrowser {
|
|
|
2592
2692
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2593
2693
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2594
2694
|
const endTime = Date.now();
|
|
2595
|
-
|
|
2695
|
+
_reportToWorld(world, {
|
|
2596
2696
|
type: Types.GET_PAGE_STATUS,
|
|
2597
2697
|
text: "Wait for page load",
|
|
2598
2698
|
screenshotId,
|
|
@@ -2601,7 +2701,7 @@ class StableBrowser {
|
|
|
2601
2701
|
status: "FAILED",
|
|
2602
2702
|
startTime,
|
|
2603
2703
|
endTime,
|
|
2604
|
-
message: error
|
|
2704
|
+
message: error?.message,
|
|
2605
2705
|
}
|
|
2606
2706
|
: {
|
|
2607
2707
|
status: "PASSED",
|
|
@@ -2612,41 +2712,35 @@ class StableBrowser {
|
|
|
2612
2712
|
}
|
|
2613
2713
|
}
|
|
2614
2714
|
async closePage(options = {}, world = null) {
|
|
2615
|
-
const
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2715
|
+
const state = {
|
|
2716
|
+
options,
|
|
2717
|
+
world,
|
|
2718
|
+
locate: false,
|
|
2719
|
+
scroll: false,
|
|
2720
|
+
highlight: false,
|
|
2721
|
+
type: Types.CLOSE_PAGE,
|
|
2722
|
+
text: `Close page`,
|
|
2723
|
+
operation: "closePage",
|
|
2724
|
+
log: "***** close page *****\n",
|
|
2725
|
+
throwError: false,
|
|
2726
|
+
};
|
|
2620
2727
|
try {
|
|
2728
|
+
await _preCommand(state, this);
|
|
2621
2729
|
await this.page.close();
|
|
2622
2730
|
}
|
|
2623
2731
|
catch (e) {
|
|
2624
2732
|
console.log(".");
|
|
2733
|
+
await _commandError(state, e, this);
|
|
2625
2734
|
}
|
|
2626
2735
|
finally {
|
|
2627
|
-
|
|
2628
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2629
|
-
const endTime = Date.now();
|
|
2630
|
-
this._reportToWorld(world, {
|
|
2631
|
-
type: Types.CLOSE_PAGE,
|
|
2632
|
-
text: "close page",
|
|
2633
|
-
screenshotId,
|
|
2634
|
-
result: error
|
|
2635
|
-
? {
|
|
2636
|
-
status: "FAILED",
|
|
2637
|
-
startTime,
|
|
2638
|
-
endTime,
|
|
2639
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2640
|
-
}
|
|
2641
|
-
: {
|
|
2642
|
-
status: "PASSED",
|
|
2643
|
-
startTime,
|
|
2644
|
-
endTime,
|
|
2645
|
-
},
|
|
2646
|
-
info: info,
|
|
2647
|
-
});
|
|
2736
|
+
_commandFinally(state, this);
|
|
2648
2737
|
}
|
|
2649
2738
|
}
|
|
2739
|
+
saveTestDataAsGlobal(options, world) {
|
|
2740
|
+
const dataFile = this._getDataFile(world);
|
|
2741
|
+
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
2742
|
+
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
2743
|
+
}
|
|
2650
2744
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
2651
2745
|
const startTime = Date.now();
|
|
2652
2746
|
let error = null;
|
|
@@ -2664,12 +2758,13 @@ class StableBrowser {
|
|
|
2664
2758
|
}
|
|
2665
2759
|
catch (e) {
|
|
2666
2760
|
console.log(".");
|
|
2761
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2667
2762
|
}
|
|
2668
2763
|
finally {
|
|
2669
2764
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2670
2765
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2671
2766
|
const endTime = Date.now();
|
|
2672
|
-
|
|
2767
|
+
_reportToWorld(world, {
|
|
2673
2768
|
type: Types.SET_VIEWPORT,
|
|
2674
2769
|
text: "set viewport size to " + width + "x" + hight,
|
|
2675
2770
|
screenshotId,
|
|
@@ -2678,7 +2773,7 @@ class StableBrowser {
|
|
|
2678
2773
|
status: "FAILED",
|
|
2679
2774
|
startTime,
|
|
2680
2775
|
endTime,
|
|
2681
|
-
message: error
|
|
2776
|
+
message: error?.message,
|
|
2682
2777
|
}
|
|
2683
2778
|
: {
|
|
2684
2779
|
status: "PASSED",
|
|
@@ -2700,12 +2795,13 @@ class StableBrowser {
|
|
|
2700
2795
|
}
|
|
2701
2796
|
catch (e) {
|
|
2702
2797
|
console.log(".");
|
|
2798
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2703
2799
|
}
|
|
2704
2800
|
finally {
|
|
2705
2801
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2706
2802
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2707
2803
|
const endTime = Date.now();
|
|
2708
|
-
|
|
2804
|
+
_reportToWorld(world, {
|
|
2709
2805
|
type: Types.GET_PAGE_STATUS,
|
|
2710
2806
|
text: "page relaod",
|
|
2711
2807
|
screenshotId,
|
|
@@ -2714,7 +2810,7 @@ class StableBrowser {
|
|
|
2714
2810
|
status: "FAILED",
|
|
2715
2811
|
startTime,
|
|
2716
2812
|
endTime,
|
|
2717
|
-
message: error
|
|
2813
|
+
message: error?.message,
|
|
2718
2814
|
}
|
|
2719
2815
|
: {
|
|
2720
2816
|
status: "PASSED",
|
|
@@ -2727,40 +2823,59 @@ class StableBrowser {
|
|
|
2727
2823
|
}
|
|
2728
2824
|
async scrollIfNeeded(element, info) {
|
|
2729
2825
|
try {
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
if (rect &&
|
|
2733
|
-
rect.top >= 0 &&
|
|
2734
|
-
rect.left >= 0 &&
|
|
2735
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2736
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2737
|
-
return false;
|
|
2738
|
-
}
|
|
2739
|
-
else {
|
|
2740
|
-
node.scrollIntoView({
|
|
2741
|
-
behavior: "smooth",
|
|
2742
|
-
block: "center",
|
|
2743
|
-
inline: "center",
|
|
2744
|
-
});
|
|
2745
|
-
return true;
|
|
2746
|
-
}
|
|
2826
|
+
await element.scrollIntoViewIfNeeded({
|
|
2827
|
+
timeout: 2000,
|
|
2747
2828
|
});
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
}
|
|
2829
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2830
|
+
if (info) {
|
|
2831
|
+
info.box = await element.boundingBox({
|
|
2832
|
+
timeout: 1000,
|
|
2833
|
+
});
|
|
2753
2834
|
}
|
|
2754
2835
|
}
|
|
2755
2836
|
catch (e) {
|
|
2756
|
-
console.log("
|
|
2837
|
+
console.log("#-#");
|
|
2757
2838
|
}
|
|
2758
2839
|
}
|
|
2759
|
-
|
|
2760
|
-
if (
|
|
2761
|
-
|
|
2840
|
+
async beforeStep(world, step) {
|
|
2841
|
+
if (this.stepIndex === undefined) {
|
|
2842
|
+
this.stepIndex = 0;
|
|
2843
|
+
}
|
|
2844
|
+
else {
|
|
2845
|
+
this.stepIndex++;
|
|
2846
|
+
}
|
|
2847
|
+
if (step && step.pickleStep && step.pickleStep.text) {
|
|
2848
|
+
this.stepName = step.pickleStep.text;
|
|
2849
|
+
this.logger.info("step: " + this.stepName);
|
|
2850
|
+
}
|
|
2851
|
+
else if (step && step.text) {
|
|
2852
|
+
this.stepName = step.text;
|
|
2853
|
+
}
|
|
2854
|
+
else {
|
|
2855
|
+
this.stepName = "step " + this.stepIndex;
|
|
2856
|
+
}
|
|
2857
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2858
|
+
if (this.context.browserObject.context) {
|
|
2859
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
if (this.tags === null && step && step.pickle && step.pickle.tags) {
|
|
2863
|
+
this.tags = step.pickle.tags.map((tag) => tag.name);
|
|
2864
|
+
// check if @global_test_data tag is present
|
|
2865
|
+
if (this.tags.includes("@global_test_data")) {
|
|
2866
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
async afterStep(world, step) {
|
|
2871
|
+
this.stepName = null;
|
|
2872
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2873
|
+
if (this.context.browserObject.context) {
|
|
2874
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2875
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2762
2878
|
}
|
|
2763
|
-
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2764
2879
|
}
|
|
2765
2880
|
}
|
|
2766
2881
|
function createTimedPromise(promise, label) {
|
|
@@ -2768,151 +2883,5 @@ function createTimedPromise(promise, label) {
|
|
|
2768
2883
|
.then((result) => ({ status: "fulfilled", label, result }))
|
|
2769
2884
|
.catch((error) => Promise.reject({ status: "rejected", label, error }));
|
|
2770
2885
|
}
|
|
2771
|
-
const KEYBOARD_EVENTS = [
|
|
2772
|
-
"ALT",
|
|
2773
|
-
"AltGraph",
|
|
2774
|
-
"CapsLock",
|
|
2775
|
-
"Control",
|
|
2776
|
-
"Fn",
|
|
2777
|
-
"FnLock",
|
|
2778
|
-
"Hyper",
|
|
2779
|
-
"Meta",
|
|
2780
|
-
"NumLock",
|
|
2781
|
-
"ScrollLock",
|
|
2782
|
-
"Shift",
|
|
2783
|
-
"Super",
|
|
2784
|
-
"Symbol",
|
|
2785
|
-
"SymbolLock",
|
|
2786
|
-
"Enter",
|
|
2787
|
-
"Tab",
|
|
2788
|
-
"ArrowDown",
|
|
2789
|
-
"ArrowLeft",
|
|
2790
|
-
"ArrowRight",
|
|
2791
|
-
"ArrowUp",
|
|
2792
|
-
"End",
|
|
2793
|
-
"Home",
|
|
2794
|
-
"PageDown",
|
|
2795
|
-
"PageUp",
|
|
2796
|
-
"Backspace",
|
|
2797
|
-
"Clear",
|
|
2798
|
-
"Copy",
|
|
2799
|
-
"CrSel",
|
|
2800
|
-
"Cut",
|
|
2801
|
-
"Delete",
|
|
2802
|
-
"EraseEof",
|
|
2803
|
-
"ExSel",
|
|
2804
|
-
"Insert",
|
|
2805
|
-
"Paste",
|
|
2806
|
-
"Redo",
|
|
2807
|
-
"Undo",
|
|
2808
|
-
"Accept",
|
|
2809
|
-
"Again",
|
|
2810
|
-
"Attn",
|
|
2811
|
-
"Cancel",
|
|
2812
|
-
"ContextMenu",
|
|
2813
|
-
"Escape",
|
|
2814
|
-
"Execute",
|
|
2815
|
-
"Find",
|
|
2816
|
-
"Finish",
|
|
2817
|
-
"Help",
|
|
2818
|
-
"Pause",
|
|
2819
|
-
"Play",
|
|
2820
|
-
"Props",
|
|
2821
|
-
"Select",
|
|
2822
|
-
"ZoomIn",
|
|
2823
|
-
"ZoomOut",
|
|
2824
|
-
"BrightnessDown",
|
|
2825
|
-
"BrightnessUp",
|
|
2826
|
-
"Eject",
|
|
2827
|
-
"LogOff",
|
|
2828
|
-
"Power",
|
|
2829
|
-
"PowerOff",
|
|
2830
|
-
"PrintScreen",
|
|
2831
|
-
"Hibernate",
|
|
2832
|
-
"Standby",
|
|
2833
|
-
"WakeUp",
|
|
2834
|
-
"AllCandidates",
|
|
2835
|
-
"Alphanumeric",
|
|
2836
|
-
"CodeInput",
|
|
2837
|
-
"Compose",
|
|
2838
|
-
"Convert",
|
|
2839
|
-
"Dead",
|
|
2840
|
-
"FinalMode",
|
|
2841
|
-
"GroupFirst",
|
|
2842
|
-
"GroupLast",
|
|
2843
|
-
"GroupNext",
|
|
2844
|
-
"GroupPrevious",
|
|
2845
|
-
"ModeChange",
|
|
2846
|
-
"NextCandidate",
|
|
2847
|
-
"NonConvert",
|
|
2848
|
-
"PreviousCandidate",
|
|
2849
|
-
"Process",
|
|
2850
|
-
"SingleCandidate",
|
|
2851
|
-
"HangulMode",
|
|
2852
|
-
"HanjaMode",
|
|
2853
|
-
"JunjaMode",
|
|
2854
|
-
"Eisu",
|
|
2855
|
-
"Hankaku",
|
|
2856
|
-
"Hiragana",
|
|
2857
|
-
"HiraganaKatakana",
|
|
2858
|
-
"KanaMode",
|
|
2859
|
-
"KanjiMode",
|
|
2860
|
-
"Katakana",
|
|
2861
|
-
"Romaji",
|
|
2862
|
-
"Zenkaku",
|
|
2863
|
-
"ZenkakuHanaku",
|
|
2864
|
-
"F1",
|
|
2865
|
-
"F2",
|
|
2866
|
-
"F3",
|
|
2867
|
-
"F4",
|
|
2868
|
-
"F5",
|
|
2869
|
-
"F6",
|
|
2870
|
-
"F7",
|
|
2871
|
-
"F8",
|
|
2872
|
-
"F9",
|
|
2873
|
-
"F10",
|
|
2874
|
-
"F11",
|
|
2875
|
-
"F12",
|
|
2876
|
-
"Soft1",
|
|
2877
|
-
"Soft2",
|
|
2878
|
-
"Soft3",
|
|
2879
|
-
"Soft4",
|
|
2880
|
-
"ChannelDown",
|
|
2881
|
-
"ChannelUp",
|
|
2882
|
-
"Close",
|
|
2883
|
-
"MailForward",
|
|
2884
|
-
"MailReply",
|
|
2885
|
-
"MailSend",
|
|
2886
|
-
"MediaFastForward",
|
|
2887
|
-
"MediaPause",
|
|
2888
|
-
"MediaPlay",
|
|
2889
|
-
"MediaPlayPause",
|
|
2890
|
-
"MediaRecord",
|
|
2891
|
-
"MediaRewind",
|
|
2892
|
-
"MediaStop",
|
|
2893
|
-
"MediaTrackNext",
|
|
2894
|
-
"MediaTrackPrevious",
|
|
2895
|
-
"AudioBalanceLeft",
|
|
2896
|
-
"AudioBalanceRight",
|
|
2897
|
-
"AudioBassBoostDown",
|
|
2898
|
-
"AudioBassBoostToggle",
|
|
2899
|
-
"AudioBassBoostUp",
|
|
2900
|
-
"AudioFaderFront",
|
|
2901
|
-
"AudioFaderRear",
|
|
2902
|
-
"AudioSurroundModeNext",
|
|
2903
|
-
"AudioTrebleDown",
|
|
2904
|
-
"AudioTrebleUp",
|
|
2905
|
-
"AudioVolumeDown",
|
|
2906
|
-
"AudioVolumeMute",
|
|
2907
|
-
"AudioVolumeUp",
|
|
2908
|
-
"MicrophoneToggle",
|
|
2909
|
-
"MicrophoneVolumeDown",
|
|
2910
|
-
"MicrophoneVolumeMute",
|
|
2911
|
-
"MicrophoneVolumeUp",
|
|
2912
|
-
"TV",
|
|
2913
|
-
"TV3DMode",
|
|
2914
|
-
"TVAntennaCable",
|
|
2915
|
-
"TVAudioDescription",
|
|
2916
|
-
];
|
|
2917
2886
|
export { StableBrowser };
|
|
2918
2887
|
//# sourceMappingURL=stable_browser.js.map
|