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