automation_model 1.0.402-dev → 1.0.402-main
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 +42 -1
- package/lib/api.js +204 -15
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +1 -1
- package/lib/auto_page.js +26 -17
- package/lib/auto_page.js.map +1 -1
- package/lib/axe/axe.mini.js +12 -0
- package/lib/browser_manager.d.ts +6 -3
- package/lib/browser_manager.js +48 -20
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +5 -0
- package/lib/command_common.js +132 -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 +182 -0
- package/lib/error-messages.js.map +1 -0
- package/lib/init_browser.d.ts +2 -1
- package/lib/init_browser.js +54 -3
- package/lib/init_browser.js.map +1 -1
- package/lib/locate_element.d.ts +7 -0
- package/lib/locate_element.js +213 -0
- package/lib/locate_element.js.map +1 -0
- package/lib/network.d.ts +3 -0
- package/lib/network.js +144 -0
- package/lib/network.js.map +1 -0
- package/lib/stable_browser.d.ts +39 -21
- package/lib/stable_browser.js +913 -877
- package/lib/stable_browser.js.map +1 -1
- 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 +3 -1
- package/lib/utils.js +70 -3
- package/lib/utils.js.map +1 -1
- package/package.json +10 -7
package/lib/stable_browser.js
CHANGED
|
@@ -2,17 +2,22 @@
|
|
|
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 { maskValue, replaceWithLocalTestData } from "./utils.js";
|
|
15
14
|
import csv from "csv-parser";
|
|
15
|
+
import { Readable } from "node:stream";
|
|
16
|
+
import readline from "readline";
|
|
17
|
+
import { getContext } from "./init_browser.js";
|
|
18
|
+
import { locate_element } from "./locate_element.js";
|
|
19
|
+
import { _commandError, _commandFinally, _preCommand, _validateSelectors, _screenshot } from "./command_common.js";
|
|
20
|
+
import { registerDownloadEvent, registerNetworkEvents } from "./network.js";
|
|
16
21
|
const Types = {
|
|
17
22
|
CLICK: "click_element",
|
|
18
23
|
NAVIGATE: "navigate",
|
|
@@ -37,16 +42,28 @@ const Types = {
|
|
|
37
42
|
SET_DATE_TIME: "set_date_time",
|
|
38
43
|
SET_VIEWPORT: "set_viewport",
|
|
39
44
|
VERIFY_VISUAL: "verify_visual",
|
|
45
|
+
LOAD_DATA: "load_data",
|
|
46
|
+
SET_INPUT: "set_input",
|
|
47
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
40
48
|
};
|
|
49
|
+
export const apps = {};
|
|
41
50
|
class StableBrowser {
|
|
42
|
-
|
|
51
|
+
browser;
|
|
52
|
+
page;
|
|
53
|
+
logger;
|
|
54
|
+
context;
|
|
55
|
+
world;
|
|
56
|
+
project_path = null;
|
|
57
|
+
webLogFile = null;
|
|
58
|
+
networkLogger = null;
|
|
59
|
+
configuration = null;
|
|
60
|
+
appName = "main";
|
|
61
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
43
62
|
this.browser = browser;
|
|
44
63
|
this.page = page;
|
|
45
64
|
this.logger = logger;
|
|
46
65
|
this.context = context;
|
|
47
|
-
this.
|
|
48
|
-
this.webLogFile = null;
|
|
49
|
-
this.configuration = null;
|
|
66
|
+
this.world = world;
|
|
50
67
|
if (!this.logger) {
|
|
51
68
|
this.logger = console;
|
|
52
69
|
}
|
|
@@ -72,22 +89,44 @@ class StableBrowser {
|
|
|
72
89
|
this.logger.error("unable to read ai_config.json");
|
|
73
90
|
}
|
|
74
91
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
75
|
-
this.
|
|
76
|
-
this.registerConsoleLogListener(page, context, this.webLogFile);
|
|
77
|
-
this.registerRequestListener();
|
|
92
|
+
this.world = world;
|
|
78
93
|
context.pages = [this.page];
|
|
79
94
|
context.pageLoading = { status: false };
|
|
95
|
+
this.registerEventListeners(this.context);
|
|
96
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
97
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
98
|
+
}
|
|
99
|
+
registerEventListeners(context) {
|
|
100
|
+
this.registerConsoleLogListener(this.page, context);
|
|
101
|
+
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
102
|
+
if (!context.pageLoading) {
|
|
103
|
+
context.pageLoading = { status: false };
|
|
104
|
+
}
|
|
80
105
|
context.playContext.on("page", async function (page) {
|
|
106
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
107
|
+
console.log("close unexpected popups");
|
|
108
|
+
await page.close();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
81
111
|
context.pageLoading.status = true;
|
|
82
112
|
this.page = page;
|
|
83
113
|
context.page = page;
|
|
84
114
|
context.pages.push(page);
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
116
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
117
|
+
page.on("close", async () => {
|
|
118
|
+
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
119
|
+
this.context.pages.pop();
|
|
120
|
+
this.page = this.context.pages[this.context.pages.length - 1];
|
|
121
|
+
this.context.page = this.page;
|
|
122
|
+
try {
|
|
123
|
+
let title = await this.page.title();
|
|
124
|
+
console.log("Switched to page " + title);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
console.error("Error on page close", error);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
91
130
|
});
|
|
92
131
|
try {
|
|
93
132
|
await this.waitForPageLoad();
|
|
@@ -99,6 +138,36 @@ class StableBrowser {
|
|
|
99
138
|
context.pageLoading.status = false;
|
|
100
139
|
}.bind(this));
|
|
101
140
|
}
|
|
141
|
+
async switchApp(appName) {
|
|
142
|
+
// check if the current app (this.appName) is the same as the new app
|
|
143
|
+
if (this.appName === appName) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
let navigate = false;
|
|
147
|
+
if (!apps[appName]) {
|
|
148
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
149
|
+
newContextCreated = true;
|
|
150
|
+
apps[appName] = {
|
|
151
|
+
context: newContext,
|
|
152
|
+
browser: newContext.browser,
|
|
153
|
+
page: newContext.page,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const tempContext = {};
|
|
157
|
+
this._copyContext(this, tempContext);
|
|
158
|
+
this._copyContext(apps[appName], this);
|
|
159
|
+
apps[this.appName] = tempContext;
|
|
160
|
+
this.appName = appName;
|
|
161
|
+
if (navigate) {
|
|
162
|
+
await this.goto(this.context.environment.baseUrl);
|
|
163
|
+
await this.waitForPageLoad();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
_copyContext(from, to) {
|
|
167
|
+
to.browser = from.browser;
|
|
168
|
+
to.page = from.page;
|
|
169
|
+
to.context = from.context;
|
|
170
|
+
}
|
|
102
171
|
getWebLogFile(logFolder) {
|
|
103
172
|
if (!fs.existsSync(logFolder)) {
|
|
104
173
|
fs.mkdirSync(logFolder, { recursive: true });
|
|
@@ -110,37 +179,63 @@ class StableBrowser {
|
|
|
110
179
|
const fileName = nextIndex + ".json";
|
|
111
180
|
return path.join(logFolder, fileName);
|
|
112
181
|
}
|
|
113
|
-
registerConsoleLogListener(page, context
|
|
182
|
+
registerConsoleLogListener(page, context) {
|
|
114
183
|
if (!this.context.webLogger) {
|
|
115
184
|
this.context.webLogger = [];
|
|
116
185
|
}
|
|
117
186
|
page.on("console", async (msg) => {
|
|
118
|
-
|
|
187
|
+
const obj = {
|
|
119
188
|
type: msg.type(),
|
|
120
189
|
text: msg.text(),
|
|
121
190
|
location: msg.location(),
|
|
122
191
|
time: new Date().toISOString(),
|
|
123
|
-
}
|
|
124
|
-
|
|
192
|
+
};
|
|
193
|
+
this.context.webLogger.push(obj);
|
|
194
|
+
if (msg.type() === "error") {
|
|
195
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
196
|
+
}
|
|
125
197
|
});
|
|
126
198
|
}
|
|
127
|
-
registerRequestListener() {
|
|
128
|
-
this.
|
|
199
|
+
registerRequestListener(page, context, logFile) {
|
|
200
|
+
if (!this.context.networkLogger) {
|
|
201
|
+
this.context.networkLogger = [];
|
|
202
|
+
}
|
|
203
|
+
page.on("request", async (data) => {
|
|
204
|
+
const startTime = new Date().getTime();
|
|
129
205
|
try {
|
|
130
|
-
const pageUrl = new URL(
|
|
206
|
+
const pageUrl = new URL(page.url());
|
|
131
207
|
const requestUrl = new URL(data.url());
|
|
132
208
|
if (pageUrl.hostname === requestUrl.hostname) {
|
|
133
209
|
const method = data.method();
|
|
134
|
-
if (
|
|
210
|
+
if (["POST", "GET", "PUT", "DELETE", "PATCH"].includes(method)) {
|
|
135
211
|
const token = await data.headerValue("Authorization");
|
|
136
212
|
if (token) {
|
|
137
|
-
|
|
213
|
+
context.authtoken = token;
|
|
138
214
|
}
|
|
139
215
|
}
|
|
140
216
|
}
|
|
217
|
+
const response = await data.response();
|
|
218
|
+
const endTime = new Date().getTime();
|
|
219
|
+
const obj = {
|
|
220
|
+
url: data.url(),
|
|
221
|
+
method: data.method(),
|
|
222
|
+
postData: data.postData(),
|
|
223
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
224
|
+
duration: endTime - startTime,
|
|
225
|
+
startTime,
|
|
226
|
+
};
|
|
227
|
+
context.networkLogger.push(obj);
|
|
228
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
141
229
|
}
|
|
142
230
|
catch (error) {
|
|
143
231
|
console.error("Error in request listener", error);
|
|
232
|
+
context.networkLogger.push({
|
|
233
|
+
error: "not able to listen",
|
|
234
|
+
message: error.message,
|
|
235
|
+
stack: error.stack,
|
|
236
|
+
time: new Date().toISOString(),
|
|
237
|
+
});
|
|
238
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
144
239
|
}
|
|
145
240
|
});
|
|
146
241
|
}
|
|
@@ -155,20 +250,6 @@ class StableBrowser {
|
|
|
155
250
|
timeout: 60000,
|
|
156
251
|
});
|
|
157
252
|
}
|
|
158
|
-
_validateSelectors(selectors) {
|
|
159
|
-
if (!selectors) {
|
|
160
|
-
throw new Error("selectors is null");
|
|
161
|
-
}
|
|
162
|
-
if (!selectors.locators) {
|
|
163
|
-
throw new Error("selectors.locators is null");
|
|
164
|
-
}
|
|
165
|
-
if (!Array.isArray(selectors.locators)) {
|
|
166
|
-
throw new Error("selectors.locators expected to be array");
|
|
167
|
-
}
|
|
168
|
-
if (selectors.locators.length === 0) {
|
|
169
|
-
throw new Error("selectors.locators expected to be non empty array");
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
253
|
_fixUsingParams(text, _params) {
|
|
173
254
|
if (!_params || typeof text !== "string") {
|
|
174
255
|
return text;
|
|
@@ -183,35 +264,84 @@ class StableBrowser {
|
|
|
183
264
|
}
|
|
184
265
|
return text;
|
|
185
266
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
267
|
+
_fixLocatorUsingParams(locator, _params) {
|
|
268
|
+
// check if not null
|
|
269
|
+
if (!locator) {
|
|
270
|
+
return locator;
|
|
189
271
|
}
|
|
272
|
+
// clone the locator
|
|
273
|
+
locator = JSON.parse(JSON.stringify(locator));
|
|
274
|
+
this.scanAndManipulate(locator, _params);
|
|
275
|
+
return locator;
|
|
276
|
+
}
|
|
277
|
+
_isObject(value) {
|
|
278
|
+
return value && typeof value === "object" && value.constructor === Object;
|
|
279
|
+
}
|
|
280
|
+
scanAndManipulate(currentObj, _params) {
|
|
281
|
+
for (const key in currentObj) {
|
|
282
|
+
if (typeof currentObj[key] === "string") {
|
|
283
|
+
// Perform string manipulation
|
|
284
|
+
currentObj[key] = this._fixUsingParams(currentObj[key], _params);
|
|
285
|
+
}
|
|
286
|
+
else if (this._isObject(currentObj[key])) {
|
|
287
|
+
// Recursively scan nested objects
|
|
288
|
+
this.scanAndManipulate(currentObj[key], _params);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
_getLocator(locator, scope, _params) {
|
|
293
|
+
locator = this._fixLocatorUsingParams(locator, _params);
|
|
294
|
+
let locatorReturn;
|
|
190
295
|
if (locator.role) {
|
|
191
296
|
if (locator.role[1].nameReg) {
|
|
192
297
|
locator.role[1].name = reg_parser(locator.role[1].nameReg);
|
|
193
298
|
delete locator.role[1].nameReg;
|
|
194
299
|
}
|
|
195
|
-
if (locator.role[1].name) {
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
300
|
+
// if (locator.role[1].name) {
|
|
301
|
+
// locator.role[1].name = this._fixUsingParams(locator.role[1].name, _params);
|
|
302
|
+
// }
|
|
303
|
+
locatorReturn = scope.getByRole(locator.role[0], locator.role[1]);
|
|
199
304
|
}
|
|
200
305
|
if (locator.css) {
|
|
201
|
-
|
|
306
|
+
locatorReturn = scope.locator(locator.css);
|
|
202
307
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
308
|
+
// handle role/name locators
|
|
309
|
+
// locator.selector will be something like: textbox[name="Username"i]
|
|
310
|
+
if (locator.engine === "internal:role") {
|
|
311
|
+
// extract the role, name and the i/s flags using regex
|
|
312
|
+
const match = locator.selector.match(/(.*)\[(.*)="(.*)"(.*)\]/);
|
|
313
|
+
if (match) {
|
|
314
|
+
const role = match[1];
|
|
315
|
+
const name = match[3];
|
|
316
|
+
const flags = match[4];
|
|
317
|
+
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
207
318
|
}
|
|
208
|
-
const locator = scope.locator(`${locator.engine}="${selector}"`);
|
|
209
|
-
return locator;
|
|
210
319
|
}
|
|
211
|
-
|
|
320
|
+
if (locator?.engine) {
|
|
321
|
+
if (locator.engine === "css") {
|
|
322
|
+
locatorReturn = scope.locator(locator.selector);
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
let selector = locator.selector;
|
|
326
|
+
if (locator.engine === "internal:attr") {
|
|
327
|
+
if (!selector.startsWith("[")) {
|
|
328
|
+
selector = `[${selector}]`;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
locatorReturn = scope.locator(`${locator.engine}=${selector}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (!locatorReturn) {
|
|
335
|
+
console.error(locator);
|
|
336
|
+
throw new Error("Locator undefined");
|
|
337
|
+
}
|
|
338
|
+
return locatorReturn;
|
|
212
339
|
}
|
|
213
340
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
214
|
-
|
|
341
|
+
if (css && css.locator) {
|
|
342
|
+
css = css.locator;
|
|
343
|
+
}
|
|
344
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
215
345
|
if (result.elementCount === 0) {
|
|
216
346
|
return;
|
|
217
347
|
}
|
|
@@ -226,7 +356,7 @@ class StableBrowser {
|
|
|
226
356
|
}
|
|
227
357
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
228
358
|
//const stringifyText = JSON.stringify(text);
|
|
229
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
359
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
230
360
|
function isParent(parent, child) {
|
|
231
361
|
let currentNode = child.parentNode;
|
|
232
362
|
while (currentNode !== null) {
|
|
@@ -238,6 +368,15 @@ class StableBrowser {
|
|
|
238
368
|
return false;
|
|
239
369
|
}
|
|
240
370
|
document.isParent = isParent;
|
|
371
|
+
function getRegex(str) {
|
|
372
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
373
|
+
if (!match) {
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
let [_, pattern, flags] = match;
|
|
377
|
+
return new RegExp(pattern, flags);
|
|
378
|
+
}
|
|
379
|
+
document.getRegex = getRegex;
|
|
241
380
|
function collectAllShadowDomElements(element, result = []) {
|
|
242
381
|
// Check and add the element if it has a shadow root
|
|
243
382
|
if (element.shadowRoot) {
|
|
@@ -254,7 +393,11 @@ class StableBrowser {
|
|
|
254
393
|
}
|
|
255
394
|
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
256
395
|
if (!tag) {
|
|
257
|
-
tag = "
|
|
396
|
+
tag = "*:not(script, style, head)";
|
|
397
|
+
}
|
|
398
|
+
let regexpSearch = document.getRegex(text);
|
|
399
|
+
if (regexpSearch) {
|
|
400
|
+
regex = true;
|
|
258
401
|
}
|
|
259
402
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
260
403
|
let shadowHosts = [];
|
|
@@ -271,7 +414,9 @@ class StableBrowser {
|
|
|
271
414
|
let randomToken = null;
|
|
272
415
|
const foundElements = [];
|
|
273
416
|
if (regex) {
|
|
274
|
-
|
|
417
|
+
if (!regexpSearch) {
|
|
418
|
+
regexpSearch = new RegExp(text, "im");
|
|
419
|
+
}
|
|
275
420
|
for (let i = 0; i < elements.length; i++) {
|
|
276
421
|
const element = elements[i];
|
|
277
422
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -285,8 +430,8 @@ class StableBrowser {
|
|
|
285
430
|
for (let i = 0; i < elements.length; i++) {
|
|
286
431
|
const element = elements[i];
|
|
287
432
|
if (partial) {
|
|
288
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
289
|
-
(element.value && element.value.includes(text))) {
|
|
433
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
434
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
290
435
|
foundElements.push(element);
|
|
291
436
|
}
|
|
292
437
|
}
|
|
@@ -330,19 +475,39 @@ class StableBrowser {
|
|
|
330
475
|
}, [text1, tag1, regex1, partial1]);
|
|
331
476
|
}
|
|
332
477
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
478
|
+
if (!info) {
|
|
479
|
+
info = {};
|
|
480
|
+
}
|
|
481
|
+
if (!info.failCause) {
|
|
482
|
+
info.failCause = {};
|
|
483
|
+
}
|
|
484
|
+
if (!info.log) {
|
|
485
|
+
info.log = "";
|
|
486
|
+
}
|
|
333
487
|
let locatorSearch = selectorHierarchy[index];
|
|
488
|
+
try {
|
|
489
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
console.error(e);
|
|
493
|
+
}
|
|
334
494
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
335
495
|
let locator = null;
|
|
336
496
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
337
497
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
338
498
|
if (!locatorString) {
|
|
499
|
+
info.failCause.textNotFound = true;
|
|
500
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
339
501
|
return;
|
|
340
502
|
}
|
|
341
503
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
342
504
|
}
|
|
343
505
|
else if (locatorSearch.text) {
|
|
344
|
-
let
|
|
506
|
+
let text = this._fixUsingParams(locatorSearch.text, _params);
|
|
507
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
345
508
|
if (result.elementCount === 0) {
|
|
509
|
+
info.failCause.textNotFound = true;
|
|
510
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
346
511
|
return;
|
|
347
512
|
}
|
|
348
513
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -359,6 +524,9 @@ class StableBrowser {
|
|
|
359
524
|
// cssHref = true;
|
|
360
525
|
// }
|
|
361
526
|
let count = await locator.count();
|
|
527
|
+
if (count > 0 && !info.failCause.count) {
|
|
528
|
+
info.failCause.count = count;
|
|
529
|
+
}
|
|
362
530
|
//info.log += "total elements found " + count + "\n";
|
|
363
531
|
//let visibleCount = 0;
|
|
364
532
|
let visibleLocator = null;
|
|
@@ -376,6 +544,8 @@ class StableBrowser {
|
|
|
376
544
|
foundLocators.push(locator.nth(j));
|
|
377
545
|
}
|
|
378
546
|
else {
|
|
547
|
+
info.failCause.visible = visible;
|
|
548
|
+
info.failCause.enabled = enabled;
|
|
379
549
|
if (!info.printMessages) {
|
|
380
550
|
info.printMessages = {};
|
|
381
551
|
}
|
|
@@ -387,6 +557,11 @@ class StableBrowser {
|
|
|
387
557
|
}
|
|
388
558
|
}
|
|
389
559
|
async closeUnexpectedPopups(info, _params) {
|
|
560
|
+
if (!info) {
|
|
561
|
+
info = {};
|
|
562
|
+
info.failCause = {};
|
|
563
|
+
info.log = "";
|
|
564
|
+
}
|
|
390
565
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
391
566
|
if (!info) {
|
|
392
567
|
info = {};
|
|
@@ -419,15 +594,20 @@ class StableBrowser {
|
|
|
419
594
|
if (result.foundElements.length > 0) {
|
|
420
595
|
let dialogCloseLocator = result.foundElements[0].locator;
|
|
421
596
|
await dialogCloseLocator.click();
|
|
597
|
+
// wait for the dialog to close
|
|
598
|
+
await dialogCloseLocator.waitFor({ state: "hidden" });
|
|
422
599
|
return { rerun: true };
|
|
423
600
|
}
|
|
424
601
|
}
|
|
425
602
|
}
|
|
426
603
|
return { rerun: false };
|
|
427
604
|
}
|
|
428
|
-
async _locate(selectors, info, _params, timeout
|
|
605
|
+
async _locate(selectors, info, _params, timeout) {
|
|
606
|
+
if (!timeout) {
|
|
607
|
+
timeout = 30000;
|
|
608
|
+
}
|
|
429
609
|
for (let i = 0; i < 3; i++) {
|
|
430
|
-
info.log += "attempt " + i + ":
|
|
610
|
+
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
431
611
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
432
612
|
let selector = selectors.locators[j];
|
|
433
613
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
@@ -439,17 +619,49 @@ class StableBrowser {
|
|
|
439
619
|
}
|
|
440
620
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
441
621
|
}
|
|
442
|
-
async
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
622
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
623
|
+
if (!info) {
|
|
624
|
+
info = {};
|
|
625
|
+
info.failCause = {};
|
|
626
|
+
info.log = "";
|
|
627
|
+
}
|
|
448
628
|
let scope = this.page;
|
|
629
|
+
if (selectors.frame) {
|
|
630
|
+
return selectors.frame;
|
|
631
|
+
}
|
|
449
632
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
450
|
-
|
|
633
|
+
const findFrame = async (frame, framescope) => {
|
|
634
|
+
for (let i = 0; i < frame.selectors.length; i++) {
|
|
635
|
+
let frameLocator = frame.selectors[i];
|
|
636
|
+
if (frameLocator.css) {
|
|
637
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
638
|
+
if (frameLocator.index) {
|
|
639
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
640
|
+
}
|
|
641
|
+
try {
|
|
642
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
643
|
+
timeout: 5000,
|
|
644
|
+
});
|
|
645
|
+
framescope = testframescope;
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
catch (error) {
|
|
649
|
+
console.error("frame not found " + frameLocator.css);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
if (frame.children) {
|
|
654
|
+
return await findFrame(frame.children, framescope);
|
|
655
|
+
}
|
|
656
|
+
return framescope;
|
|
657
|
+
};
|
|
451
658
|
while (true) {
|
|
452
659
|
let frameFound = false;
|
|
660
|
+
if (selectors.nestFrmLoc) {
|
|
661
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
662
|
+
frameFound = true;
|
|
663
|
+
break;
|
|
664
|
+
}
|
|
453
665
|
if (selectors.frameLocators) {
|
|
454
666
|
for (let i = 0; i < selectors.frameLocators.length; i++) {
|
|
455
667
|
let frameLocator = selectors.frameLocators[i];
|
|
@@ -466,6 +678,8 @@ class StableBrowser {
|
|
|
466
678
|
if (!scope) {
|
|
467
679
|
info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
468
680
|
if (performance.now() - startTime > timeout) {
|
|
681
|
+
info.failCause.iframeNotFound = true;
|
|
682
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
469
683
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
470
684
|
}
|
|
471
685
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -475,6 +689,30 @@ class StableBrowser {
|
|
|
475
689
|
}
|
|
476
690
|
}
|
|
477
691
|
}
|
|
692
|
+
if (!scope) {
|
|
693
|
+
scope = this.page;
|
|
694
|
+
}
|
|
695
|
+
return scope;
|
|
696
|
+
}
|
|
697
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
698
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
699
|
+
return scope.evaluate(() => {
|
|
700
|
+
var bodyContent = document.body.innerHTML;
|
|
701
|
+
return bodyContent;
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
705
|
+
if (!info) {
|
|
706
|
+
info = {};
|
|
707
|
+
info.failCause = {};
|
|
708
|
+
info.log = "";
|
|
709
|
+
}
|
|
710
|
+
let highPriorityTimeout = 5000;
|
|
711
|
+
let visibleOnlyTimeout = 6000;
|
|
712
|
+
let startTime = performance.now();
|
|
713
|
+
let locatorsCount = 0;
|
|
714
|
+
//let arrayMode = Array.isArray(selectors);
|
|
715
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
478
716
|
let selectorsLocators = null;
|
|
479
717
|
selectorsLocators = selectors.locators;
|
|
480
718
|
// group selectors by priority
|
|
@@ -576,6 +814,8 @@ class StableBrowser {
|
|
|
576
814
|
}
|
|
577
815
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
578
816
|
info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
817
|
+
info.failCause.locatorNotFound = true;
|
|
818
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
579
819
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
580
820
|
}
|
|
581
821
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -607,86 +847,129 @@ class StableBrowser {
|
|
|
607
847
|
});
|
|
608
848
|
result.locatorIndex = i;
|
|
609
849
|
}
|
|
850
|
+
if (foundLocators.length > 1) {
|
|
851
|
+
info.failCause.foundMultiple = true;
|
|
852
|
+
}
|
|
610
853
|
}
|
|
611
854
|
return result;
|
|
612
855
|
}
|
|
613
|
-
async
|
|
614
|
-
this._validateSelectors(selectors);
|
|
856
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
615
857
|
const startTime = Date.now();
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
858
|
+
let timeout = 30000;
|
|
859
|
+
if (options && options.timeout) {
|
|
860
|
+
timeout = options.timeout;
|
|
861
|
+
}
|
|
862
|
+
while (true) {
|
|
863
|
+
try {
|
|
864
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
865
|
+
if (result?.elementNumber >= 0) {
|
|
866
|
+
const selectors = {
|
|
867
|
+
frame: result?.frame,
|
|
868
|
+
locators: [
|
|
869
|
+
{
|
|
870
|
+
css: result?.css,
|
|
871
|
+
},
|
|
872
|
+
],
|
|
873
|
+
};
|
|
874
|
+
await this.click(selectors, _params, options, world);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
catch (e) {
|
|
879
|
+
if (performance.now() - startTime > timeout) {
|
|
880
|
+
// throw e;
|
|
881
|
+
await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
888
|
+
const startTime = Date.now();
|
|
889
|
+
let timeout = 30000;
|
|
890
|
+
if (options && options.timeout) {
|
|
891
|
+
timeout = options.timeout;
|
|
892
|
+
}
|
|
893
|
+
while (true) {
|
|
894
|
+
try {
|
|
895
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
896
|
+
if (result?.elementNumber >= 0) {
|
|
897
|
+
const selectors = {
|
|
898
|
+
frame: result?.frame,
|
|
899
|
+
locators: [
|
|
900
|
+
{
|
|
901
|
+
css: result?.css,
|
|
902
|
+
},
|
|
903
|
+
],
|
|
904
|
+
};
|
|
905
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
catch (e) {
|
|
910
|
+
if (performance.now() - startTime > timeout) {
|
|
911
|
+
// throw e;
|
|
912
|
+
await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async click(selectors, _params, options = {}, world = null) {
|
|
919
|
+
const state = {
|
|
920
|
+
selectors,
|
|
921
|
+
_params,
|
|
922
|
+
options,
|
|
923
|
+
world,
|
|
924
|
+
text: "Click element",
|
|
925
|
+
type: Types.CLICK,
|
|
926
|
+
operation: "click",
|
|
927
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
928
|
+
};
|
|
623
929
|
try {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
930
|
+
await _preCommand(state, this);
|
|
931
|
+
if (state.options && state.options.context) {
|
|
932
|
+
state.selectors.locators[0].text = state.options.context;
|
|
933
|
+
}
|
|
627
934
|
try {
|
|
628
|
-
await
|
|
629
|
-
await
|
|
630
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
935
|
+
await state.element.click();
|
|
936
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
631
937
|
}
|
|
632
938
|
catch (e) {
|
|
633
939
|
// await this.closeUnexpectedPopups();
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
await
|
|
637
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
940
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
941
|
+
await state.element.dispatchEvent("click");
|
|
942
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
638
943
|
}
|
|
639
944
|
await this.waitForPageLoad();
|
|
640
|
-
return info;
|
|
945
|
+
return state.info;
|
|
641
946
|
}
|
|
642
947
|
catch (e) {
|
|
643
|
-
|
|
644
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
645
|
-
info.screenshotPath = screenshotPath;
|
|
646
|
-
Object.assign(e, { info: info });
|
|
647
|
-
error = e;
|
|
648
|
-
throw e;
|
|
948
|
+
await _commandError(state, e, this);
|
|
649
949
|
}
|
|
650
950
|
finally {
|
|
651
|
-
|
|
652
|
-
this._reportToWorld(world, {
|
|
653
|
-
element_name: selectors.element_name,
|
|
654
|
-
type: Types.CLICK,
|
|
655
|
-
text: `Click element`,
|
|
656
|
-
screenshotId,
|
|
657
|
-
result: error
|
|
658
|
-
? {
|
|
659
|
-
status: "FAILED",
|
|
660
|
-
startTime,
|
|
661
|
-
endTime,
|
|
662
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
663
|
-
}
|
|
664
|
-
: {
|
|
665
|
-
status: "PASSED",
|
|
666
|
-
startTime,
|
|
667
|
-
endTime,
|
|
668
|
-
},
|
|
669
|
-
info: info,
|
|
670
|
-
});
|
|
951
|
+
_commandFinally(state, this);
|
|
671
952
|
}
|
|
672
953
|
}
|
|
673
954
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
955
|
+
const state = {
|
|
956
|
+
selectors,
|
|
957
|
+
_params,
|
|
958
|
+
options,
|
|
959
|
+
world,
|
|
960
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
961
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
962
|
+
operation: "setCheck",
|
|
963
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
964
|
+
};
|
|
684
965
|
try {
|
|
685
|
-
|
|
686
|
-
|
|
966
|
+
await _preCommand(state, this);
|
|
967
|
+
state.info.checked = checked;
|
|
968
|
+
// let element = await this._locate(selectors, info, _params);
|
|
969
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
687
970
|
try {
|
|
688
|
-
await this._highlightElements(element);
|
|
689
|
-
await element.setChecked(checked
|
|
971
|
+
// await this._highlightElements(element);
|
|
972
|
+
await state.element.setChecked(checked);
|
|
690
973
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
691
974
|
}
|
|
692
975
|
catch (e) {
|
|
@@ -695,179 +978,108 @@ class StableBrowser {
|
|
|
695
978
|
}
|
|
696
979
|
else {
|
|
697
980
|
//await this.closeUnexpectedPopups();
|
|
698
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
699
|
-
element = await this._locate(selectors, info, _params);
|
|
700
|
-
await element.setChecked(checked, { timeout: 5000, force: true });
|
|
981
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
982
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
983
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
701
984
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
702
985
|
}
|
|
703
986
|
}
|
|
704
987
|
await this.waitForPageLoad();
|
|
705
|
-
return info;
|
|
988
|
+
return state.info;
|
|
706
989
|
}
|
|
707
990
|
catch (e) {
|
|
708
|
-
|
|
709
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
710
|
-
info.screenshotPath = screenshotPath;
|
|
711
|
-
Object.assign(e, { info: info });
|
|
712
|
-
error = e;
|
|
713
|
-
throw e;
|
|
991
|
+
await _commandError(state, e, this);
|
|
714
992
|
}
|
|
715
993
|
finally {
|
|
716
|
-
|
|
717
|
-
this._reportToWorld(world, {
|
|
718
|
-
element_name: selectors.element_name,
|
|
719
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
720
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
721
|
-
screenshotId,
|
|
722
|
-
result: error
|
|
723
|
-
? {
|
|
724
|
-
status: "FAILED",
|
|
725
|
-
startTime,
|
|
726
|
-
endTime,
|
|
727
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
728
|
-
}
|
|
729
|
-
: {
|
|
730
|
-
status: "PASSED",
|
|
731
|
-
startTime,
|
|
732
|
-
endTime,
|
|
733
|
-
},
|
|
734
|
-
info: info,
|
|
735
|
-
});
|
|
994
|
+
_commandFinally(state, this);
|
|
736
995
|
}
|
|
737
996
|
}
|
|
738
997
|
async hover(selectors, _params, options = {}, world = null) {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
998
|
+
const state = {
|
|
999
|
+
selectors,
|
|
1000
|
+
_params,
|
|
1001
|
+
options,
|
|
1002
|
+
world,
|
|
1003
|
+
type: Types.HOVER,
|
|
1004
|
+
text: `Hover element`,
|
|
1005
|
+
operation: "hover",
|
|
1006
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
1007
|
+
};
|
|
748
1008
|
try {
|
|
749
|
-
|
|
750
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1009
|
+
await _preCommand(state, this);
|
|
751
1010
|
try {
|
|
752
|
-
await
|
|
753
|
-
await element.hover({ timeout: 10000 });
|
|
1011
|
+
await state.element.hover();
|
|
754
1012
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
755
1013
|
}
|
|
756
1014
|
catch (e) {
|
|
757
1015
|
//await this.closeUnexpectedPopups();
|
|
758
|
-
info.log += "hover failed, will try again" + "\n";
|
|
759
|
-
element = await this._locate(selectors, info, _params);
|
|
760
|
-
await element.hover({ timeout: 10000 });
|
|
1016
|
+
state.info.log += "hover failed, will try again" + "\n";
|
|
1017
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
1018
|
+
await state.element.hover({ timeout: 10000 });
|
|
761
1019
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
762
1020
|
}
|
|
763
1021
|
await this.waitForPageLoad();
|
|
764
|
-
return info;
|
|
1022
|
+
return state.info;
|
|
765
1023
|
}
|
|
766
1024
|
catch (e) {
|
|
767
|
-
|
|
768
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
769
|
-
info.screenshotPath = screenshotPath;
|
|
770
|
-
Object.assign(e, { info: info });
|
|
771
|
-
error = e;
|
|
772
|
-
throw e;
|
|
1025
|
+
await _commandError(state, e, this);
|
|
773
1026
|
}
|
|
774
1027
|
finally {
|
|
775
|
-
|
|
776
|
-
this._reportToWorld(world, {
|
|
777
|
-
element_name: selectors.element_name,
|
|
778
|
-
type: Types.HOVER,
|
|
779
|
-
text: `Hover element`,
|
|
780
|
-
screenshotId,
|
|
781
|
-
result: error
|
|
782
|
-
? {
|
|
783
|
-
status: "FAILED",
|
|
784
|
-
startTime,
|
|
785
|
-
endTime,
|
|
786
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
787
|
-
}
|
|
788
|
-
: {
|
|
789
|
-
status: "PASSED",
|
|
790
|
-
startTime,
|
|
791
|
-
endTime,
|
|
792
|
-
},
|
|
793
|
-
info: info,
|
|
794
|
-
});
|
|
1028
|
+
_commandFinally(state, this);
|
|
795
1029
|
}
|
|
796
1030
|
}
|
|
797
1031
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
798
|
-
this._validateSelectors(selectors);
|
|
799
1032
|
if (!values) {
|
|
800
1033
|
throw new Error("values is null");
|
|
801
1034
|
}
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1035
|
+
const state = {
|
|
1036
|
+
selectors,
|
|
1037
|
+
_params,
|
|
1038
|
+
options,
|
|
1039
|
+
world,
|
|
1040
|
+
value: values.toString(),
|
|
1041
|
+
type: Types.SELECT,
|
|
1042
|
+
text: `Select option: ${values}`,
|
|
1043
|
+
operation: "selectOption",
|
|
1044
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1045
|
+
};
|
|
810
1046
|
try {
|
|
811
|
-
|
|
812
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1047
|
+
await _preCommand(state, this);
|
|
813
1048
|
try {
|
|
814
|
-
await
|
|
815
|
-
await element.selectOption(values, { timeout: 5000 });
|
|
1049
|
+
await state.element.selectOption(values);
|
|
816
1050
|
}
|
|
817
1051
|
catch (e) {
|
|
818
1052
|
//await this.closeUnexpectedPopups();
|
|
819
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
820
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1053
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1054
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
821
1055
|
}
|
|
822
1056
|
await this.waitForPageLoad();
|
|
823
|
-
return info;
|
|
1057
|
+
return state.info;
|
|
824
1058
|
}
|
|
825
1059
|
catch (e) {
|
|
826
|
-
|
|
827
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
828
|
-
info.screenshotPath = screenshotPath;
|
|
829
|
-
Object.assign(e, { info: info });
|
|
830
|
-
this.logger.info("click failed, will try next selector");
|
|
831
|
-
error = e;
|
|
832
|
-
throw e;
|
|
1060
|
+
await _commandError(state, e, this);
|
|
833
1061
|
}
|
|
834
1062
|
finally {
|
|
835
|
-
|
|
836
|
-
this._reportToWorld(world, {
|
|
837
|
-
element_name: selectors.element_name,
|
|
838
|
-
type: Types.SELECT,
|
|
839
|
-
text: `Select option: ${values}`,
|
|
840
|
-
value: values.toString(),
|
|
841
|
-
screenshotId,
|
|
842
|
-
result: error
|
|
843
|
-
? {
|
|
844
|
-
status: "FAILED",
|
|
845
|
-
startTime,
|
|
846
|
-
endTime,
|
|
847
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
848
|
-
}
|
|
849
|
-
: {
|
|
850
|
-
status: "PASSED",
|
|
851
|
-
startTime,
|
|
852
|
-
endTime,
|
|
853
|
-
},
|
|
854
|
-
info: info,
|
|
855
|
-
});
|
|
1063
|
+
_commandFinally(state, this);
|
|
856
1064
|
}
|
|
857
1065
|
}
|
|
858
1066
|
async type(_value, _params = null, options = {}, world = null) {
|
|
859
|
-
const
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
1067
|
+
const state = {
|
|
1068
|
+
value: _value,
|
|
1069
|
+
_params,
|
|
1070
|
+
options,
|
|
1071
|
+
world,
|
|
1072
|
+
locate: false,
|
|
1073
|
+
scroll: false,
|
|
1074
|
+
highlight: false,
|
|
1075
|
+
type: Types.TYPE_PRESS,
|
|
1076
|
+
text: `Type value: ${_value}`,
|
|
1077
|
+
operation: "type",
|
|
1078
|
+
log: "",
|
|
1079
|
+
};
|
|
868
1080
|
try {
|
|
869
|
-
|
|
870
|
-
const valueSegment =
|
|
1081
|
+
await _preCommand(state, this);
|
|
1082
|
+
const valueSegment = state.value.split("&&");
|
|
871
1083
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
872
1084
|
if (i > 0) {
|
|
873
1085
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -887,68 +1099,76 @@ class StableBrowser {
|
|
|
887
1099
|
await this.page.keyboard.type(value);
|
|
888
1100
|
}
|
|
889
1101
|
}
|
|
890
|
-
return info;
|
|
1102
|
+
return state.info;
|
|
891
1103
|
}
|
|
892
1104
|
catch (e) {
|
|
893
|
-
|
|
894
|
-
this.logger.error("type failed " + info.log);
|
|
895
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
896
|
-
info.screenshotPath = screenshotPath;
|
|
897
|
-
Object.assign(e, { info: info });
|
|
898
|
-
error = e;
|
|
899
|
-
throw e;
|
|
1105
|
+
await _commandError(state, e, this);
|
|
900
1106
|
}
|
|
901
1107
|
finally {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1108
|
+
_commandFinally(state, this);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1112
|
+
const state = {
|
|
1113
|
+
selectors,
|
|
1114
|
+
_params,
|
|
1115
|
+
value,
|
|
1116
|
+
options,
|
|
1117
|
+
world,
|
|
1118
|
+
type: Types.SET_INPUT,
|
|
1119
|
+
text: `Set input value`,
|
|
1120
|
+
operation: "setInputValue",
|
|
1121
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1122
|
+
};
|
|
1123
|
+
try {
|
|
1124
|
+
await _preCommand(state, this);
|
|
1125
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1126
|
+
try {
|
|
1127
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1128
|
+
el.value = value;
|
|
1129
|
+
}, value);
|
|
1130
|
+
}
|
|
1131
|
+
catch (error) {
|
|
1132
|
+
this.logger.error("setInputValue failed, will try again");
|
|
1133
|
+
await _screenshot(state, this);
|
|
1134
|
+
Object.assign(error, { info: state.info });
|
|
1135
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1136
|
+
el.value = value;
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
catch (e) {
|
|
1141
|
+
await _commandError(state, e, this);
|
|
1142
|
+
}
|
|
1143
|
+
finally {
|
|
1144
|
+
_commandFinally(state, this);
|
|
922
1145
|
}
|
|
923
1146
|
}
|
|
924
1147
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1148
|
+
const state = {
|
|
1149
|
+
selectors,
|
|
1150
|
+
_params,
|
|
1151
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1152
|
+
options,
|
|
1153
|
+
world,
|
|
1154
|
+
type: Types.SET_DATE_TIME,
|
|
1155
|
+
text: `Set date time value: ${value}`,
|
|
1156
|
+
operation: "setDateTime",
|
|
1157
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1158
|
+
throwError: false,
|
|
1159
|
+
};
|
|
935
1160
|
try {
|
|
936
|
-
|
|
937
|
-
let element = await this._locate(selectors, info, _params);
|
|
938
|
-
//insert red border around the element
|
|
939
|
-
await this.scrollIfNeeded(element, info);
|
|
940
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
941
|
-
await this._highlightElements(element);
|
|
1161
|
+
await _preCommand(state, this);
|
|
942
1162
|
try {
|
|
943
|
-
await element.click();
|
|
1163
|
+
await state.element.click();
|
|
944
1164
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
945
1165
|
if (format) {
|
|
946
|
-
value = dayjs(value).format(format);
|
|
947
|
-
await element.fill(value);
|
|
1166
|
+
state.value = dayjs(state.value).format(format);
|
|
1167
|
+
await state.element.fill(state.value);
|
|
948
1168
|
}
|
|
949
1169
|
else {
|
|
950
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
951
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1170
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1171
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
952
1172
|
el.value = ""; // clear input
|
|
953
1173
|
el.value = dateTimeValue;
|
|
954
1174
|
}, dateTimeValue);
|
|
@@ -959,21 +1179,21 @@ class StableBrowser {
|
|
|
959
1179
|
await this.waitForPageLoad();
|
|
960
1180
|
}
|
|
961
1181
|
}
|
|
962
|
-
catch (
|
|
1182
|
+
catch (err) {
|
|
963
1183
|
//await this.closeUnexpectedPopups();
|
|
964
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
965
|
-
this.logger.info("Trying again")
|
|
966
|
-
|
|
967
|
-
Object.assign(
|
|
1184
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1185
|
+
this.logger.info("Trying again");
|
|
1186
|
+
await _screenshot(state, this);
|
|
1187
|
+
Object.assign(err, { info: state.info });
|
|
968
1188
|
await element.click();
|
|
969
1189
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
970
1190
|
if (format) {
|
|
971
|
-
value = dayjs(value).format(format);
|
|
972
|
-
await element.fill(value);
|
|
1191
|
+
state.value = dayjs(state.value).format(format);
|
|
1192
|
+
await state.element.fill(state.value);
|
|
973
1193
|
}
|
|
974
1194
|
else {
|
|
975
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
976
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1195
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1196
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
977
1197
|
el.value = ""; // clear input
|
|
978
1198
|
el.value = dateTimeValue;
|
|
979
1199
|
}, dateTimeValue);
|
|
@@ -985,130 +1205,40 @@ class StableBrowser {
|
|
|
985
1205
|
}
|
|
986
1206
|
}
|
|
987
1207
|
}
|
|
988
|
-
catch (
|
|
989
|
-
|
|
990
|
-
throw e;
|
|
991
|
-
}
|
|
992
|
-
finally {
|
|
993
|
-
const endTime = Date.now();
|
|
994
|
-
this._reportToWorld(world, {
|
|
995
|
-
element_name: selectors.element_name,
|
|
996
|
-
type: Types.SET_DATE_TIME,
|
|
997
|
-
screenshotId,
|
|
998
|
-
value: value,
|
|
999
|
-
text: `setDateTime input with value: ${value}`,
|
|
1000
|
-
result: error
|
|
1001
|
-
? {
|
|
1002
|
-
status: "FAILED",
|
|
1003
|
-
startTime,
|
|
1004
|
-
endTime,
|
|
1005
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1006
|
-
}
|
|
1007
|
-
: {
|
|
1008
|
-
status: "PASSED",
|
|
1009
|
-
startTime,
|
|
1010
|
-
endTime,
|
|
1011
|
-
},
|
|
1012
|
-
info: info,
|
|
1013
|
-
});
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
async setDateTime(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1017
|
-
this._validateSelectors(selectors);
|
|
1018
|
-
const startTime = Date.now();
|
|
1019
|
-
let error = null;
|
|
1020
|
-
let screenshotId = null;
|
|
1021
|
-
let screenshotPath = null;
|
|
1022
|
-
const info = {};
|
|
1023
|
-
info.log = "";
|
|
1024
|
-
info.operation = Types.SET_DATE_TIME;
|
|
1025
|
-
info.selectors = selectors;
|
|
1026
|
-
info.value = value;
|
|
1027
|
-
try {
|
|
1028
|
-
let element = await this._locate(selectors, info, _params);
|
|
1029
|
-
//insert red border around the element
|
|
1030
|
-
await this.scrollIfNeeded(element, info);
|
|
1031
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1032
|
-
await this._highlightElements(element);
|
|
1033
|
-
try {
|
|
1034
|
-
await element.click();
|
|
1035
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1036
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1037
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1038
|
-
el.value = ""; // clear input
|
|
1039
|
-
el.value = dateTimeValue;
|
|
1040
|
-
}, dateTimeValue);
|
|
1041
|
-
}
|
|
1042
|
-
catch (error) {
|
|
1043
|
-
//await this.closeUnexpectedPopups();
|
|
1044
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1045
|
-
this.logger.info("Trying again")(({ screenshotId, screenshotPath } = await this._screenShot(options, world, info)));
|
|
1046
|
-
info.screenshotPath = screenshotPath;
|
|
1047
|
-
Object.assign(error, { info: info });
|
|
1048
|
-
await element.click();
|
|
1049
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1050
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1051
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1052
|
-
el.value = ""; // clear input
|
|
1053
|
-
el.value = dateTimeValue;
|
|
1054
|
-
}, dateTimeValue);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
catch (error) {
|
|
1058
|
-
error = e;
|
|
1059
|
-
throw e;
|
|
1208
|
+
catch (e) {
|
|
1209
|
+
await _commandError(state, e, this);
|
|
1060
1210
|
}
|
|
1061
1211
|
finally {
|
|
1062
|
-
|
|
1063
|
-
this._reportToWorld(world, {
|
|
1064
|
-
element_name: selectors.element_name,
|
|
1065
|
-
type: Types.SET_DATE_TIME,
|
|
1066
|
-
screenshotId,
|
|
1067
|
-
value: value,
|
|
1068
|
-
text: `setDateTime input with value: ${value}`,
|
|
1069
|
-
result: error
|
|
1070
|
-
? {
|
|
1071
|
-
status: "FAILED",
|
|
1072
|
-
startTime,
|
|
1073
|
-
endTime,
|
|
1074
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1075
|
-
}
|
|
1076
|
-
: {
|
|
1077
|
-
status: "PASSED",
|
|
1078
|
-
startTime,
|
|
1079
|
-
endTime,
|
|
1080
|
-
},
|
|
1081
|
-
info: info,
|
|
1082
|
-
});
|
|
1212
|
+
_commandFinally(state, this);
|
|
1083
1213
|
}
|
|
1084
1214
|
}
|
|
1085
1215
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1086
|
-
|
|
1087
|
-
const startTime = Date.now();
|
|
1088
|
-
let error = null;
|
|
1089
|
-
let screenshotId = null;
|
|
1090
|
-
let screenshotPath = null;
|
|
1091
|
-
const info = {};
|
|
1092
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1093
|
-
info.operation = "clickType";
|
|
1094
|
-
info.selectors = selectors;
|
|
1216
|
+
_value = unEscapeString(_value);
|
|
1095
1217
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1218
|
+
const state = {
|
|
1219
|
+
selectors,
|
|
1220
|
+
_params,
|
|
1221
|
+
value: newValue,
|
|
1222
|
+
originalValue: _value,
|
|
1223
|
+
options,
|
|
1224
|
+
world,
|
|
1225
|
+
type: Types.FILL,
|
|
1226
|
+
text: `Click type input with value: ${_value}`,
|
|
1227
|
+
operation: "clickType",
|
|
1228
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1229
|
+
};
|
|
1096
1230
|
if (newValue !== _value) {
|
|
1097
1231
|
//this.logger.info(_value + "=" + newValue);
|
|
1098
1232
|
_value = newValue;
|
|
1099
1233
|
}
|
|
1100
|
-
info.value = _value;
|
|
1101
1234
|
try {
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
await this.scrollIfNeeded(element, info);
|
|
1105
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1106
|
-
await this._highlightElements(element);
|
|
1235
|
+
await _preCommand(state, this);
|
|
1236
|
+
state.info.value = _value;
|
|
1107
1237
|
if (options === null || options === undefined || !options.press) {
|
|
1108
1238
|
try {
|
|
1109
|
-
let currentValue = await element.inputValue();
|
|
1239
|
+
let currentValue = await state.element.inputValue();
|
|
1110
1240
|
if (currentValue) {
|
|
1111
|
-
await element.fill("");
|
|
1241
|
+
await state.element.fill("");
|
|
1112
1242
|
}
|
|
1113
1243
|
}
|
|
1114
1244
|
catch (e) {
|
|
@@ -1117,22 +1247,22 @@ class StableBrowser {
|
|
|
1117
1247
|
}
|
|
1118
1248
|
if (options === null || options === undefined || options.press) {
|
|
1119
1249
|
try {
|
|
1120
|
-
await element.click({ timeout: 5000 });
|
|
1250
|
+
await state.element.click({ timeout: 5000 });
|
|
1121
1251
|
}
|
|
1122
1252
|
catch (e) {
|
|
1123
|
-
await element.dispatchEvent("click");
|
|
1253
|
+
await state.element.dispatchEvent("click");
|
|
1124
1254
|
}
|
|
1125
1255
|
}
|
|
1126
1256
|
else {
|
|
1127
1257
|
try {
|
|
1128
|
-
await element.focus();
|
|
1258
|
+
await state.element.focus();
|
|
1129
1259
|
}
|
|
1130
1260
|
catch (e) {
|
|
1131
|
-
await element.dispatchEvent("focus");
|
|
1261
|
+
await state.element.dispatchEvent("focus");
|
|
1132
1262
|
}
|
|
1133
1263
|
}
|
|
1134
1264
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1135
|
-
const valueSegment =
|
|
1265
|
+
const valueSegment = state.value.split("&&");
|
|
1136
1266
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1137
1267
|
if (i > 0) {
|
|
1138
1268
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1152,13 +1282,14 @@ class StableBrowser {
|
|
|
1152
1282
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1153
1283
|
}
|
|
1154
1284
|
}
|
|
1285
|
+
await _screenshot(state, this);
|
|
1155
1286
|
if (enter === true) {
|
|
1156
1287
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1157
1288
|
await this.page.keyboard.press("Enter");
|
|
1158
1289
|
await this.waitForPageLoad();
|
|
1159
1290
|
}
|
|
1160
1291
|
else if (enter === false) {
|
|
1161
|
-
await element.dispatchEvent("change");
|
|
1292
|
+
await state.element.dispatchEvent("change");
|
|
1162
1293
|
//await this.page.keyboard.press("Tab");
|
|
1163
1294
|
}
|
|
1164
1295
|
else {
|
|
@@ -1167,103 +1298,50 @@ class StableBrowser {
|
|
|
1167
1298
|
await this.waitForPageLoad();
|
|
1168
1299
|
}
|
|
1169
1300
|
}
|
|
1170
|
-
return info;
|
|
1301
|
+
return state.info;
|
|
1171
1302
|
}
|
|
1172
1303
|
catch (e) {
|
|
1173
|
-
|
|
1174
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1175
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1176
|
-
info.screenshotPath = screenshotPath;
|
|
1177
|
-
Object.assign(e, { info: info });
|
|
1178
|
-
error = e;
|
|
1179
|
-
throw e;
|
|
1304
|
+
await _commandError(state, e, this);
|
|
1180
1305
|
}
|
|
1181
1306
|
finally {
|
|
1182
|
-
|
|
1183
|
-
this._reportToWorld(world, {
|
|
1184
|
-
element_name: selectors.element_name,
|
|
1185
|
-
type: Types.FILL,
|
|
1186
|
-
screenshotId,
|
|
1187
|
-
value: _value,
|
|
1188
|
-
text: `clickType input with value: ${_value}`,
|
|
1189
|
-
result: error
|
|
1190
|
-
? {
|
|
1191
|
-
status: "FAILED",
|
|
1192
|
-
startTime,
|
|
1193
|
-
endTime,
|
|
1194
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1195
|
-
}
|
|
1196
|
-
: {
|
|
1197
|
-
status: "PASSED",
|
|
1198
|
-
startTime,
|
|
1199
|
-
endTime,
|
|
1200
|
-
},
|
|
1201
|
-
info: info,
|
|
1202
|
-
});
|
|
1307
|
+
_commandFinally(state, this);
|
|
1203
1308
|
}
|
|
1204
1309
|
}
|
|
1205
1310
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1311
|
+
const state = {
|
|
1312
|
+
selectors,
|
|
1313
|
+
_params,
|
|
1314
|
+
value: unEscapeString(value),
|
|
1315
|
+
options,
|
|
1316
|
+
world,
|
|
1317
|
+
type: Types.FILL,
|
|
1318
|
+
text: `Fill input with value: ${value}`,
|
|
1319
|
+
operation: "fill",
|
|
1320
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1321
|
+
};
|
|
1216
1322
|
try {
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
await
|
|
1220
|
-
await element.fill(value, { timeout: 10000 });
|
|
1221
|
-
await element.dispatchEvent("change");
|
|
1323
|
+
await _preCommand(state, this);
|
|
1324
|
+
await state.element.fill(value);
|
|
1325
|
+
await state.element.dispatchEvent("change");
|
|
1222
1326
|
if (enter) {
|
|
1223
1327
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1224
1328
|
await this.page.keyboard.press("Enter");
|
|
1225
1329
|
}
|
|
1226
1330
|
await this.waitForPageLoad();
|
|
1227
|
-
return info;
|
|
1331
|
+
return state.info;
|
|
1228
1332
|
}
|
|
1229
1333
|
catch (e) {
|
|
1230
|
-
|
|
1231
|
-
this.logger.error("fill failed " + info.log);
|
|
1232
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1233
|
-
info.screenshotPath = screenshotPath;
|
|
1234
|
-
Object.assign(e, { info: info });
|
|
1235
|
-
error = e;
|
|
1236
|
-
throw e;
|
|
1334
|
+
await _commandError(state, e, this);
|
|
1237
1335
|
}
|
|
1238
1336
|
finally {
|
|
1239
|
-
|
|
1240
|
-
this._reportToWorld(world, {
|
|
1241
|
-
element_name: selectors.element_name,
|
|
1242
|
-
type: Types.FILL,
|
|
1243
|
-
screenshotId,
|
|
1244
|
-
value,
|
|
1245
|
-
text: `Fill input with value: ${value}`,
|
|
1246
|
-
result: error
|
|
1247
|
-
? {
|
|
1248
|
-
status: "FAILED",
|
|
1249
|
-
startTime,
|
|
1250
|
-
endTime,
|
|
1251
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1252
|
-
}
|
|
1253
|
-
: {
|
|
1254
|
-
status: "PASSED",
|
|
1255
|
-
startTime,
|
|
1256
|
-
endTime,
|
|
1257
|
-
},
|
|
1258
|
-
info: info,
|
|
1259
|
-
});
|
|
1337
|
+
_commandFinally(state, this);
|
|
1260
1338
|
}
|
|
1261
1339
|
}
|
|
1262
1340
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1263
1341
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1264
1342
|
}
|
|
1265
1343
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1266
|
-
|
|
1344
|
+
_validateSelectors(selectors);
|
|
1267
1345
|
let screenshotId = null;
|
|
1268
1346
|
let screenshotPath = null;
|
|
1269
1347
|
if (!info.log) {
|
|
@@ -1307,165 +1385,124 @@ class StableBrowser {
|
|
|
1307
1385
|
}
|
|
1308
1386
|
}
|
|
1309
1387
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1310
|
-
var _a;
|
|
1311
|
-
this._validateSelectors(selectors);
|
|
1312
1388
|
if (!pattern) {
|
|
1313
1389
|
throw new Error("pattern is null");
|
|
1314
1390
|
}
|
|
1315
1391
|
if (!text) {
|
|
1316
1392
|
throw new Error("text is null");
|
|
1317
1393
|
}
|
|
1394
|
+
const state = {
|
|
1395
|
+
selectors,
|
|
1396
|
+
_params,
|
|
1397
|
+
pattern,
|
|
1398
|
+
value: pattern,
|
|
1399
|
+
options,
|
|
1400
|
+
world,
|
|
1401
|
+
locate: false,
|
|
1402
|
+
scroll: false,
|
|
1403
|
+
screenshot: false,
|
|
1404
|
+
highlight: false,
|
|
1405
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1406
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1407
|
+
operation: "containsPattern",
|
|
1408
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1409
|
+
};
|
|
1318
1410
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1319
1411
|
if (newValue !== text) {
|
|
1320
1412
|
this.logger.info(text + "=" + newValue);
|
|
1321
1413
|
text = newValue;
|
|
1322
1414
|
}
|
|
1323
|
-
const startTime = Date.now();
|
|
1324
|
-
let error = null;
|
|
1325
|
-
let screenshotId = null;
|
|
1326
|
-
let screenshotPath = null;
|
|
1327
|
-
const info = {};
|
|
1328
|
-
info.log =
|
|
1329
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1330
|
-
info.operation = "containsPattern";
|
|
1331
|
-
info.selectors = selectors;
|
|
1332
|
-
info.value = text;
|
|
1333
|
-
info.pattern = pattern;
|
|
1334
1415
|
let foundObj = null;
|
|
1335
1416
|
try {
|
|
1336
|
-
|
|
1417
|
+
await _preCommand(state, this);
|
|
1418
|
+
state.info.pattern = pattern;
|
|
1419
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1337
1420
|
if (foundObj && foundObj.element) {
|
|
1338
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1421
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1339
1422
|
}
|
|
1340
|
-
|
|
1423
|
+
await _screenshot(state, this);
|
|
1341
1424
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1342
1425
|
pattern = pattern.replace("{text}", escapedText);
|
|
1343
1426
|
let regex = new RegExp(pattern, "im");
|
|
1344
|
-
if (!regex.test(foundObj
|
|
1345
|
-
info.foundText = foundObj
|
|
1427
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1428
|
+
state.info.foundText = foundObj?.text;
|
|
1346
1429
|
throw new Error("element doesn't contain text " + text);
|
|
1347
|
-
}
|
|
1348
|
-
return info;
|
|
1349
|
-
}
|
|
1350
|
-
catch (e) {
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1354
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1355
|
-
info.screenshotPath = screenshotPath;
|
|
1356
|
-
Object.assign(e, { info: info });
|
|
1357
|
-
error = e;
|
|
1358
|
-
throw e;
|
|
1430
|
+
}
|
|
1431
|
+
return state.info;
|
|
1432
|
+
}
|
|
1433
|
+
catch (e) {
|
|
1434
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1435
|
+
await _commandError(state, e, this);
|
|
1359
1436
|
}
|
|
1360
1437
|
finally {
|
|
1361
|
-
|
|
1362
|
-
this._reportToWorld(world, {
|
|
1363
|
-
element_name: selectors.element_name,
|
|
1364
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1365
|
-
value: pattern,
|
|
1366
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1367
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1368
|
-
result: error
|
|
1369
|
-
? {
|
|
1370
|
-
status: "FAILED",
|
|
1371
|
-
startTime,
|
|
1372
|
-
endTime,
|
|
1373
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1374
|
-
}
|
|
1375
|
-
: {
|
|
1376
|
-
status: "PASSED",
|
|
1377
|
-
startTime,
|
|
1378
|
-
endTime,
|
|
1379
|
-
},
|
|
1380
|
-
info: info,
|
|
1381
|
-
});
|
|
1438
|
+
_commandFinally(state, this);
|
|
1382
1439
|
}
|
|
1383
1440
|
}
|
|
1384
1441
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1385
|
-
|
|
1386
|
-
|
|
1442
|
+
const state = {
|
|
1443
|
+
selectors,
|
|
1444
|
+
_params,
|
|
1445
|
+
value: text,
|
|
1446
|
+
options,
|
|
1447
|
+
world,
|
|
1448
|
+
locate: false,
|
|
1449
|
+
scroll: false,
|
|
1450
|
+
screenshot: false,
|
|
1451
|
+
highlight: false,
|
|
1452
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1453
|
+
text: `Verify element contains text: ${text}`,
|
|
1454
|
+
operation: "containsText",
|
|
1455
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1456
|
+
};
|
|
1387
1457
|
if (!text) {
|
|
1388
1458
|
throw new Error("text is null");
|
|
1389
1459
|
}
|
|
1390
|
-
|
|
1391
|
-
let error = null;
|
|
1392
|
-
let screenshotId = null;
|
|
1393
|
-
let screenshotPath = null;
|
|
1394
|
-
const info = {};
|
|
1395
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1396
|
-
info.operation = "containsText";
|
|
1397
|
-
info.selectors = selectors;
|
|
1460
|
+
text = unEscapeString(text);
|
|
1398
1461
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1399
1462
|
if (newValue !== text) {
|
|
1400
1463
|
this.logger.info(text + "=" + newValue);
|
|
1401
1464
|
text = newValue;
|
|
1402
1465
|
}
|
|
1403
|
-
info.value = text;
|
|
1404
1466
|
let foundObj = null;
|
|
1405
1467
|
try {
|
|
1406
|
-
|
|
1468
|
+
await _preCommand(state, this);
|
|
1469
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1407
1470
|
if (foundObj && foundObj.element) {
|
|
1408
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1471
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1409
1472
|
}
|
|
1410
|
-
|
|
1473
|
+
await _screenshot(state, this);
|
|
1411
1474
|
const dateAlternatives = findDateAlternatives(text);
|
|
1412
1475
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1413
1476
|
if (dateAlternatives.date) {
|
|
1414
1477
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1415
|
-
if (
|
|
1416
|
-
|
|
1417
|
-
return info;
|
|
1478
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1479
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1480
|
+
return state.info;
|
|
1418
1481
|
}
|
|
1419
1482
|
}
|
|
1420
1483
|
throw new Error("element doesn't contain text " + text);
|
|
1421
1484
|
}
|
|
1422
1485
|
else if (numberAlternatives.number) {
|
|
1423
1486
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1424
|
-
if (
|
|
1425
|
-
|
|
1426
|
-
return info;
|
|
1487
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1488
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1489
|
+
return state.info;
|
|
1427
1490
|
}
|
|
1428
1491
|
}
|
|
1429
1492
|
throw new Error("element doesn't contain text " + text);
|
|
1430
1493
|
}
|
|
1431
|
-
else if (!
|
|
1432
|
-
info.foundText = foundObj
|
|
1433
|
-
info.value = foundObj
|
|
1494
|
+
else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
|
|
1495
|
+
state.info.foundText = foundObj?.text;
|
|
1496
|
+
state.info.value = foundObj?.value;
|
|
1434
1497
|
throw new Error("element doesn't contain text " + text);
|
|
1435
1498
|
}
|
|
1436
|
-
return info;
|
|
1499
|
+
return state.info;
|
|
1437
1500
|
}
|
|
1438
1501
|
catch (e) {
|
|
1439
|
-
|
|
1440
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1441
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1442
|
-
info.screenshotPath = screenshotPath;
|
|
1443
|
-
Object.assign(e, { info: info });
|
|
1444
|
-
error = e;
|
|
1445
|
-
throw e;
|
|
1502
|
+
await _commandError(state, e, this);
|
|
1446
1503
|
}
|
|
1447
1504
|
finally {
|
|
1448
|
-
|
|
1449
|
-
this._reportToWorld(world, {
|
|
1450
|
-
element_name: selectors.element_name,
|
|
1451
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1452
|
-
text: `Verify element contains text: ${text}`,
|
|
1453
|
-
value: text,
|
|
1454
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1455
|
-
result: error
|
|
1456
|
-
? {
|
|
1457
|
-
status: "FAILED",
|
|
1458
|
-
startTime,
|
|
1459
|
-
endTime,
|
|
1460
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1461
|
-
}
|
|
1462
|
-
: {
|
|
1463
|
-
status: "PASSED",
|
|
1464
|
-
startTime,
|
|
1465
|
-
endTime,
|
|
1466
|
-
},
|
|
1467
|
-
info: info,
|
|
1468
|
-
});
|
|
1505
|
+
_commandFinally(state, this);
|
|
1469
1506
|
}
|
|
1470
1507
|
}
|
|
1471
1508
|
_getDataFile(world = null) {
|
|
@@ -1484,6 +1521,29 @@ class StableBrowser {
|
|
|
1484
1521
|
}
|
|
1485
1522
|
return dataFile;
|
|
1486
1523
|
}
|
|
1524
|
+
async waitForUserInput(message, world = null) {
|
|
1525
|
+
if (!message) {
|
|
1526
|
+
message = "# Wait for user input. Press any key to continue";
|
|
1527
|
+
}
|
|
1528
|
+
else {
|
|
1529
|
+
message = "# Wait for user input. " + message;
|
|
1530
|
+
}
|
|
1531
|
+
message += "\n";
|
|
1532
|
+
const value = await new Promise((resolve) => {
|
|
1533
|
+
const rl = readline.createInterface({
|
|
1534
|
+
input: process.stdin,
|
|
1535
|
+
output: process.stdout,
|
|
1536
|
+
});
|
|
1537
|
+
rl.question(message, (answer) => {
|
|
1538
|
+
rl.close();
|
|
1539
|
+
resolve(answer);
|
|
1540
|
+
});
|
|
1541
|
+
});
|
|
1542
|
+
if (value) {
|
|
1543
|
+
this.logger.info(`{{userInput}} was set to: ${value}`);
|
|
1544
|
+
}
|
|
1545
|
+
this.setTestData({ userInput: value }, world);
|
|
1546
|
+
}
|
|
1487
1547
|
setTestData(testData, world = null) {
|
|
1488
1548
|
if (!testData) {
|
|
1489
1549
|
return;
|
|
@@ -1511,7 +1571,7 @@ class StableBrowser {
|
|
|
1511
1571
|
const data = fs.readFileSync(filePath, "utf8");
|
|
1512
1572
|
const results = [];
|
|
1513
1573
|
return new Promise((resolve, reject) => {
|
|
1514
|
-
const readableStream = new
|
|
1574
|
+
const readableStream = new Readable();
|
|
1515
1575
|
readableStream._read = () => { }; // _read is required but you can noop it
|
|
1516
1576
|
readableStream.push(data);
|
|
1517
1577
|
readableStream.push(null);
|
|
@@ -1671,7 +1731,6 @@ class StableBrowser {
|
|
|
1671
1731
|
}
|
|
1672
1732
|
async takeScreenshot(screenshotPath) {
|
|
1673
1733
|
const playContext = this.context.playContext;
|
|
1674
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1675
1734
|
// Using CDP to capture the screenshot
|
|
1676
1735
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1677
1736
|
document.body.scrollWidth,
|
|
@@ -1681,164 +1740,105 @@ class StableBrowser {
|
|
|
1681
1740
|
document.body.clientWidth,
|
|
1682
1741
|
document.documentElement.clientWidth,
|
|
1683
1742
|
])));
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
await client.detach();
|
|
1743
|
+
let screenshotBuffer = null;
|
|
1744
|
+
if (this.context.browserName === "chromium") {
|
|
1745
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1746
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1747
|
+
format: "png",
|
|
1748
|
+
// clip: {
|
|
1749
|
+
// x: 0,
|
|
1750
|
+
// y: 0,
|
|
1751
|
+
// width: viewportWidth,
|
|
1752
|
+
// height: viewportHeight,
|
|
1753
|
+
// scale: 1,
|
|
1754
|
+
// },
|
|
1755
|
+
});
|
|
1756
|
+
await client.detach();
|
|
1757
|
+
if (!screenshotPath) {
|
|
1758
|
+
return data;
|
|
1759
|
+
}
|
|
1760
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1761
|
+
}
|
|
1762
|
+
else {
|
|
1763
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1764
|
+
}
|
|
1765
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1766
|
+
// Get the image dimensions
|
|
1767
|
+
const { width, height } = image.bitmap;
|
|
1768
|
+
const resizeRatio = viewportWidth / width;
|
|
1769
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1770
|
+
if (width > viewportWidth) {
|
|
1771
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1772
|
+
await image.write(screenshotPath);
|
|
1773
|
+
}
|
|
1774
|
+
else {
|
|
1775
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1776
|
+
}
|
|
1719
1777
|
}
|
|
1720
1778
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1779
|
+
const state = {
|
|
1780
|
+
selectors,
|
|
1781
|
+
_params,
|
|
1782
|
+
options,
|
|
1783
|
+
world,
|
|
1784
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1785
|
+
text: `Verify element exists in page`,
|
|
1786
|
+
operation: "verifyElementExistInPage",
|
|
1787
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1788
|
+
};
|
|
1726
1789
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1727
|
-
const info = {};
|
|
1728
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1729
|
-
info.operation = "verify";
|
|
1730
|
-
info.selectors = selectors;
|
|
1731
1790
|
try {
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
}
|
|
1736
|
-
await this._highlightElements(element);
|
|
1737
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1738
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1739
|
-
return info;
|
|
1791
|
+
await _preCommand(state, this);
|
|
1792
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1793
|
+
return state.info;
|
|
1740
1794
|
}
|
|
1741
1795
|
catch (e) {
|
|
1742
|
-
|
|
1743
|
-
this.logger.error("verify failed " + info.log);
|
|
1744
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1745
|
-
info.screenshotPath = screenshotPath;
|
|
1746
|
-
Object.assign(e, { info: info });
|
|
1747
|
-
error = e;
|
|
1748
|
-
throw e;
|
|
1796
|
+
await _commandError(state, e, this);
|
|
1749
1797
|
}
|
|
1750
1798
|
finally {
|
|
1751
|
-
|
|
1752
|
-
this._reportToWorld(world, {
|
|
1753
|
-
element_name: selectors.element_name,
|
|
1754
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1755
|
-
text: "Verify element exists in page",
|
|
1756
|
-
screenshotId,
|
|
1757
|
-
result: error
|
|
1758
|
-
? {
|
|
1759
|
-
status: "FAILED",
|
|
1760
|
-
startTime,
|
|
1761
|
-
endTime,
|
|
1762
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1763
|
-
}
|
|
1764
|
-
: {
|
|
1765
|
-
status: "PASSED",
|
|
1766
|
-
startTime,
|
|
1767
|
-
endTime,
|
|
1768
|
-
},
|
|
1769
|
-
info: info,
|
|
1770
|
-
});
|
|
1799
|
+
_commandFinally(state, this);
|
|
1771
1800
|
}
|
|
1772
1801
|
}
|
|
1773
1802
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1803
|
+
const state = {
|
|
1804
|
+
selectors,
|
|
1805
|
+
_params,
|
|
1806
|
+
attribute,
|
|
1807
|
+
variable,
|
|
1808
|
+
options,
|
|
1809
|
+
world,
|
|
1810
|
+
type: Types.EXTRACT,
|
|
1811
|
+
text: `Extract attribute from element`,
|
|
1812
|
+
operation: "extractAttribute",
|
|
1813
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1814
|
+
};
|
|
1779
1815
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1780
|
-
const info = {};
|
|
1781
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1782
|
-
info.operation = "extract";
|
|
1783
|
-
info.selectors = selectors;
|
|
1784
1816
|
try {
|
|
1785
|
-
|
|
1786
|
-
await this._highlightElements(element);
|
|
1787
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1817
|
+
await _preCommand(state, this);
|
|
1788
1818
|
switch (attribute) {
|
|
1789
1819
|
case "inner_text":
|
|
1790
|
-
|
|
1820
|
+
state.value = await state.element.innerText();
|
|
1791
1821
|
break;
|
|
1792
1822
|
case "href":
|
|
1793
|
-
|
|
1823
|
+
state.value = await state.element.getAttribute("href");
|
|
1794
1824
|
break;
|
|
1795
1825
|
case "value":
|
|
1796
|
-
|
|
1826
|
+
state.value = await state.element.inputValue();
|
|
1797
1827
|
break;
|
|
1798
1828
|
default:
|
|
1799
|
-
|
|
1829
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1800
1830
|
break;
|
|
1801
1831
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
this.setTestData({ [variable]: info.value }, world);
|
|
1807
|
-
this.logger.info("set test data: " + variable + "=" + info.value);
|
|
1808
|
-
return info;
|
|
1832
|
+
state.info.value = state.value;
|
|
1833
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1834
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1835
|
+
return state.info;
|
|
1809
1836
|
}
|
|
1810
1837
|
catch (e) {
|
|
1811
|
-
|
|
1812
|
-
this.logger.error("extract failed " + info.log);
|
|
1813
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1814
|
-
info.screenshotPath = screenshotPath;
|
|
1815
|
-
Object.assign(e, { info: info });
|
|
1816
|
-
error = e;
|
|
1817
|
-
throw e;
|
|
1838
|
+
await _commandError(state, e, this);
|
|
1818
1839
|
}
|
|
1819
1840
|
finally {
|
|
1820
|
-
|
|
1821
|
-
this._reportToWorld(world, {
|
|
1822
|
-
element_name: selectors.element_name,
|
|
1823
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1824
|
-
variable: variable,
|
|
1825
|
-
value: info.value,
|
|
1826
|
-
text: "Extract attribute from element",
|
|
1827
|
-
screenshotId,
|
|
1828
|
-
result: error
|
|
1829
|
-
? {
|
|
1830
|
-
status: "FAILED",
|
|
1831
|
-
startTime,
|
|
1832
|
-
endTime,
|
|
1833
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1834
|
-
}
|
|
1835
|
-
: {
|
|
1836
|
-
status: "PASSED",
|
|
1837
|
-
startTime,
|
|
1838
|
-
endTime,
|
|
1839
|
-
},
|
|
1840
|
-
info: info,
|
|
1841
|
-
});
|
|
1841
|
+
_commandFinally(state, this);
|
|
1842
1842
|
}
|
|
1843
1843
|
}
|
|
1844
1844
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -1915,7 +1915,8 @@ class StableBrowser {
|
|
|
1915
1915
|
catch (e) {
|
|
1916
1916
|
errorCount++;
|
|
1917
1917
|
if (errorCount > 3) {
|
|
1918
|
-
throw e;
|
|
1918
|
+
// throw e;
|
|
1919
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
1919
1920
|
}
|
|
1920
1921
|
// ignore
|
|
1921
1922
|
}
|
|
@@ -2026,7 +2027,8 @@ class StableBrowser {
|
|
|
2026
2027
|
info.screenshotPath = screenshotPath;
|
|
2027
2028
|
Object.assign(e, { info: info });
|
|
2028
2029
|
error = e;
|
|
2029
|
-
throw e;
|
|
2030
|
+
// throw e;
|
|
2031
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2030
2032
|
}
|
|
2031
2033
|
finally {
|
|
2032
2034
|
const endTime = Date.now();
|
|
@@ -2039,7 +2041,7 @@ class StableBrowser {
|
|
|
2039
2041
|
status: "FAILED",
|
|
2040
2042
|
startTime,
|
|
2041
2043
|
endTime,
|
|
2042
|
-
message: error
|
|
2044
|
+
message: error?.message,
|
|
2043
2045
|
}
|
|
2044
2046
|
: {
|
|
2045
2047
|
status: "PASSED",
|
|
@@ -2051,52 +2053,59 @@ class StableBrowser {
|
|
|
2051
2053
|
}
|
|
2052
2054
|
}
|
|
2053
2055
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2054
|
-
|
|
2056
|
+
text = unEscapeString(text);
|
|
2057
|
+
const state = {
|
|
2058
|
+
text_search: text,
|
|
2059
|
+
options,
|
|
2060
|
+
world,
|
|
2061
|
+
locate: false,
|
|
2062
|
+
scroll: false,
|
|
2063
|
+
highlight: false,
|
|
2064
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2065
|
+
text: `Verify text exists in page`,
|
|
2066
|
+
operation: "verifyTextExistInPage",
|
|
2067
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2068
|
+
};
|
|
2055
2069
|
const timeout = this._getLoadTimeout(options);
|
|
2056
|
-
let error = null;
|
|
2057
|
-
let screenshotId = null;
|
|
2058
|
-
let screenshotPath = null;
|
|
2059
2070
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2060
|
-
const info = {};
|
|
2061
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2062
|
-
info.operation = "verifyTextExistInPage";
|
|
2063
2071
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2064
2072
|
if (newValue !== text) {
|
|
2065
2073
|
this.logger.info(text + "=" + newValue);
|
|
2066
2074
|
text = newValue;
|
|
2067
2075
|
}
|
|
2068
|
-
info.text = text;
|
|
2069
2076
|
let dateAlternatives = findDateAlternatives(text);
|
|
2070
2077
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2071
2078
|
try {
|
|
2079
|
+
await _preCommand(state, this);
|
|
2080
|
+
state.info.text = text;
|
|
2072
2081
|
while (true) {
|
|
2073
2082
|
const frames = this.page.frames();
|
|
2074
2083
|
let results = [];
|
|
2075
2084
|
for (let i = 0; i < frames.length; i++) {
|
|
2076
2085
|
if (dateAlternatives.date) {
|
|
2077
2086
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2078
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "
|
|
2087
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2079
2088
|
result.frame = frames[i];
|
|
2080
2089
|
results.push(result);
|
|
2081
2090
|
}
|
|
2082
2091
|
}
|
|
2083
2092
|
else if (numberAlternatives.number) {
|
|
2084
2093
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2085
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "
|
|
2094
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2086
2095
|
result.frame = frames[i];
|
|
2087
2096
|
results.push(result);
|
|
2088
2097
|
}
|
|
2089
2098
|
}
|
|
2090
2099
|
else {
|
|
2091
|
-
const result = await this._locateElementByText(frames[i], text, "
|
|
2100
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2092
2101
|
result.frame = frames[i];
|
|
2093
2102
|
results.push(result);
|
|
2094
2103
|
}
|
|
2095
2104
|
}
|
|
2096
|
-
info.results = results;
|
|
2105
|
+
state.info.results = results;
|
|
2097
2106
|
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2098
2107
|
if (resultWithElementsFound.length === 0) {
|
|
2099
|
-
if (Date.now() - startTime > timeout) {
|
|
2108
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2100
2109
|
throw new Error(`Text ${text} not found in page`);
|
|
2101
2110
|
}
|
|
2102
2111
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2108,44 +2117,89 @@ class StableBrowser {
|
|
|
2108
2117
|
await this._highlightElements(frame, dataAttribute);
|
|
2109
2118
|
const element = await frame.$(dataAttribute);
|
|
2110
2119
|
if (element) {
|
|
2111
|
-
await this.scrollIfNeeded(element, info);
|
|
2120
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2112
2121
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2113
2122
|
}
|
|
2114
2123
|
}
|
|
2115
|
-
|
|
2116
|
-
return info;
|
|
2124
|
+
await _screenshot(state, this);
|
|
2125
|
+
return state.info;
|
|
2117
2126
|
}
|
|
2118
2127
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2119
2128
|
}
|
|
2120
2129
|
catch (e) {
|
|
2121
|
-
|
|
2122
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2123
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2124
|
-
info.screenshotPath = screenshotPath;
|
|
2125
|
-
Object.assign(e, { info: info });
|
|
2126
|
-
error = e;
|
|
2127
|
-
throw e;
|
|
2130
|
+
await _commandError(state, e, this);
|
|
2128
2131
|
}
|
|
2129
2132
|
finally {
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2133
|
+
_commandFinally(state, this);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
async waitForTextToDisappear(text, options = {}, world = null) {
|
|
2137
|
+
text = unEscapeString(text);
|
|
2138
|
+
const state = {
|
|
2139
|
+
text_search: text,
|
|
2140
|
+
options,
|
|
2141
|
+
world,
|
|
2142
|
+
locate: false,
|
|
2143
|
+
scroll: false,
|
|
2144
|
+
highlight: false,
|
|
2145
|
+
type: Types.WAIT_FOR_TEXT_TO_DISAPPEAR,
|
|
2146
|
+
text: `Verify text does not exist in page`,
|
|
2147
|
+
operation: "verifyTextNotExistInPage",
|
|
2148
|
+
log: "***** verify text " + text + " does not exist in page *****\n",
|
|
2149
|
+
};
|
|
2150
|
+
const timeout = this._getLoadTimeout(options);
|
|
2151
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2152
|
+
const newValue = await this._replaceWithLocalData(text, world);
|
|
2153
|
+
if (newValue !== text) {
|
|
2154
|
+
this.logger.info(text + "=" + newValue);
|
|
2155
|
+
text = newValue;
|
|
2156
|
+
}
|
|
2157
|
+
let dateAlternatives = findDateAlternatives(text);
|
|
2158
|
+
let numberAlternatives = findNumberAlternatives(text);
|
|
2159
|
+
try {
|
|
2160
|
+
await _preCommand(state, this);
|
|
2161
|
+
state.info.text = text;
|
|
2162
|
+
while (true) {
|
|
2163
|
+
const frames = this.page.frames();
|
|
2164
|
+
let results = [];
|
|
2165
|
+
for (let i = 0; i < frames.length; i++) {
|
|
2166
|
+
if (dateAlternatives.date) {
|
|
2167
|
+
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2168
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2169
|
+
result.frame = frames[i];
|
|
2170
|
+
results.push(result);
|
|
2171
|
+
}
|
|
2141
2172
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2173
|
+
else if (numberAlternatives.number) {
|
|
2174
|
+
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2175
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2176
|
+
result.frame = frames[i];
|
|
2177
|
+
results.push(result);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
else {
|
|
2181
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2182
|
+
result.frame = frames[i];
|
|
2183
|
+
results.push(result);
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
state.info.results = results;
|
|
2187
|
+
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2188
|
+
if (resultWithElementsFound.length === 0) {
|
|
2189
|
+
await _screenshot(state, this);
|
|
2190
|
+
return state.info;
|
|
2191
|
+
}
|
|
2192
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2193
|
+
throw new Error(`Text ${text} found in page`);
|
|
2194
|
+
}
|
|
2195
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
catch (e) {
|
|
2199
|
+
await _commandError(state, e, this);
|
|
2200
|
+
}
|
|
2201
|
+
finally {
|
|
2202
|
+
_commandFinally(state, this);
|
|
2149
2203
|
}
|
|
2150
2204
|
}
|
|
2151
2205
|
_getServerUrl() {
|
|
@@ -2208,7 +2262,8 @@ class StableBrowser {
|
|
|
2208
2262
|
info.screenshotPath = screenshotPath;
|
|
2209
2263
|
Object.assign(e, { info: info });
|
|
2210
2264
|
error = e;
|
|
2211
|
-
throw e;
|
|
2265
|
+
// throw e;
|
|
2266
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2212
2267
|
}
|
|
2213
2268
|
finally {
|
|
2214
2269
|
const endTime = Date.now();
|
|
@@ -2221,7 +2276,7 @@ class StableBrowser {
|
|
|
2221
2276
|
status: "FAILED",
|
|
2222
2277
|
startTime,
|
|
2223
2278
|
endTime,
|
|
2224
|
-
message: error
|
|
2279
|
+
message: error?.message,
|
|
2225
2280
|
}
|
|
2226
2281
|
: {
|
|
2227
2282
|
status: "PASSED",
|
|
@@ -2253,7 +2308,7 @@ class StableBrowser {
|
|
|
2253
2308
|
this.logger.info("Table data verified");
|
|
2254
2309
|
}
|
|
2255
2310
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2256
|
-
|
|
2311
|
+
_validateSelectors(selectors);
|
|
2257
2312
|
const startTime = Date.now();
|
|
2258
2313
|
let error = null;
|
|
2259
2314
|
let screenshotId = null;
|
|
@@ -2275,7 +2330,8 @@ class StableBrowser {
|
|
|
2275
2330
|
info.screenshotPath = screenshotPath;
|
|
2276
2331
|
Object.assign(e, { info: info });
|
|
2277
2332
|
error = e;
|
|
2278
|
-
throw e;
|
|
2333
|
+
// throw e;
|
|
2334
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2279
2335
|
}
|
|
2280
2336
|
finally {
|
|
2281
2337
|
const endTime = Date.now();
|
|
@@ -2289,7 +2345,7 @@ class StableBrowser {
|
|
|
2289
2345
|
status: "FAILED",
|
|
2290
2346
|
startTime,
|
|
2291
2347
|
endTime,
|
|
2292
|
-
message: error
|
|
2348
|
+
message: error?.message,
|
|
2293
2349
|
}
|
|
2294
2350
|
: {
|
|
2295
2351
|
status: "PASSED",
|
|
@@ -2301,7 +2357,7 @@ class StableBrowser {
|
|
|
2301
2357
|
}
|
|
2302
2358
|
}
|
|
2303
2359
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2304
|
-
|
|
2360
|
+
_validateSelectors(selectors);
|
|
2305
2361
|
if (!query) {
|
|
2306
2362
|
throw new Error("query is null");
|
|
2307
2363
|
}
|
|
@@ -2440,7 +2496,8 @@ class StableBrowser {
|
|
|
2440
2496
|
info.screenshotPath = screenshotPath;
|
|
2441
2497
|
Object.assign(e, { info: info });
|
|
2442
2498
|
error = e;
|
|
2443
|
-
throw e;
|
|
2499
|
+
// throw e;
|
|
2500
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2444
2501
|
}
|
|
2445
2502
|
finally {
|
|
2446
2503
|
const endTime = Date.now();
|
|
@@ -2454,7 +2511,7 @@ class StableBrowser {
|
|
|
2454
2511
|
status: "FAILED",
|
|
2455
2512
|
startTime,
|
|
2456
2513
|
endTime,
|
|
2457
|
-
message: error
|
|
2514
|
+
message: error?.message,
|
|
2458
2515
|
}
|
|
2459
2516
|
: {
|
|
2460
2517
|
status: "PASSED",
|
|
@@ -2466,27 +2523,7 @@ class StableBrowser {
|
|
|
2466
2523
|
}
|
|
2467
2524
|
}
|
|
2468
2525
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2469
|
-
|
|
2470
|
-
return value;
|
|
2471
|
-
}
|
|
2472
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2473
|
-
let regex = /{{(.*?)}}/g;
|
|
2474
|
-
let matches = value.match(regex);
|
|
2475
|
-
if (matches) {
|
|
2476
|
-
const testData = this.getTestData(world);
|
|
2477
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2478
|
-
let match = matches[i];
|
|
2479
|
-
let key = match.substring(2, match.length - 2);
|
|
2480
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2481
|
-
if (newValue !== null) {
|
|
2482
|
-
value = value.replace(match, newValue);
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
}
|
|
2486
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2487
|
-
return await decrypt(value, null, totpWait);
|
|
2488
|
-
}
|
|
2489
|
-
return value;
|
|
2526
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2490
2527
|
}
|
|
2491
2528
|
_getLoadTimeout(options) {
|
|
2492
2529
|
let timeout = 15000;
|
|
@@ -2523,13 +2560,13 @@ class StableBrowser {
|
|
|
2523
2560
|
}
|
|
2524
2561
|
catch (e) {
|
|
2525
2562
|
if (e.label === "networkidle") {
|
|
2526
|
-
console.log("
|
|
2563
|
+
console.log("waited for the network to be idle timeout");
|
|
2527
2564
|
}
|
|
2528
2565
|
else if (e.label === "load") {
|
|
2529
|
-
console.log("
|
|
2566
|
+
console.log("waited for the load timeout");
|
|
2530
2567
|
}
|
|
2531
2568
|
else if (e.label === "domcontentloaded") {
|
|
2532
|
-
console.log("
|
|
2569
|
+
console.log("waited for the domcontent loaded timeout");
|
|
2533
2570
|
}
|
|
2534
2571
|
console.log(".");
|
|
2535
2572
|
}
|
|
@@ -2546,7 +2583,7 @@ class StableBrowser {
|
|
|
2546
2583
|
status: "FAILED",
|
|
2547
2584
|
startTime,
|
|
2548
2585
|
endTime,
|
|
2549
|
-
message: error
|
|
2586
|
+
message: error?.message,
|
|
2550
2587
|
}
|
|
2551
2588
|
: {
|
|
2552
2589
|
status: "PASSED",
|
|
@@ -2557,46 +2594,28 @@ class StableBrowser {
|
|
|
2557
2594
|
}
|
|
2558
2595
|
}
|
|
2559
2596
|
async closePage(options = {}, world = null) {
|
|
2560
|
-
const
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2597
|
+
const state = {
|
|
2598
|
+
options,
|
|
2599
|
+
world,
|
|
2600
|
+
locate: false,
|
|
2601
|
+
scroll: false,
|
|
2602
|
+
highlight: false,
|
|
2603
|
+
type: Types.CLOSE_PAGE,
|
|
2604
|
+
text: `Close page`,
|
|
2605
|
+
operation: "closePage",
|
|
2606
|
+
log: "***** close page *****\n",
|
|
2607
|
+
throwError: false,
|
|
2608
|
+
};
|
|
2565
2609
|
try {
|
|
2610
|
+
await _preCommand(state, this);
|
|
2566
2611
|
await this.page.close();
|
|
2567
|
-
if (this.context && this.context.pages && this.context.pages.length > 0) {
|
|
2568
|
-
this.context.pages.pop();
|
|
2569
|
-
this.page = this.context.pages[this.context.pages.length - 1];
|
|
2570
|
-
this.context.page = this.page;
|
|
2571
|
-
let title = await this.page.title();
|
|
2572
|
-
console.log("Switched to page " + title);
|
|
2573
|
-
}
|
|
2574
2612
|
}
|
|
2575
2613
|
catch (e) {
|
|
2576
2614
|
console.log(".");
|
|
2615
|
+
await _commandError(state, e, this);
|
|
2577
2616
|
}
|
|
2578
2617
|
finally {
|
|
2579
|
-
|
|
2580
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2581
|
-
const endTime = Date.now();
|
|
2582
|
-
this._reportToWorld(world, {
|
|
2583
|
-
type: Types.CLOSE_PAGE,
|
|
2584
|
-
text: "close page",
|
|
2585
|
-
screenshotId,
|
|
2586
|
-
result: error
|
|
2587
|
-
? {
|
|
2588
|
-
status: "FAILED",
|
|
2589
|
-
startTime,
|
|
2590
|
-
endTime,
|
|
2591
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2592
|
-
}
|
|
2593
|
-
: {
|
|
2594
|
-
status: "PASSED",
|
|
2595
|
-
startTime,
|
|
2596
|
-
endTime,
|
|
2597
|
-
},
|
|
2598
|
-
info: info,
|
|
2599
|
-
});
|
|
2618
|
+
_commandFinally(state, this);
|
|
2600
2619
|
}
|
|
2601
2620
|
}
|
|
2602
2621
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
@@ -2616,6 +2635,7 @@ class StableBrowser {
|
|
|
2616
2635
|
}
|
|
2617
2636
|
catch (e) {
|
|
2618
2637
|
console.log(".");
|
|
2638
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2619
2639
|
}
|
|
2620
2640
|
finally {
|
|
2621
2641
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -2630,7 +2650,7 @@ class StableBrowser {
|
|
|
2630
2650
|
status: "FAILED",
|
|
2631
2651
|
startTime,
|
|
2632
2652
|
endTime,
|
|
2633
|
-
message: error
|
|
2653
|
+
message: error?.message,
|
|
2634
2654
|
}
|
|
2635
2655
|
: {
|
|
2636
2656
|
status: "PASSED",
|
|
@@ -2652,6 +2672,7 @@ class StableBrowser {
|
|
|
2652
2672
|
}
|
|
2653
2673
|
catch (e) {
|
|
2654
2674
|
console.log(".");
|
|
2675
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2655
2676
|
}
|
|
2656
2677
|
finally {
|
|
2657
2678
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -2666,7 +2687,7 @@ class StableBrowser {
|
|
|
2666
2687
|
status: "FAILED",
|
|
2667
2688
|
startTime,
|
|
2668
2689
|
endTime,
|
|
2669
|
-
message: error
|
|
2690
|
+
message: error?.message,
|
|
2670
2691
|
}
|
|
2671
2692
|
: {
|
|
2672
2693
|
status: "PASSED",
|
|
@@ -2679,33 +2700,18 @@ class StableBrowser {
|
|
|
2679
2700
|
}
|
|
2680
2701
|
async scrollIfNeeded(element, info) {
|
|
2681
2702
|
try {
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
if (rect &&
|
|
2685
|
-
rect.top >= 0 &&
|
|
2686
|
-
rect.left >= 0 &&
|
|
2687
|
-
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
|
|
2688
|
-
rect.right <= (window.innerWidth || document.documentElement.clientWidth)) {
|
|
2689
|
-
return false;
|
|
2690
|
-
}
|
|
2691
|
-
else {
|
|
2692
|
-
node.scrollIntoView({
|
|
2693
|
-
behavior: "smooth",
|
|
2694
|
-
block: "center",
|
|
2695
|
-
inline: "center",
|
|
2696
|
-
});
|
|
2697
|
-
return true;
|
|
2698
|
-
}
|
|
2703
|
+
await element.scrollIntoViewIfNeeded({
|
|
2704
|
+
timeout: 2000,
|
|
2699
2705
|
});
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
}
|
|
2706
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2707
|
+
if (info) {
|
|
2708
|
+
info.box = await element.boundingBox({
|
|
2709
|
+
timeout: 1000,
|
|
2710
|
+
});
|
|
2705
2711
|
}
|
|
2706
2712
|
}
|
|
2707
2713
|
catch (e) {
|
|
2708
|
-
console.log("
|
|
2714
|
+
console.log("#-#");
|
|
2709
2715
|
}
|
|
2710
2716
|
}
|
|
2711
2717
|
_reportToWorld(world, properties) {
|
|
@@ -2714,6 +2720,31 @@ class StableBrowser {
|
|
|
2714
2720
|
}
|
|
2715
2721
|
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2716
2722
|
}
|
|
2723
|
+
async beforeStep(world, step) {
|
|
2724
|
+
this.stepName = step.pickleStep.text;
|
|
2725
|
+
this.logger.info("step: " + this.stepName);
|
|
2726
|
+
if (this.stepIndex === undefined) {
|
|
2727
|
+
this.stepIndex = 0;
|
|
2728
|
+
}
|
|
2729
|
+
else {
|
|
2730
|
+
this.stepIndex++;
|
|
2731
|
+
}
|
|
2732
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2733
|
+
if (this.context.browserObject.context) {
|
|
2734
|
+
await this.context.browserObject.context.tracing.startChunk({ title: this.stepName });
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
async afterStep(world, step) {
|
|
2739
|
+
this.stepName = null;
|
|
2740
|
+
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
2741
|
+
if (this.context.browserObject.context) {
|
|
2742
|
+
await this.context.browserObject.context.tracing.stopChunk({
|
|
2743
|
+
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2717
2748
|
}
|
|
2718
2749
|
function createTimedPromise(promise, label) {
|
|
2719
2750
|
return promise
|
|
@@ -2866,5 +2897,10 @@ const KEYBOARD_EVENTS = [
|
|
|
2866
2897
|
"TVAntennaCable",
|
|
2867
2898
|
"TVAudioDescription",
|
|
2868
2899
|
];
|
|
2900
|
+
function unEscapeString(str) {
|
|
2901
|
+
const placeholder = "__NEWLINE__";
|
|
2902
|
+
str = str.replace(new RegExp(placeholder, "g"), "\n");
|
|
2903
|
+
return str;
|
|
2904
|
+
}
|
|
2869
2905
|
export { StableBrowser };
|
|
2870
2906
|
//# sourceMappingURL=stable_browser.js.map
|