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