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