automation_model 1.0.448-dev → 1.0.448-stage
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 +183 -7
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +21 -39
- package/lib/auto_page.js.map +1 -1
- package/lib/axe/axe.mini.js +12 -0
- package/lib/browser_manager.js +19 -9
- package/lib/browser_manager.js.map +1 -1
- package/lib/command_common.d.ts +5 -0
- package/lib/command_common.js +131 -0
- package/lib/command_common.js.map +1 -0
- package/lib/environment.js +5 -3
- 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 +1 -1
- package/lib/init_browser.js +39 -2
- 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 +23 -19
- package/lib/stable_browser.js +607 -814
- package/lib/stable_browser.js.map +1 -1
- package/lib/test_context.d.ts +2 -0
- package/lib/test_context.js +11 -10
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +3 -1
- package/lib/utils.js +68 -1
- package/lib/utils.js.map +1 -1
- package/package.json +6 -6
package/lib/stable_browser.js
CHANGED
|
@@ -2,20 +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";
|
|
16
15
|
import { Readable } from "node:stream";
|
|
17
16
|
import readline from "readline";
|
|
18
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";
|
|
19
21
|
const Types = {
|
|
20
22
|
CLICK: "click_element",
|
|
21
23
|
NAVIGATE: "navigate",
|
|
@@ -45,16 +47,22 @@ const Types = {
|
|
|
45
47
|
};
|
|
46
48
|
export const apps = {};
|
|
47
49
|
class StableBrowser {
|
|
48
|
-
|
|
50
|
+
browser;
|
|
51
|
+
page;
|
|
52
|
+
logger;
|
|
53
|
+
context;
|
|
54
|
+
world;
|
|
55
|
+
project_path = null;
|
|
56
|
+
webLogFile = null;
|
|
57
|
+
networkLogger = null;
|
|
58
|
+
configuration = null;
|
|
59
|
+
appName = "main";
|
|
60
|
+
constructor(browser, page, logger = null, context = null, world = null) {
|
|
49
61
|
this.browser = browser;
|
|
50
62
|
this.page = page;
|
|
51
63
|
this.logger = logger;
|
|
52
64
|
this.context = context;
|
|
53
|
-
this.
|
|
54
|
-
this.webLogFile = null;
|
|
55
|
-
this.networkLogger = null;
|
|
56
|
-
this.configuration = null;
|
|
57
|
-
this.appName = "main";
|
|
65
|
+
this.world = world;
|
|
58
66
|
if (!this.logger) {
|
|
59
67
|
this.logger = console;
|
|
60
68
|
}
|
|
@@ -79,20 +87,32 @@ class StableBrowser {
|
|
|
79
87
|
catch (e) {
|
|
80
88
|
this.logger.error("unable to read ai_config.json");
|
|
81
89
|
}
|
|
82
|
-
context.pageLoading = { status: false };
|
|
83
|
-
context.pages = [this.page];
|
|
84
90
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
91
|
+
this.world = world;
|
|
92
|
+
context.pages = [this.page];
|
|
93
|
+
context.pageLoading = { status: false };
|
|
94
|
+
this.registerEventListeners(this.context);
|
|
95
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
96
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
87
97
|
}
|
|
88
98
|
registerEventListeners(context) {
|
|
89
|
-
this.registerConsoleLogListener(this.page, context
|
|
99
|
+
this.registerConsoleLogListener(this.page, context);
|
|
90
100
|
this.registerRequestListener(this.page, context, this.webLogFile);
|
|
101
|
+
if (!context.pageLoading) {
|
|
102
|
+
context.pageLoading = { status: false };
|
|
103
|
+
}
|
|
91
104
|
context.playContext.on("page", async function (page) {
|
|
105
|
+
if (this.configuration && this.configuration.closePopups === true) {
|
|
106
|
+
console.log("close unexpected popups");
|
|
107
|
+
await page.close();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
92
110
|
context.pageLoading.status = true;
|
|
93
111
|
this.page = page;
|
|
94
112
|
context.page = page;
|
|
95
113
|
context.pages.push(page);
|
|
114
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
115
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
96
116
|
page.on("close", async () => {
|
|
97
117
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
98
118
|
this.context.pages.pop();
|
|
@@ -122,10 +142,10 @@ class StableBrowser {
|
|
|
122
142
|
if (this.appName === appName) {
|
|
123
143
|
return;
|
|
124
144
|
}
|
|
125
|
-
let
|
|
145
|
+
let navigate = false;
|
|
126
146
|
if (!apps[appName]) {
|
|
127
147
|
let newContext = await getContext(null, false, this.logger, appName, false, this);
|
|
128
|
-
|
|
148
|
+
navigate = true;
|
|
129
149
|
apps[appName] = {
|
|
130
150
|
context: newContext,
|
|
131
151
|
browser: newContext.browser,
|
|
@@ -137,8 +157,7 @@ class StableBrowser {
|
|
|
137
157
|
this._copyContext(apps[appName], this);
|
|
138
158
|
apps[this.appName] = tempContext;
|
|
139
159
|
this.appName = appName;
|
|
140
|
-
if (
|
|
141
|
-
this.registerEventListeners(this.context);
|
|
160
|
+
if (navigate) {
|
|
142
161
|
await this.goto(this.context.environment.baseUrl);
|
|
143
162
|
await this.waitForPageLoad();
|
|
144
163
|
}
|
|
@@ -159,18 +178,21 @@ class StableBrowser {
|
|
|
159
178
|
const fileName = nextIndex + ".json";
|
|
160
179
|
return path.join(logFolder, fileName);
|
|
161
180
|
}
|
|
162
|
-
registerConsoleLogListener(page, context
|
|
181
|
+
registerConsoleLogListener(page, context) {
|
|
163
182
|
if (!this.context.webLogger) {
|
|
164
183
|
this.context.webLogger = [];
|
|
165
184
|
}
|
|
166
185
|
page.on("console", async (msg) => {
|
|
167
|
-
|
|
186
|
+
const obj = {
|
|
168
187
|
type: msg.type(),
|
|
169
188
|
text: msg.text(),
|
|
170
189
|
location: msg.location(),
|
|
171
190
|
time: new Date().toISOString(),
|
|
172
|
-
}
|
|
173
|
-
|
|
191
|
+
};
|
|
192
|
+
this.context.webLogger.push(obj);
|
|
193
|
+
if (msg.type() === "error") {
|
|
194
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
195
|
+
}
|
|
174
196
|
});
|
|
175
197
|
}
|
|
176
198
|
registerRequestListener(page, context, logFile) {
|
|
@@ -178,6 +200,7 @@ class StableBrowser {
|
|
|
178
200
|
this.context.networkLogger = [];
|
|
179
201
|
}
|
|
180
202
|
page.on("request", async (data) => {
|
|
203
|
+
const startTime = new Date().getTime();
|
|
181
204
|
try {
|
|
182
205
|
const pageUrl = new URL(page.url());
|
|
183
206
|
const requestUrl = new URL(data.url());
|
|
@@ -190,13 +213,18 @@ class StableBrowser {
|
|
|
190
213
|
}
|
|
191
214
|
}
|
|
192
215
|
}
|
|
193
|
-
|
|
216
|
+
const response = await data.response();
|
|
217
|
+
const endTime = new Date().getTime();
|
|
218
|
+
const obj = {
|
|
194
219
|
url: data.url(),
|
|
195
220
|
method: data.method(),
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
221
|
+
postData: data.postData(),
|
|
222
|
+
error: data.failure() ? data.failure().errorText : null,
|
|
223
|
+
duration: endTime - startTime,
|
|
224
|
+
startTime,
|
|
225
|
+
};
|
|
226
|
+
context.networkLogger.push(obj);
|
|
227
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
200
228
|
}
|
|
201
229
|
catch (error) {
|
|
202
230
|
console.error("Error in request listener", error);
|
|
@@ -204,9 +232,9 @@ class StableBrowser {
|
|
|
204
232
|
error: "not able to listen",
|
|
205
233
|
message: error.message,
|
|
206
234
|
stack: error.stack,
|
|
207
|
-
time: new Date().toISOString()
|
|
235
|
+
time: new Date().toISOString(),
|
|
208
236
|
});
|
|
209
|
-
await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
237
|
+
// await fs.promises.writeFile(logFile, JSON.stringify(context.networkLogger, null, 2));
|
|
210
238
|
}
|
|
211
239
|
});
|
|
212
240
|
}
|
|
@@ -221,20 +249,6 @@ class StableBrowser {
|
|
|
221
249
|
timeout: 60000,
|
|
222
250
|
});
|
|
223
251
|
}
|
|
224
|
-
_validateSelectors(selectors) {
|
|
225
|
-
if (!selectors) {
|
|
226
|
-
throw new Error("selectors is null");
|
|
227
|
-
}
|
|
228
|
-
if (!selectors.locators) {
|
|
229
|
-
throw new Error("selectors.locators is null");
|
|
230
|
-
}
|
|
231
|
-
if (!Array.isArray(selectors.locators)) {
|
|
232
|
-
throw new Error("selectors.locators expected to be array");
|
|
233
|
-
}
|
|
234
|
-
if (selectors.locators.length === 0) {
|
|
235
|
-
throw new Error("selectors.locators expected to be non empty array");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
252
|
_fixUsingParams(text, _params) {
|
|
239
253
|
if (!_params || typeof text !== "string") {
|
|
240
254
|
return text;
|
|
@@ -302,7 +316,7 @@ class StableBrowser {
|
|
|
302
316
|
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
303
317
|
}
|
|
304
318
|
}
|
|
305
|
-
if (locator
|
|
319
|
+
if (locator?.engine) {
|
|
306
320
|
if (locator.engine === "css") {
|
|
307
321
|
locatorReturn = scope.locator(locator.selector);
|
|
308
322
|
}
|
|
@@ -323,7 +337,10 @@ class StableBrowser {
|
|
|
323
337
|
return locatorReturn;
|
|
324
338
|
}
|
|
325
339
|
async _locateElmentByTextClimbCss(scope, text, climb, css, _params) {
|
|
326
|
-
|
|
340
|
+
if (css && css.locator) {
|
|
341
|
+
css = css.locator;
|
|
342
|
+
}
|
|
343
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
327
344
|
if (result.elementCount === 0) {
|
|
328
345
|
return;
|
|
329
346
|
}
|
|
@@ -338,7 +355,7 @@ class StableBrowser {
|
|
|
338
355
|
}
|
|
339
356
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
340
357
|
//const stringifyText = JSON.stringify(text);
|
|
341
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
358
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
342
359
|
function isParent(parent, child) {
|
|
343
360
|
let currentNode = child.parentNode;
|
|
344
361
|
while (currentNode !== null) {
|
|
@@ -350,6 +367,15 @@ class StableBrowser {
|
|
|
350
367
|
return false;
|
|
351
368
|
}
|
|
352
369
|
document.isParent = isParent;
|
|
370
|
+
function getRegex(str) {
|
|
371
|
+
const match = str.match(/^\/(.*?)\/([gimuy]*)$/);
|
|
372
|
+
if (!match) {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
let [_, pattern, flags] = match;
|
|
376
|
+
return new RegExp(pattern, flags);
|
|
377
|
+
}
|
|
378
|
+
document.getRegex = getRegex;
|
|
353
379
|
function collectAllShadowDomElements(element, result = []) {
|
|
354
380
|
// Check and add the element if it has a shadow root
|
|
355
381
|
if (element.shadowRoot) {
|
|
@@ -366,7 +392,11 @@ class StableBrowser {
|
|
|
366
392
|
}
|
|
367
393
|
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
368
394
|
if (!tag) {
|
|
369
|
-
tag = "
|
|
395
|
+
tag = "*:not(script, style, head)";
|
|
396
|
+
}
|
|
397
|
+
let regexpSearch = document.getRegex(text);
|
|
398
|
+
if (regexpSearch) {
|
|
399
|
+
regex = true;
|
|
370
400
|
}
|
|
371
401
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
372
402
|
let shadowHosts = [];
|
|
@@ -383,7 +413,9 @@ class StableBrowser {
|
|
|
383
413
|
let randomToken = null;
|
|
384
414
|
const foundElements = [];
|
|
385
415
|
if (regex) {
|
|
386
|
-
|
|
416
|
+
if (!regexpSearch) {
|
|
417
|
+
regexpSearch = new RegExp(text, "im");
|
|
418
|
+
}
|
|
387
419
|
for (let i = 0; i < elements.length; i++) {
|
|
388
420
|
const element = elements[i];
|
|
389
421
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -397,8 +429,8 @@ class StableBrowser {
|
|
|
397
429
|
for (let i = 0; i < elements.length; i++) {
|
|
398
430
|
const element = elements[i];
|
|
399
431
|
if (partial) {
|
|
400
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
401
|
-
(element.value && element.value.includes(text))) {
|
|
432
|
+
if ((element.innerText && element.innerText.toLowerCase().trim().includes(text.toLowerCase())) ||
|
|
433
|
+
(element.value && element.value.toLowerCase().includes(text.toLowerCase()))) {
|
|
402
434
|
foundElements.push(element);
|
|
403
435
|
}
|
|
404
436
|
}
|
|
@@ -443,18 +475,29 @@ class StableBrowser {
|
|
|
443
475
|
}
|
|
444
476
|
async _collectLocatorInformation(selectorHierarchy, index = 0, scope, foundLocators, _params, info, visibleOnly = true) {
|
|
445
477
|
let locatorSearch = selectorHierarchy[index];
|
|
478
|
+
try {
|
|
479
|
+
locatorSearch = JSON.parse(this._fixUsingParams(JSON.stringify(locatorSearch), _params));
|
|
480
|
+
}
|
|
481
|
+
catch (e) {
|
|
482
|
+
console.error(e);
|
|
483
|
+
}
|
|
446
484
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
447
485
|
let locator = null;
|
|
448
486
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
449
487
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
450
488
|
if (!locatorString) {
|
|
489
|
+
info.failCause.textNotFound = true;
|
|
490
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
451
491
|
return;
|
|
452
492
|
}
|
|
453
493
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
454
494
|
}
|
|
455
495
|
else if (locatorSearch.text) {
|
|
456
|
-
let
|
|
496
|
+
let text = this._fixUsingParams(locatorSearch.text, _params);
|
|
497
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
457
498
|
if (result.elementCount === 0) {
|
|
499
|
+
info.failCause.textNotFound = true;
|
|
500
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
458
501
|
return;
|
|
459
502
|
}
|
|
460
503
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -471,6 +514,9 @@ class StableBrowser {
|
|
|
471
514
|
// cssHref = true;
|
|
472
515
|
// }
|
|
473
516
|
let count = await locator.count();
|
|
517
|
+
if (count > 0 && !info.failCause.count) {
|
|
518
|
+
info.failCause.count = count;
|
|
519
|
+
}
|
|
474
520
|
//info.log += "total elements found " + count + "\n";
|
|
475
521
|
//let visibleCount = 0;
|
|
476
522
|
let visibleLocator = null;
|
|
@@ -488,6 +534,8 @@ class StableBrowser {
|
|
|
488
534
|
foundLocators.push(locator.nth(j));
|
|
489
535
|
}
|
|
490
536
|
else {
|
|
537
|
+
info.failCause.visible = visible;
|
|
538
|
+
info.failCause.enabled = enabled;
|
|
491
539
|
if (!info.printMessages) {
|
|
492
540
|
info.printMessages = {};
|
|
493
541
|
}
|
|
@@ -499,6 +547,11 @@ class StableBrowser {
|
|
|
499
547
|
}
|
|
500
548
|
}
|
|
501
549
|
async closeUnexpectedPopups(info, _params) {
|
|
550
|
+
if (!info) {
|
|
551
|
+
info = {};
|
|
552
|
+
info.failCause = {};
|
|
553
|
+
info.log = "";
|
|
554
|
+
}
|
|
502
555
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
503
556
|
if (!info) {
|
|
504
557
|
info = {};
|
|
@@ -539,7 +592,10 @@ class StableBrowser {
|
|
|
539
592
|
}
|
|
540
593
|
return { rerun: false };
|
|
541
594
|
}
|
|
542
|
-
async _locate(selectors, info, _params, timeout
|
|
595
|
+
async _locate(selectors, info, _params, timeout) {
|
|
596
|
+
if (!timeout) {
|
|
597
|
+
timeout = 30000;
|
|
598
|
+
}
|
|
543
599
|
for (let i = 0; i < 3; i++) {
|
|
544
600
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
545
601
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
@@ -553,32 +609,46 @@ class StableBrowser {
|
|
|
553
609
|
}
|
|
554
610
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
555
611
|
}
|
|
556
|
-
async
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
612
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
613
|
+
if (!info) {
|
|
614
|
+
info = {};
|
|
615
|
+
info.failCause = {};
|
|
616
|
+
info.log = "";
|
|
617
|
+
}
|
|
562
618
|
let scope = this.page;
|
|
619
|
+
if (selectors.frame) {
|
|
620
|
+
return selectors.frame;
|
|
621
|
+
}
|
|
563
622
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
564
|
-
const findFrame = (frame, framescope) => {
|
|
623
|
+
const findFrame = async (frame, framescope) => {
|
|
565
624
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
566
625
|
let frameLocator = frame.selectors[i];
|
|
567
626
|
if (frameLocator.css) {
|
|
568
|
-
|
|
569
|
-
|
|
627
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
628
|
+
if (frameLocator.index) {
|
|
629
|
+
testframescope = framescope.nth(frameLocator.index);
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
await testframescope.owner().evaluateHandle(() => true, null, {
|
|
633
|
+
timeout: 5000,
|
|
634
|
+
});
|
|
635
|
+
framescope = testframescope;
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
console.error("frame not found " + frameLocator.css);
|
|
640
|
+
}
|
|
570
641
|
}
|
|
571
642
|
}
|
|
572
643
|
if (frame.children) {
|
|
573
|
-
return findFrame(frame.children, framescope);
|
|
644
|
+
return await findFrame(frame.children, framescope);
|
|
574
645
|
}
|
|
575
646
|
return framescope;
|
|
576
647
|
};
|
|
577
|
-
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
578
648
|
while (true) {
|
|
579
649
|
let frameFound = false;
|
|
580
650
|
if (selectors.nestFrmLoc) {
|
|
581
|
-
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
651
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
582
652
|
frameFound = true;
|
|
583
653
|
break;
|
|
584
654
|
}
|
|
@@ -598,6 +668,8 @@ class StableBrowser {
|
|
|
598
668
|
if (!scope) {
|
|
599
669
|
info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
600
670
|
if (performance.now() - startTime > timeout) {
|
|
671
|
+
info.failCause.iframeNotFound = true;
|
|
672
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
601
673
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
602
674
|
}
|
|
603
675
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -607,6 +679,30 @@ class StableBrowser {
|
|
|
607
679
|
}
|
|
608
680
|
}
|
|
609
681
|
}
|
|
682
|
+
if (!scope) {
|
|
683
|
+
scope = this.page;
|
|
684
|
+
}
|
|
685
|
+
return scope;
|
|
686
|
+
}
|
|
687
|
+
async _getDocumentBody(selectors, timeout = 30000, info) {
|
|
688
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
689
|
+
return scope.evaluate(() => {
|
|
690
|
+
var bodyContent = document.body.innerHTML;
|
|
691
|
+
return bodyContent;
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
async _locate_internal(selectors, info, _params, timeout = 30000) {
|
|
695
|
+
if (!info) {
|
|
696
|
+
info = {};
|
|
697
|
+
info.failCause = {};
|
|
698
|
+
info.log = "";
|
|
699
|
+
}
|
|
700
|
+
let highPriorityTimeout = 5000;
|
|
701
|
+
let visibleOnlyTimeout = 6000;
|
|
702
|
+
let startTime = performance.now();
|
|
703
|
+
let locatorsCount = 0;
|
|
704
|
+
//let arrayMode = Array.isArray(selectors);
|
|
705
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
610
706
|
let selectorsLocators = null;
|
|
611
707
|
selectorsLocators = selectors.locators;
|
|
612
708
|
// group selectors by priority
|
|
@@ -708,6 +804,8 @@ class StableBrowser {
|
|
|
708
804
|
}
|
|
709
805
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
710
806
|
info.log += "failed to locate unique element, total elements found " + locatorsCount + "\n";
|
|
807
|
+
info.failCause.locatorNotFound = true;
|
|
808
|
+
info.failCause.lastError = "failed to locate unique element";
|
|
711
809
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
712
810
|
}
|
|
713
811
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -739,89 +837,129 @@ class StableBrowser {
|
|
|
739
837
|
});
|
|
740
838
|
result.locatorIndex = i;
|
|
741
839
|
}
|
|
840
|
+
if (foundLocators.length > 1) {
|
|
841
|
+
info.failCause.foundMultiple = true;
|
|
842
|
+
}
|
|
742
843
|
}
|
|
743
844
|
return result;
|
|
744
845
|
}
|
|
745
|
-
async
|
|
746
|
-
this._validateSelectors(selectors);
|
|
846
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
747
847
|
const startTime = Date.now();
|
|
748
|
-
|
|
749
|
-
|
|
848
|
+
let timeout = 30000;
|
|
849
|
+
if (options && options.timeout) {
|
|
850
|
+
timeout = options.timeout;
|
|
750
851
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
852
|
+
while (true) {
|
|
853
|
+
try {
|
|
854
|
+
const result = await locate_element(this.context, elementDescription, "click");
|
|
855
|
+
if (result?.elementNumber >= 0) {
|
|
856
|
+
const selectors = {
|
|
857
|
+
frame: result?.frame,
|
|
858
|
+
locators: [
|
|
859
|
+
{
|
|
860
|
+
css: result?.css,
|
|
861
|
+
},
|
|
862
|
+
],
|
|
863
|
+
};
|
|
864
|
+
await this.click(selectors, _params, options, world);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch (e) {
|
|
869
|
+
if (performance.now() - startTime > timeout) {
|
|
870
|
+
// throw e;
|
|
871
|
+
await _commandError({ text: "simpleClick", operation: "simpleClick", elementDescription, info: {} }, e, this);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
async simpleClickType(elementDescription, value, _params, options = {}, world = null) {
|
|
878
|
+
const startTime = Date.now();
|
|
879
|
+
let timeout = 30000;
|
|
880
|
+
if (options && options.timeout) {
|
|
881
|
+
timeout = options.timeout;
|
|
882
|
+
}
|
|
883
|
+
while (true) {
|
|
884
|
+
try {
|
|
885
|
+
const result = await locate_element(this.context, elementDescription, "fill", value);
|
|
886
|
+
if (result?.elementNumber >= 0) {
|
|
887
|
+
const selectors = {
|
|
888
|
+
frame: result?.frame,
|
|
889
|
+
locators: [
|
|
890
|
+
{
|
|
891
|
+
css: result?.css,
|
|
892
|
+
},
|
|
893
|
+
],
|
|
894
|
+
};
|
|
895
|
+
await this.clickType(selectors, value, false, _params, options, world);
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
catch (e) {
|
|
900
|
+
if (performance.now() - startTime > timeout) {
|
|
901
|
+
// throw e;
|
|
902
|
+
await _commandError({ text: "simpleClickType", operation: "simpleClickType", value, elementDescription, info: {} }, e, this);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
async click(selectors, _params, options = {}, world = null) {
|
|
909
|
+
const state = {
|
|
910
|
+
selectors,
|
|
911
|
+
_params,
|
|
912
|
+
options,
|
|
913
|
+
world,
|
|
914
|
+
text: "Click element",
|
|
915
|
+
type: Types.CLICK,
|
|
916
|
+
operation: "click",
|
|
917
|
+
log: "***** click on " + selectors.element_name + " *****\n",
|
|
918
|
+
};
|
|
758
919
|
try {
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
920
|
+
await _preCommand(state, this);
|
|
921
|
+
if (state.options && state.options.context) {
|
|
922
|
+
state.selectors.locators[0].text = state.options.context;
|
|
923
|
+
}
|
|
762
924
|
try {
|
|
763
|
-
await
|
|
764
|
-
await element.click();
|
|
925
|
+
await state.element.click();
|
|
765
926
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
766
927
|
}
|
|
767
928
|
catch (e) {
|
|
768
929
|
// await this.closeUnexpectedPopups();
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
await element.dispatchEvent("click");
|
|
930
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
931
|
+
await state.element.dispatchEvent("click");
|
|
772
932
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
773
933
|
}
|
|
774
934
|
await this.waitForPageLoad();
|
|
775
|
-
return info;
|
|
935
|
+
return state.info;
|
|
776
936
|
}
|
|
777
937
|
catch (e) {
|
|
778
|
-
|
|
779
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
780
|
-
info.screenshotPath = screenshotPath;
|
|
781
|
-
Object.assign(e, { info: info });
|
|
782
|
-
error = e;
|
|
783
|
-
throw e;
|
|
938
|
+
await _commandError(state, e, this);
|
|
784
939
|
}
|
|
785
940
|
finally {
|
|
786
|
-
|
|
787
|
-
this._reportToWorld(world, {
|
|
788
|
-
element_name: selectors.element_name,
|
|
789
|
-
type: Types.CLICK,
|
|
790
|
-
text: `Click element`,
|
|
791
|
-
screenshotId,
|
|
792
|
-
result: error
|
|
793
|
-
? {
|
|
794
|
-
status: "FAILED",
|
|
795
|
-
startTime,
|
|
796
|
-
endTime,
|
|
797
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
798
|
-
}
|
|
799
|
-
: {
|
|
800
|
-
status: "PASSED",
|
|
801
|
-
startTime,
|
|
802
|
-
endTime,
|
|
803
|
-
},
|
|
804
|
-
info: info,
|
|
805
|
-
});
|
|
941
|
+
_commandFinally(state, this);
|
|
806
942
|
}
|
|
807
943
|
}
|
|
808
944
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
945
|
+
const state = {
|
|
946
|
+
selectors,
|
|
947
|
+
_params,
|
|
948
|
+
options,
|
|
949
|
+
world,
|
|
950
|
+
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
951
|
+
text: checked ? `Check element` : `Uncheck element`,
|
|
952
|
+
operation: "setCheck",
|
|
953
|
+
log: "***** check " + selectors.element_name + " *****\n",
|
|
954
|
+
};
|
|
819
955
|
try {
|
|
820
|
-
|
|
821
|
-
|
|
956
|
+
await _preCommand(state, this);
|
|
957
|
+
state.info.checked = checked;
|
|
958
|
+
// let element = await this._locate(selectors, info, _params);
|
|
959
|
+
// ({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
822
960
|
try {
|
|
823
|
-
await this._highlightElements(element);
|
|
824
|
-
await element.setChecked(checked);
|
|
961
|
+
// await this._highlightElements(element);
|
|
962
|
+
await state.element.setChecked(checked);
|
|
825
963
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
826
964
|
}
|
|
827
965
|
catch (e) {
|
|
@@ -830,179 +968,108 @@ class StableBrowser {
|
|
|
830
968
|
}
|
|
831
969
|
else {
|
|
832
970
|
//await this.closeUnexpectedPopups();
|
|
833
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
834
|
-
element = await this._locate(selectors, info, _params);
|
|
835
|
-
await element.setChecked(checked, { timeout: 5000, force: true });
|
|
971
|
+
state.info.log += "setCheck failed, will try again" + "\n";
|
|
972
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
973
|
+
await state.element.setChecked(checked, { timeout: 5000, force: true });
|
|
836
974
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
837
975
|
}
|
|
838
976
|
}
|
|
839
977
|
await this.waitForPageLoad();
|
|
840
|
-
return info;
|
|
978
|
+
return state.info;
|
|
841
979
|
}
|
|
842
980
|
catch (e) {
|
|
843
|
-
|
|
844
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
845
|
-
info.screenshotPath = screenshotPath;
|
|
846
|
-
Object.assign(e, { info: info });
|
|
847
|
-
error = e;
|
|
848
|
-
throw e;
|
|
981
|
+
await _commandError(state, e, this);
|
|
849
982
|
}
|
|
850
983
|
finally {
|
|
851
|
-
|
|
852
|
-
this._reportToWorld(world, {
|
|
853
|
-
element_name: selectors.element_name,
|
|
854
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
855
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
856
|
-
screenshotId,
|
|
857
|
-
result: error
|
|
858
|
-
? {
|
|
859
|
-
status: "FAILED",
|
|
860
|
-
startTime,
|
|
861
|
-
endTime,
|
|
862
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
863
|
-
}
|
|
864
|
-
: {
|
|
865
|
-
status: "PASSED",
|
|
866
|
-
startTime,
|
|
867
|
-
endTime,
|
|
868
|
-
},
|
|
869
|
-
info: info,
|
|
870
|
-
});
|
|
984
|
+
_commandFinally(state, this);
|
|
871
985
|
}
|
|
872
986
|
}
|
|
873
987
|
async hover(selectors, _params, options = {}, world = null) {
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
988
|
+
const state = {
|
|
989
|
+
selectors,
|
|
990
|
+
_params,
|
|
991
|
+
options,
|
|
992
|
+
world,
|
|
993
|
+
type: Types.HOVER,
|
|
994
|
+
text: `Hover element`,
|
|
995
|
+
operation: "hover",
|
|
996
|
+
log: "***** hover " + selectors.element_name + " *****\n",
|
|
997
|
+
};
|
|
883
998
|
try {
|
|
884
|
-
|
|
885
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
999
|
+
await _preCommand(state, this);
|
|
886
1000
|
try {
|
|
887
|
-
await
|
|
888
|
-
await element.hover();
|
|
1001
|
+
await state.element.hover();
|
|
889
1002
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
890
1003
|
}
|
|
891
1004
|
catch (e) {
|
|
892
1005
|
//await this.closeUnexpectedPopups();
|
|
893
|
-
info.log += "hover failed, will try again" + "\n";
|
|
894
|
-
element = await this._locate(selectors, info, _params);
|
|
895
|
-
await element.hover({ timeout: 10000 });
|
|
1006
|
+
state.info.log += "hover failed, will try again" + "\n";
|
|
1007
|
+
state.element = await this._locate(selectors, state.info, _params);
|
|
1008
|
+
await state.element.hover({ timeout: 10000 });
|
|
896
1009
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
897
1010
|
}
|
|
898
1011
|
await this.waitForPageLoad();
|
|
899
|
-
return info;
|
|
1012
|
+
return state.info;
|
|
900
1013
|
}
|
|
901
1014
|
catch (e) {
|
|
902
|
-
|
|
903
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
904
|
-
info.screenshotPath = screenshotPath;
|
|
905
|
-
Object.assign(e, { info: info });
|
|
906
|
-
error = e;
|
|
907
|
-
throw e;
|
|
1015
|
+
await _commandError(state, e, this);
|
|
908
1016
|
}
|
|
909
1017
|
finally {
|
|
910
|
-
|
|
911
|
-
this._reportToWorld(world, {
|
|
912
|
-
element_name: selectors.element_name,
|
|
913
|
-
type: Types.HOVER,
|
|
914
|
-
text: `Hover element`,
|
|
915
|
-
screenshotId,
|
|
916
|
-
result: error
|
|
917
|
-
? {
|
|
918
|
-
status: "FAILED",
|
|
919
|
-
startTime,
|
|
920
|
-
endTime,
|
|
921
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
922
|
-
}
|
|
923
|
-
: {
|
|
924
|
-
status: "PASSED",
|
|
925
|
-
startTime,
|
|
926
|
-
endTime,
|
|
927
|
-
},
|
|
928
|
-
info: info,
|
|
929
|
-
});
|
|
1018
|
+
_commandFinally(state, this);
|
|
930
1019
|
}
|
|
931
1020
|
}
|
|
932
1021
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
933
|
-
this._validateSelectors(selectors);
|
|
934
1022
|
if (!values) {
|
|
935
1023
|
throw new Error("values is null");
|
|
936
1024
|
}
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1025
|
+
const state = {
|
|
1026
|
+
selectors,
|
|
1027
|
+
_params,
|
|
1028
|
+
options,
|
|
1029
|
+
world,
|
|
1030
|
+
value: values.toString(),
|
|
1031
|
+
type: Types.SELECT,
|
|
1032
|
+
text: `Select option: ${values}`,
|
|
1033
|
+
operation: "selectOption",
|
|
1034
|
+
log: "***** select option " + selectors.element_name + " *****\n",
|
|
1035
|
+
};
|
|
945
1036
|
try {
|
|
946
|
-
|
|
947
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1037
|
+
await _preCommand(state, this);
|
|
948
1038
|
try {
|
|
949
|
-
await
|
|
950
|
-
await element.selectOption(values);
|
|
1039
|
+
await state.element.selectOption(values);
|
|
951
1040
|
}
|
|
952
1041
|
catch (e) {
|
|
953
1042
|
//await this.closeUnexpectedPopups();
|
|
954
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
955
|
-
await element.selectOption(values, { timeout: 10000, force: true });
|
|
1043
|
+
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1044
|
+
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
956
1045
|
}
|
|
957
1046
|
await this.waitForPageLoad();
|
|
958
|
-
return info;
|
|
1047
|
+
return state.info;
|
|
959
1048
|
}
|
|
960
1049
|
catch (e) {
|
|
961
|
-
|
|
962
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
963
|
-
info.screenshotPath = screenshotPath;
|
|
964
|
-
Object.assign(e, { info: info });
|
|
965
|
-
this.logger.info("click failed, will try next selector");
|
|
966
|
-
error = e;
|
|
967
|
-
throw e;
|
|
1050
|
+
await _commandError(state, e, this);
|
|
968
1051
|
}
|
|
969
1052
|
finally {
|
|
970
|
-
|
|
971
|
-
this._reportToWorld(world, {
|
|
972
|
-
element_name: selectors.element_name,
|
|
973
|
-
type: Types.SELECT,
|
|
974
|
-
text: `Select option: ${values}`,
|
|
975
|
-
value: values.toString(),
|
|
976
|
-
screenshotId,
|
|
977
|
-
result: error
|
|
978
|
-
? {
|
|
979
|
-
status: "FAILED",
|
|
980
|
-
startTime,
|
|
981
|
-
endTime,
|
|
982
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
983
|
-
}
|
|
984
|
-
: {
|
|
985
|
-
status: "PASSED",
|
|
986
|
-
startTime,
|
|
987
|
-
endTime,
|
|
988
|
-
},
|
|
989
|
-
info: info,
|
|
990
|
-
});
|
|
1053
|
+
_commandFinally(state, this);
|
|
991
1054
|
}
|
|
992
1055
|
}
|
|
993
1056
|
async type(_value, _params = null, options = {}, world = null) {
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1057
|
+
const state = {
|
|
1058
|
+
value: _value,
|
|
1059
|
+
_params,
|
|
1060
|
+
options,
|
|
1061
|
+
world,
|
|
1062
|
+
locate: false,
|
|
1063
|
+
scroll: false,
|
|
1064
|
+
highlight: false,
|
|
1065
|
+
type: Types.TYPE_PRESS,
|
|
1066
|
+
text: `Type value: ${_value}`,
|
|
1067
|
+
operation: "type",
|
|
1068
|
+
log: "",
|
|
1069
|
+
};
|
|
1003
1070
|
try {
|
|
1004
|
-
|
|
1005
|
-
const valueSegment =
|
|
1071
|
+
await _preCommand(state, this);
|
|
1072
|
+
const valueSegment = state.value.split("&&");
|
|
1006
1073
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1007
1074
|
if (i > 0) {
|
|
1008
1075
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1022,134 +1089,76 @@ class StableBrowser {
|
|
|
1022
1089
|
await this.page.keyboard.type(value);
|
|
1023
1090
|
}
|
|
1024
1091
|
}
|
|
1025
|
-
return info;
|
|
1092
|
+
return state.info;
|
|
1026
1093
|
}
|
|
1027
1094
|
catch (e) {
|
|
1028
|
-
|
|
1029
|
-
this.logger.error("type failed " + info.log);
|
|
1030
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1031
|
-
info.screenshotPath = screenshotPath;
|
|
1032
|
-
Object.assign(e, { info: info });
|
|
1033
|
-
error = e;
|
|
1034
|
-
throw e;
|
|
1095
|
+
await _commandError(state, e, this);
|
|
1035
1096
|
}
|
|
1036
1097
|
finally {
|
|
1037
|
-
|
|
1038
|
-
this._reportToWorld(world, {
|
|
1039
|
-
type: Types.TYPE_PRESS,
|
|
1040
|
-
screenshotId,
|
|
1041
|
-
value: _value,
|
|
1042
|
-
text: `type value: ${_value}`,
|
|
1043
|
-
result: error
|
|
1044
|
-
? {
|
|
1045
|
-
status: "FAILED",
|
|
1046
|
-
startTime,
|
|
1047
|
-
endTime,
|
|
1048
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1049
|
-
}
|
|
1050
|
-
: {
|
|
1051
|
-
status: "PASSED",
|
|
1052
|
-
startTime,
|
|
1053
|
-
endTime,
|
|
1054
|
-
},
|
|
1055
|
-
info: info,
|
|
1056
|
-
});
|
|
1098
|
+
_commandFinally(state, this);
|
|
1057
1099
|
}
|
|
1058
1100
|
}
|
|
1059
1101
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
let screenshotPath = null;
|
|
1102
|
+
const state = {
|
|
1103
|
+
selectors,
|
|
1104
|
+
_params,
|
|
1105
|
+
value,
|
|
1106
|
+
options,
|
|
1107
|
+
world,
|
|
1108
|
+
type: Types.SET_INPUT,
|
|
1109
|
+
text: `Set input value`,
|
|
1110
|
+
operation: "setInputValue",
|
|
1111
|
+
log: "***** set input value " + selectors.element_name + " *****\n",
|
|
1112
|
+
};
|
|
1072
1113
|
try {
|
|
1073
|
-
|
|
1074
|
-
let
|
|
1075
|
-
await this.scrollIfNeeded(element, info);
|
|
1076
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1077
|
-
await this._highlightElements(element);
|
|
1114
|
+
await _preCommand(state, this);
|
|
1115
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1078
1116
|
try {
|
|
1079
|
-
await element.evaluateHandle((el, value) => {
|
|
1117
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1080
1118
|
el.value = value;
|
|
1081
1119
|
}, value);
|
|
1082
1120
|
}
|
|
1083
1121
|
catch (error) {
|
|
1084
1122
|
this.logger.error("setInputValue failed, will try again");
|
|
1085
|
-
|
|
1086
|
-
info.
|
|
1087
|
-
|
|
1088
|
-
await element.evaluateHandle((el, value) => {
|
|
1123
|
+
await _screenshot(state, this);
|
|
1124
|
+
Object.assign(error, { info: state.info });
|
|
1125
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1089
1126
|
el.value = value;
|
|
1090
1127
|
});
|
|
1091
1128
|
}
|
|
1092
1129
|
}
|
|
1093
1130
|
catch (e) {
|
|
1094
|
-
|
|
1095
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1096
|
-
info.screenshotPath = screenshotPath;
|
|
1097
|
-
Object.assign(e, { info: info });
|
|
1098
|
-
error = e;
|
|
1099
|
-
throw e;
|
|
1131
|
+
await _commandError(state, e, this);
|
|
1100
1132
|
}
|
|
1101
1133
|
finally {
|
|
1102
|
-
|
|
1103
|
-
this._reportToWorld(world, {
|
|
1104
|
-
element_name: selectors.element_name,
|
|
1105
|
-
type: Types.SET_INPUT,
|
|
1106
|
-
text: `Set input value`,
|
|
1107
|
-
value: value,
|
|
1108
|
-
screenshotId,
|
|
1109
|
-
result: error
|
|
1110
|
-
? {
|
|
1111
|
-
status: "FAILED",
|
|
1112
|
-
startTime,
|
|
1113
|
-
endTime,
|
|
1114
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1115
|
-
}
|
|
1116
|
-
: {
|
|
1117
|
-
status: "PASSED",
|
|
1118
|
-
startTime,
|
|
1119
|
-
endTime,
|
|
1120
|
-
},
|
|
1121
|
-
info: info,
|
|
1122
|
-
});
|
|
1134
|
+
_commandFinally(state, this);
|
|
1123
1135
|
}
|
|
1124
1136
|
}
|
|
1125
1137
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1138
|
+
const state = {
|
|
1139
|
+
selectors,
|
|
1140
|
+
_params,
|
|
1141
|
+
value: await this._replaceWithLocalData(value, this),
|
|
1142
|
+
options,
|
|
1143
|
+
world,
|
|
1144
|
+
type: Types.SET_DATE_TIME,
|
|
1145
|
+
text: `Set date time value: ${value}`,
|
|
1146
|
+
operation: "setDateTime",
|
|
1147
|
+
log: "***** set date time value " + selectors.element_name + " *****\n",
|
|
1148
|
+
throwError: false,
|
|
1149
|
+
};
|
|
1136
1150
|
try {
|
|
1137
|
-
|
|
1138
|
-
let element = await this._locate(selectors, info, _params);
|
|
1139
|
-
//insert red border around the element
|
|
1140
|
-
await this.scrollIfNeeded(element, info);
|
|
1141
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1142
|
-
await this._highlightElements(element);
|
|
1151
|
+
await _preCommand(state, this);
|
|
1143
1152
|
try {
|
|
1144
|
-
await element.click();
|
|
1153
|
+
await state.element.click();
|
|
1145
1154
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1146
1155
|
if (format) {
|
|
1147
|
-
value = dayjs(value).format(format);
|
|
1148
|
-
await element.fill(value);
|
|
1156
|
+
state.value = dayjs(state.value).format(format);
|
|
1157
|
+
await state.element.fill(state.value);
|
|
1149
1158
|
}
|
|
1150
1159
|
else {
|
|
1151
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1152
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1160
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1161
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1153
1162
|
el.value = ""; // clear input
|
|
1154
1163
|
el.value = dateTimeValue;
|
|
1155
1164
|
}, dateTimeValue);
|
|
@@ -1162,20 +1171,19 @@ class StableBrowser {
|
|
|
1162
1171
|
}
|
|
1163
1172
|
catch (err) {
|
|
1164
1173
|
//await this.closeUnexpectedPopups();
|
|
1165
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1174
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1166
1175
|
this.logger.info("Trying again");
|
|
1167
|
-
|
|
1168
|
-
info.
|
|
1169
|
-
Object.assign(err, { info: info });
|
|
1176
|
+
await _screenshot(state, this);
|
|
1177
|
+
Object.assign(err, { info: state.info });
|
|
1170
1178
|
await element.click();
|
|
1171
1179
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1172
1180
|
if (format) {
|
|
1173
|
-
value = dayjs(value).format(format);
|
|
1174
|
-
await element.fill(value);
|
|
1181
|
+
state.value = dayjs(state.value).format(format);
|
|
1182
|
+
await state.element.fill(state.value);
|
|
1175
1183
|
}
|
|
1176
1184
|
else {
|
|
1177
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1178
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1185
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1186
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1179
1187
|
el.value = ""; // clear input
|
|
1180
1188
|
el.value = dateTimeValue;
|
|
1181
1189
|
}, dateTimeValue);
|
|
@@ -1188,61 +1196,39 @@ class StableBrowser {
|
|
|
1188
1196
|
}
|
|
1189
1197
|
}
|
|
1190
1198
|
catch (e) {
|
|
1191
|
-
|
|
1192
|
-
throw e;
|
|
1199
|
+
await _commandError(state, e, this);
|
|
1193
1200
|
}
|
|
1194
1201
|
finally {
|
|
1195
|
-
|
|
1196
|
-
this._reportToWorld(world, {
|
|
1197
|
-
element_name: selectors.element_name,
|
|
1198
|
-
type: Types.SET_DATE_TIME,
|
|
1199
|
-
screenshotId,
|
|
1200
|
-
value: value,
|
|
1201
|
-
text: `setDateTime input with value: ${value}`,
|
|
1202
|
-
result: error
|
|
1203
|
-
? {
|
|
1204
|
-
status: "FAILED",
|
|
1205
|
-
startTime,
|
|
1206
|
-
endTime,
|
|
1207
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1208
|
-
}
|
|
1209
|
-
: {
|
|
1210
|
-
status: "PASSED",
|
|
1211
|
-
startTime,
|
|
1212
|
-
endTime,
|
|
1213
|
-
},
|
|
1214
|
-
info: info,
|
|
1215
|
-
});
|
|
1202
|
+
_commandFinally(state, this);
|
|
1216
1203
|
}
|
|
1217
1204
|
}
|
|
1218
1205
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1219
1206
|
_value = unEscapeString(_value);
|
|
1220
|
-
this._validateSelectors(selectors);
|
|
1221
|
-
const startTime = Date.now();
|
|
1222
|
-
let error = null;
|
|
1223
|
-
let screenshotId = null;
|
|
1224
|
-
let screenshotPath = null;
|
|
1225
|
-
const info = {};
|
|
1226
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1227
|
-
info.operation = "clickType";
|
|
1228
|
-
info.selectors = selectors;
|
|
1229
1207
|
const newValue = await this._replaceWithLocalData(_value, world);
|
|
1208
|
+
const state = {
|
|
1209
|
+
selectors,
|
|
1210
|
+
_params,
|
|
1211
|
+
value: newValue,
|
|
1212
|
+
originalValue: _value,
|
|
1213
|
+
options,
|
|
1214
|
+
world,
|
|
1215
|
+
type: Types.FILL,
|
|
1216
|
+
text: `Click type input with value: ${_value}`,
|
|
1217
|
+
operation: "clickType",
|
|
1218
|
+
log: "***** clickType on " + selectors.element_name + " with value " + maskValue(_value) + "*****\n",
|
|
1219
|
+
};
|
|
1230
1220
|
if (newValue !== _value) {
|
|
1231
1221
|
//this.logger.info(_value + "=" + newValue);
|
|
1232
1222
|
_value = newValue;
|
|
1233
1223
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
//insert red border around the element
|
|
1238
|
-
await this.scrollIfNeeded(element, info);
|
|
1239
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1240
|
-
await this._highlightElements(element);
|
|
1224
|
+
try {
|
|
1225
|
+
await _preCommand(state, this);
|
|
1226
|
+
state.info.value = _value;
|
|
1241
1227
|
if (options === null || options === undefined || !options.press) {
|
|
1242
1228
|
try {
|
|
1243
|
-
let currentValue = await element.inputValue();
|
|
1229
|
+
let currentValue = await state.element.inputValue();
|
|
1244
1230
|
if (currentValue) {
|
|
1245
|
-
await element.fill("");
|
|
1231
|
+
await state.element.fill("");
|
|
1246
1232
|
}
|
|
1247
1233
|
}
|
|
1248
1234
|
catch (e) {
|
|
@@ -1251,22 +1237,22 @@ class StableBrowser {
|
|
|
1251
1237
|
}
|
|
1252
1238
|
if (options === null || options === undefined || options.press) {
|
|
1253
1239
|
try {
|
|
1254
|
-
await element.click({ timeout: 5000 });
|
|
1240
|
+
await state.element.click({ timeout: 5000 });
|
|
1255
1241
|
}
|
|
1256
1242
|
catch (e) {
|
|
1257
|
-
await element.dispatchEvent("click");
|
|
1243
|
+
await state.element.dispatchEvent("click");
|
|
1258
1244
|
}
|
|
1259
1245
|
}
|
|
1260
1246
|
else {
|
|
1261
1247
|
try {
|
|
1262
|
-
await element.focus();
|
|
1248
|
+
await state.element.focus();
|
|
1263
1249
|
}
|
|
1264
1250
|
catch (e) {
|
|
1265
|
-
await element.dispatchEvent("focus");
|
|
1251
|
+
await state.element.dispatchEvent("focus");
|
|
1266
1252
|
}
|
|
1267
1253
|
}
|
|
1268
1254
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1269
|
-
const valueSegment =
|
|
1255
|
+
const valueSegment = state.value.split("&&");
|
|
1270
1256
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1271
1257
|
if (i > 0) {
|
|
1272
1258
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1286,13 +1272,14 @@ class StableBrowser {
|
|
|
1286
1272
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1287
1273
|
}
|
|
1288
1274
|
}
|
|
1275
|
+
await _screenshot(state, this);
|
|
1289
1276
|
if (enter === true) {
|
|
1290
1277
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1291
1278
|
await this.page.keyboard.press("Enter");
|
|
1292
1279
|
await this.waitForPageLoad();
|
|
1293
1280
|
}
|
|
1294
1281
|
else if (enter === false) {
|
|
1295
|
-
await element.dispatchEvent("change");
|
|
1282
|
+
await state.element.dispatchEvent("change");
|
|
1296
1283
|
//await this.page.keyboard.press("Tab");
|
|
1297
1284
|
}
|
|
1298
1285
|
else {
|
|
@@ -1301,104 +1288,50 @@ class StableBrowser {
|
|
|
1301
1288
|
await this.waitForPageLoad();
|
|
1302
1289
|
}
|
|
1303
1290
|
}
|
|
1304
|
-
return info;
|
|
1291
|
+
return state.info;
|
|
1305
1292
|
}
|
|
1306
1293
|
catch (e) {
|
|
1307
|
-
|
|
1308
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1309
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1310
|
-
info.screenshotPath = screenshotPath;
|
|
1311
|
-
Object.assign(e, { info: info });
|
|
1312
|
-
error = e;
|
|
1313
|
-
throw e;
|
|
1294
|
+
await _commandError(state, e, this);
|
|
1314
1295
|
}
|
|
1315
1296
|
finally {
|
|
1316
|
-
|
|
1317
|
-
this._reportToWorld(world, {
|
|
1318
|
-
element_name: selectors.element_name,
|
|
1319
|
-
type: Types.FILL,
|
|
1320
|
-
screenshotId,
|
|
1321
|
-
value: _value,
|
|
1322
|
-
text: `clickType input with value: ${_value}`,
|
|
1323
|
-
result: error
|
|
1324
|
-
? {
|
|
1325
|
-
status: "FAILED",
|
|
1326
|
-
startTime,
|
|
1327
|
-
endTime,
|
|
1328
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1329
|
-
}
|
|
1330
|
-
: {
|
|
1331
|
-
status: "PASSED",
|
|
1332
|
-
startTime,
|
|
1333
|
-
endTime,
|
|
1334
|
-
},
|
|
1335
|
-
info: info,
|
|
1336
|
-
});
|
|
1297
|
+
_commandFinally(state, this);
|
|
1337
1298
|
}
|
|
1338
1299
|
}
|
|
1339
1300
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1301
|
+
const state = {
|
|
1302
|
+
selectors,
|
|
1303
|
+
_params,
|
|
1304
|
+
value: unEscapeString(value),
|
|
1305
|
+
options,
|
|
1306
|
+
world,
|
|
1307
|
+
type: Types.FILL,
|
|
1308
|
+
text: `Fill input with value: ${value}`,
|
|
1309
|
+
operation: "fill",
|
|
1310
|
+
log: "***** fill on " + selectors.element_name + " with value " + value + "*****\n",
|
|
1311
|
+
};
|
|
1351
1312
|
try {
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
await
|
|
1355
|
-
await element.fill(value);
|
|
1356
|
-
await element.dispatchEvent("change");
|
|
1313
|
+
await _preCommand(state, this);
|
|
1314
|
+
await state.element.fill(value);
|
|
1315
|
+
await state.element.dispatchEvent("change");
|
|
1357
1316
|
if (enter) {
|
|
1358
1317
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1359
1318
|
await this.page.keyboard.press("Enter");
|
|
1360
1319
|
}
|
|
1361
1320
|
await this.waitForPageLoad();
|
|
1362
|
-
return info;
|
|
1321
|
+
return state.info;
|
|
1363
1322
|
}
|
|
1364
1323
|
catch (e) {
|
|
1365
|
-
|
|
1366
|
-
this.logger.error("fill failed " + info.log);
|
|
1367
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1368
|
-
info.screenshotPath = screenshotPath;
|
|
1369
|
-
Object.assign(e, { info: info });
|
|
1370
|
-
error = e;
|
|
1371
|
-
throw e;
|
|
1324
|
+
await _commandError(state, e, this);
|
|
1372
1325
|
}
|
|
1373
1326
|
finally {
|
|
1374
|
-
|
|
1375
|
-
this._reportToWorld(world, {
|
|
1376
|
-
element_name: selectors.element_name,
|
|
1377
|
-
type: Types.FILL,
|
|
1378
|
-
screenshotId,
|
|
1379
|
-
value,
|
|
1380
|
-
text: `Fill input with value: ${value}`,
|
|
1381
|
-
result: error
|
|
1382
|
-
? {
|
|
1383
|
-
status: "FAILED",
|
|
1384
|
-
startTime,
|
|
1385
|
-
endTime,
|
|
1386
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1387
|
-
}
|
|
1388
|
-
: {
|
|
1389
|
-
status: "PASSED",
|
|
1390
|
-
startTime,
|
|
1391
|
-
endTime,
|
|
1392
|
-
},
|
|
1393
|
-
info: info,
|
|
1394
|
-
});
|
|
1327
|
+
_commandFinally(state, this);
|
|
1395
1328
|
}
|
|
1396
1329
|
}
|
|
1397
1330
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1398
1331
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1399
1332
|
}
|
|
1400
1333
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1401
|
-
|
|
1334
|
+
_validateSelectors(selectors);
|
|
1402
1335
|
let screenshotId = null;
|
|
1403
1336
|
let screenshotPath = null;
|
|
1404
1337
|
if (!info.log) {
|
|
@@ -1442,166 +1375,124 @@ class StableBrowser {
|
|
|
1442
1375
|
}
|
|
1443
1376
|
}
|
|
1444
1377
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1445
|
-
var _a;
|
|
1446
|
-
this._validateSelectors(selectors);
|
|
1447
1378
|
if (!pattern) {
|
|
1448
1379
|
throw new Error("pattern is null");
|
|
1449
1380
|
}
|
|
1450
1381
|
if (!text) {
|
|
1451
1382
|
throw new Error("text is null");
|
|
1452
1383
|
}
|
|
1384
|
+
const state = {
|
|
1385
|
+
selectors,
|
|
1386
|
+
_params,
|
|
1387
|
+
pattern,
|
|
1388
|
+
value: pattern,
|
|
1389
|
+
options,
|
|
1390
|
+
world,
|
|
1391
|
+
locate: false,
|
|
1392
|
+
scroll: false,
|
|
1393
|
+
screenshot: false,
|
|
1394
|
+
highlight: false,
|
|
1395
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1396
|
+
text: `Verify element contains pattern: ${pattern}`,
|
|
1397
|
+
operation: "containsPattern",
|
|
1398
|
+
log: "***** verify element " + selectors.element_name + " contains pattern " + pattern + " *****\n",
|
|
1399
|
+
};
|
|
1453
1400
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1454
1401
|
if (newValue !== text) {
|
|
1455
1402
|
this.logger.info(text + "=" + newValue);
|
|
1456
1403
|
text = newValue;
|
|
1457
1404
|
}
|
|
1458
|
-
const startTime = Date.now();
|
|
1459
|
-
let error = null;
|
|
1460
|
-
let screenshotId = null;
|
|
1461
|
-
let screenshotPath = null;
|
|
1462
|
-
const info = {};
|
|
1463
|
-
info.log =
|
|
1464
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1465
|
-
info.operation = "containsPattern";
|
|
1466
|
-
info.selectors = selectors;
|
|
1467
|
-
info.value = text;
|
|
1468
|
-
info.pattern = pattern;
|
|
1469
1405
|
let foundObj = null;
|
|
1470
1406
|
try {
|
|
1471
|
-
|
|
1407
|
+
await _preCommand(state, this);
|
|
1408
|
+
state.info.pattern = pattern;
|
|
1409
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1472
1410
|
if (foundObj && foundObj.element) {
|
|
1473
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1411
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1474
1412
|
}
|
|
1475
|
-
|
|
1413
|
+
await _screenshot(state, this);
|
|
1476
1414
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1477
1415
|
pattern = pattern.replace("{text}", escapedText);
|
|
1478
1416
|
let regex = new RegExp(pattern, "im");
|
|
1479
|
-
if (!regex.test(foundObj
|
|
1480
|
-
info.foundText = foundObj
|
|
1417
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1418
|
+
state.info.foundText = foundObj?.text;
|
|
1481
1419
|
throw new Error("element doesn't contain text " + text);
|
|
1482
1420
|
}
|
|
1483
|
-
return info;
|
|
1421
|
+
return state.info;
|
|
1484
1422
|
}
|
|
1485
1423
|
catch (e) {
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1489
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1490
|
-
info.screenshotPath = screenshotPath;
|
|
1491
|
-
Object.assign(e, { info: info });
|
|
1492
|
-
error = e;
|
|
1493
|
-
throw e;
|
|
1424
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1425
|
+
await _commandError(state, e, this);
|
|
1494
1426
|
}
|
|
1495
1427
|
finally {
|
|
1496
|
-
|
|
1497
|
-
this._reportToWorld(world, {
|
|
1498
|
-
element_name: selectors.element_name,
|
|
1499
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1500
|
-
value: pattern,
|
|
1501
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1502
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1503
|
-
result: error
|
|
1504
|
-
? {
|
|
1505
|
-
status: "FAILED",
|
|
1506
|
-
startTime,
|
|
1507
|
-
endTime,
|
|
1508
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1509
|
-
}
|
|
1510
|
-
: {
|
|
1511
|
-
status: "PASSED",
|
|
1512
|
-
startTime,
|
|
1513
|
-
endTime,
|
|
1514
|
-
},
|
|
1515
|
-
info: info,
|
|
1516
|
-
});
|
|
1428
|
+
_commandFinally(state, this);
|
|
1517
1429
|
}
|
|
1518
1430
|
}
|
|
1519
1431
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1432
|
+
const state = {
|
|
1433
|
+
selectors,
|
|
1434
|
+
_params,
|
|
1435
|
+
value: text,
|
|
1436
|
+
options,
|
|
1437
|
+
world,
|
|
1438
|
+
locate: false,
|
|
1439
|
+
scroll: false,
|
|
1440
|
+
screenshot: false,
|
|
1441
|
+
highlight: false,
|
|
1442
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1443
|
+
text: `Verify element contains text: ${text}`,
|
|
1444
|
+
operation: "containsText",
|
|
1445
|
+
log: "***** verify element " + selectors.element_name + " contains text " + text + " *****\n",
|
|
1446
|
+
};
|
|
1523
1447
|
if (!text) {
|
|
1524
1448
|
throw new Error("text is null");
|
|
1525
1449
|
}
|
|
1526
|
-
|
|
1527
|
-
let error = null;
|
|
1528
|
-
let screenshotId = null;
|
|
1529
|
-
let screenshotPath = null;
|
|
1530
|
-
const info = {};
|
|
1531
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1532
|
-
info.operation = "containsText";
|
|
1533
|
-
info.selectors = selectors;
|
|
1450
|
+
text = unEscapeString(text);
|
|
1534
1451
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1535
1452
|
if (newValue !== text) {
|
|
1536
1453
|
this.logger.info(text + "=" + newValue);
|
|
1537
1454
|
text = newValue;
|
|
1538
1455
|
}
|
|
1539
|
-
info.value = text;
|
|
1540
1456
|
let foundObj = null;
|
|
1541
1457
|
try {
|
|
1542
|
-
|
|
1458
|
+
await _preCommand(state, this);
|
|
1459
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1543
1460
|
if (foundObj && foundObj.element) {
|
|
1544
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1461
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1545
1462
|
}
|
|
1546
|
-
|
|
1463
|
+
await _screenshot(state, this);
|
|
1547
1464
|
const dateAlternatives = findDateAlternatives(text);
|
|
1548
1465
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1549
1466
|
if (dateAlternatives.date) {
|
|
1550
1467
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1551
|
-
if (
|
|
1552
|
-
|
|
1553
|
-
return info;
|
|
1468
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1469
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1470
|
+
return state.info;
|
|
1554
1471
|
}
|
|
1555
1472
|
}
|
|
1556
1473
|
throw new Error("element doesn't contain text " + text);
|
|
1557
1474
|
}
|
|
1558
1475
|
else if (numberAlternatives.number) {
|
|
1559
1476
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1560
|
-
if (
|
|
1561
|
-
|
|
1562
|
-
return info;
|
|
1477
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1478
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1479
|
+
return state.info;
|
|
1563
1480
|
}
|
|
1564
1481
|
}
|
|
1565
1482
|
throw new Error("element doesn't contain text " + text);
|
|
1566
1483
|
}
|
|
1567
|
-
else if (!
|
|
1568
|
-
info.foundText = foundObj
|
|
1569
|
-
info.value = foundObj
|
|
1484
|
+
else if (!foundObj?.text.includes(text) && !foundObj?.value?.includes(text)) {
|
|
1485
|
+
state.info.foundText = foundObj?.text;
|
|
1486
|
+
state.info.value = foundObj?.value;
|
|
1570
1487
|
throw new Error("element doesn't contain text " + text);
|
|
1571
1488
|
}
|
|
1572
|
-
return info;
|
|
1489
|
+
return state.info;
|
|
1573
1490
|
}
|
|
1574
1491
|
catch (e) {
|
|
1575
|
-
|
|
1576
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1577
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1578
|
-
info.screenshotPath = screenshotPath;
|
|
1579
|
-
Object.assign(e, { info: info });
|
|
1580
|
-
error = e;
|
|
1581
|
-
throw e;
|
|
1492
|
+
await _commandError(state, e, this);
|
|
1582
1493
|
}
|
|
1583
1494
|
finally {
|
|
1584
|
-
|
|
1585
|
-
this._reportToWorld(world, {
|
|
1586
|
-
element_name: selectors.element_name,
|
|
1587
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1588
|
-
text: `Verify element contains text: ${text}`,
|
|
1589
|
-
value: text,
|
|
1590
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1591
|
-
result: error
|
|
1592
|
-
? {
|
|
1593
|
-
status: "FAILED",
|
|
1594
|
-
startTime,
|
|
1595
|
-
endTime,
|
|
1596
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1597
|
-
}
|
|
1598
|
-
: {
|
|
1599
|
-
status: "PASSED",
|
|
1600
|
-
startTime,
|
|
1601
|
-
endTime,
|
|
1602
|
-
},
|
|
1603
|
-
info: info,
|
|
1604
|
-
});
|
|
1495
|
+
_commandFinally(state, this);
|
|
1605
1496
|
}
|
|
1606
1497
|
}
|
|
1607
1498
|
_getDataFile(world = null) {
|
|
@@ -1830,7 +1721,6 @@ class StableBrowser {
|
|
|
1830
1721
|
}
|
|
1831
1722
|
async takeScreenshot(screenshotPath) {
|
|
1832
1723
|
const playContext = this.context.playContext;
|
|
1833
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1834
1724
|
// Using CDP to capture the screenshot
|
|
1835
1725
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1836
1726
|
document.body.scrollWidth,
|
|
@@ -1840,164 +1730,109 @@ class StableBrowser {
|
|
|
1840
1730
|
document.body.clientWidth,
|
|
1841
1731
|
document.documentElement.clientWidth,
|
|
1842
1732
|
])));
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
await client.detach();
|
|
1733
|
+
let screenshotBuffer = null;
|
|
1734
|
+
if (this.context.browserName === "chromium") {
|
|
1735
|
+
const client = await playContext.newCDPSession(this.page);
|
|
1736
|
+
const { data } = await client.send("Page.captureScreenshot", {
|
|
1737
|
+
format: "png",
|
|
1738
|
+
// clip: {
|
|
1739
|
+
// x: 0,
|
|
1740
|
+
// y: 0,
|
|
1741
|
+
// width: viewportWidth,
|
|
1742
|
+
// height: viewportHeight,
|
|
1743
|
+
// scale: 1,
|
|
1744
|
+
// },
|
|
1745
|
+
});
|
|
1746
|
+
await client.detach();
|
|
1747
|
+
if (!screenshotPath) {
|
|
1748
|
+
return data;
|
|
1749
|
+
}
|
|
1750
|
+
screenshotBuffer = Buffer.from(data, "base64");
|
|
1751
|
+
}
|
|
1752
|
+
else {
|
|
1753
|
+
screenshotBuffer = await this.page.screenshot();
|
|
1754
|
+
}
|
|
1755
|
+
let image = await Jimp.read(screenshotBuffer);
|
|
1756
|
+
// Get the image dimensions
|
|
1757
|
+
const { width, height } = image.bitmap;
|
|
1758
|
+
const resizeRatio = viewportWidth / width;
|
|
1759
|
+
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1760
|
+
if (width > viewportWidth) {
|
|
1761
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1762
|
+
await image.write(screenshotPath);
|
|
1763
|
+
}
|
|
1764
|
+
else {
|
|
1765
|
+
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1766
|
+
}
|
|
1878
1767
|
}
|
|
1879
1768
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1769
|
+
const state = {
|
|
1770
|
+
selectors,
|
|
1771
|
+
_params,
|
|
1772
|
+
options,
|
|
1773
|
+
world,
|
|
1774
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1775
|
+
text: `Verify element exists in page`,
|
|
1776
|
+
operation: "verifyElementExistInPage",
|
|
1777
|
+
log: "***** verify element " + selectors.element_name + " exists in page *****\n",
|
|
1778
|
+
};
|
|
1885
1779
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1886
|
-
const info = {};
|
|
1887
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1888
|
-
info.operation = "verify";
|
|
1889
|
-
info.selectors = selectors;
|
|
1890
1780
|
try {
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
}
|
|
1895
|
-
await this._highlightElements(element);
|
|
1896
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1897
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1898
|
-
return info;
|
|
1781
|
+
await _preCommand(state, this);
|
|
1782
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1783
|
+
return state.info;
|
|
1899
1784
|
}
|
|
1900
1785
|
catch (e) {
|
|
1901
|
-
|
|
1902
|
-
this.logger.error("verify failed " + info.log);
|
|
1903
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1904
|
-
info.screenshotPath = screenshotPath;
|
|
1905
|
-
Object.assign(e, { info: info });
|
|
1906
|
-
error = e;
|
|
1907
|
-
throw e;
|
|
1786
|
+
await _commandError(state, e, this);
|
|
1908
1787
|
}
|
|
1909
1788
|
finally {
|
|
1910
|
-
|
|
1911
|
-
this._reportToWorld(world, {
|
|
1912
|
-
element_name: selectors.element_name,
|
|
1913
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1914
|
-
text: "Verify element exists in page",
|
|
1915
|
-
screenshotId,
|
|
1916
|
-
result: error
|
|
1917
|
-
? {
|
|
1918
|
-
status: "FAILED",
|
|
1919
|
-
startTime,
|
|
1920
|
-
endTime,
|
|
1921
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1922
|
-
}
|
|
1923
|
-
: {
|
|
1924
|
-
status: "PASSED",
|
|
1925
|
-
startTime,
|
|
1926
|
-
endTime,
|
|
1927
|
-
},
|
|
1928
|
-
info: info,
|
|
1929
|
-
});
|
|
1789
|
+
_commandFinally(state, this);
|
|
1930
1790
|
}
|
|
1931
1791
|
}
|
|
1932
1792
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1793
|
+
const state = {
|
|
1794
|
+
selectors,
|
|
1795
|
+
_params,
|
|
1796
|
+
attribute,
|
|
1797
|
+
variable,
|
|
1798
|
+
options,
|
|
1799
|
+
world,
|
|
1800
|
+
type: Types.EXTRACT,
|
|
1801
|
+
text: `Extract attribute from element`,
|
|
1802
|
+
operation: "extractAttribute",
|
|
1803
|
+
log: "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n",
|
|
1804
|
+
};
|
|
1938
1805
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1939
|
-
const info = {};
|
|
1940
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1941
|
-
info.operation = "extract";
|
|
1942
|
-
info.selectors = selectors;
|
|
1943
1806
|
try {
|
|
1944
|
-
|
|
1945
|
-
await this._highlightElements(element);
|
|
1946
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1807
|
+
await _preCommand(state, this);
|
|
1947
1808
|
switch (attribute) {
|
|
1948
1809
|
case "inner_text":
|
|
1949
|
-
|
|
1810
|
+
state.value = await state.element.innerText();
|
|
1950
1811
|
break;
|
|
1951
1812
|
case "href":
|
|
1952
|
-
|
|
1813
|
+
state.value = await state.element.getAttribute("href");
|
|
1953
1814
|
break;
|
|
1954
1815
|
case "value":
|
|
1955
|
-
|
|
1816
|
+
state.value = await state.element.inputValue();
|
|
1956
1817
|
break;
|
|
1957
1818
|
default:
|
|
1958
|
-
|
|
1819
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1959
1820
|
break;
|
|
1960
1821
|
}
|
|
1961
|
-
|
|
1822
|
+
state.info.value = state.value;
|
|
1823
|
+
this[variable] = state.value;
|
|
1962
1824
|
if (world) {
|
|
1963
|
-
world[variable] =
|
|
1825
|
+
world[variable] = state.value;
|
|
1964
1826
|
}
|
|
1965
|
-
this.setTestData({ [variable]:
|
|
1966
|
-
this.logger.info("set test data: " + variable + "=" +
|
|
1967
|
-
return info;
|
|
1827
|
+
this.setTestData({ [variable]: state.value }, world);
|
|
1828
|
+
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
1829
|
+
return state.info;
|
|
1968
1830
|
}
|
|
1969
1831
|
catch (e) {
|
|
1970
|
-
|
|
1971
|
-
this.logger.error("extract failed " + info.log);
|
|
1972
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1973
|
-
info.screenshotPath = screenshotPath;
|
|
1974
|
-
Object.assign(e, { info: info });
|
|
1975
|
-
error = e;
|
|
1976
|
-
throw e;
|
|
1832
|
+
await _commandError(state, e, this);
|
|
1977
1833
|
}
|
|
1978
1834
|
finally {
|
|
1979
|
-
|
|
1980
|
-
this._reportToWorld(world, {
|
|
1981
|
-
element_name: selectors.element_name,
|
|
1982
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
1983
|
-
variable: variable,
|
|
1984
|
-
value: info.value,
|
|
1985
|
-
text: "Extract attribute from element",
|
|
1986
|
-
screenshotId,
|
|
1987
|
-
result: error
|
|
1988
|
-
? {
|
|
1989
|
-
status: "FAILED",
|
|
1990
|
-
startTime,
|
|
1991
|
-
endTime,
|
|
1992
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1993
|
-
}
|
|
1994
|
-
: {
|
|
1995
|
-
status: "PASSED",
|
|
1996
|
-
startTime,
|
|
1997
|
-
endTime,
|
|
1998
|
-
},
|
|
1999
|
-
info: info,
|
|
2000
|
-
});
|
|
1835
|
+
_commandFinally(state, this);
|
|
2001
1836
|
}
|
|
2002
1837
|
}
|
|
2003
1838
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2074,7 +1909,8 @@ class StableBrowser {
|
|
|
2074
1909
|
catch (e) {
|
|
2075
1910
|
errorCount++;
|
|
2076
1911
|
if (errorCount > 3) {
|
|
2077
|
-
throw e;
|
|
1912
|
+
// throw e;
|
|
1913
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
2078
1914
|
}
|
|
2079
1915
|
// ignore
|
|
2080
1916
|
}
|
|
@@ -2185,7 +2021,8 @@ class StableBrowser {
|
|
|
2185
2021
|
info.screenshotPath = screenshotPath;
|
|
2186
2022
|
Object.assign(e, { info: info });
|
|
2187
2023
|
error = e;
|
|
2188
|
-
throw e;
|
|
2024
|
+
// throw e;
|
|
2025
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2189
2026
|
}
|
|
2190
2027
|
finally {
|
|
2191
2028
|
const endTime = Date.now();
|
|
@@ -2198,7 +2035,7 @@ class StableBrowser {
|
|
|
2198
2035
|
status: "FAILED",
|
|
2199
2036
|
startTime,
|
|
2200
2037
|
endTime,
|
|
2201
|
-
message: error
|
|
2038
|
+
message: error?.message,
|
|
2202
2039
|
}
|
|
2203
2040
|
: {
|
|
2204
2041
|
status: "PASSED",
|
|
@@ -2211,52 +2048,58 @@ class StableBrowser {
|
|
|
2211
2048
|
}
|
|
2212
2049
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2213
2050
|
text = unEscapeString(text);
|
|
2214
|
-
const
|
|
2051
|
+
const state = {
|
|
2052
|
+
text_search: text,
|
|
2053
|
+
options,
|
|
2054
|
+
world,
|
|
2055
|
+
locate: false,
|
|
2056
|
+
scroll: false,
|
|
2057
|
+
highlight: false,
|
|
2058
|
+
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2059
|
+
text: `Verify text exists in page`,
|
|
2060
|
+
operation: "verifyTextExistInPage",
|
|
2061
|
+
log: "***** verify text " + text + " exists in page *****\n",
|
|
2062
|
+
};
|
|
2215
2063
|
const timeout = this._getLoadTimeout(options);
|
|
2216
|
-
let error = null;
|
|
2217
|
-
let screenshotId = null;
|
|
2218
|
-
let screenshotPath = null;
|
|
2219
2064
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2220
|
-
const info = {};
|
|
2221
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2222
|
-
info.operation = "verifyTextExistInPage";
|
|
2223
2065
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2224
2066
|
if (newValue !== text) {
|
|
2225
2067
|
this.logger.info(text + "=" + newValue);
|
|
2226
2068
|
text = newValue;
|
|
2227
2069
|
}
|
|
2228
|
-
info.text = text;
|
|
2229
2070
|
let dateAlternatives = findDateAlternatives(text);
|
|
2230
2071
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2231
2072
|
try {
|
|
2073
|
+
await _preCommand(state, this);
|
|
2074
|
+
state.info.text = text;
|
|
2232
2075
|
while (true) {
|
|
2233
2076
|
const frames = this.page.frames();
|
|
2234
2077
|
let results = [];
|
|
2235
2078
|
for (let i = 0; i < frames.length; i++) {
|
|
2236
2079
|
if (dateAlternatives.date) {
|
|
2237
2080
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2238
|
-
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "
|
|
2081
|
+
const result = await this._locateElementByText(frames[i], dateAlternatives.dates[j], "*:not(script, style, head)", true, true, {});
|
|
2239
2082
|
result.frame = frames[i];
|
|
2240
2083
|
results.push(result);
|
|
2241
2084
|
}
|
|
2242
2085
|
}
|
|
2243
2086
|
else if (numberAlternatives.number) {
|
|
2244
2087
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2245
|
-
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "
|
|
2088
|
+
const result = await this._locateElementByText(frames[i], numberAlternatives.numbers[j], "*:not(script, style, head)", true, true, {});
|
|
2246
2089
|
result.frame = frames[i];
|
|
2247
2090
|
results.push(result);
|
|
2248
2091
|
}
|
|
2249
2092
|
}
|
|
2250
2093
|
else {
|
|
2251
|
-
const result = await this._locateElementByText(frames[i], text, "
|
|
2094
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2252
2095
|
result.frame = frames[i];
|
|
2253
2096
|
results.push(result);
|
|
2254
2097
|
}
|
|
2255
2098
|
}
|
|
2256
|
-
info.results = results;
|
|
2099
|
+
state.info.results = results;
|
|
2257
2100
|
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2258
2101
|
if (resultWithElementsFound.length === 0) {
|
|
2259
|
-
if (Date.now() - startTime > timeout) {
|
|
2102
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2260
2103
|
throw new Error(`Text ${text} not found in page`);
|
|
2261
2104
|
}
|
|
2262
2105
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2268,44 +2111,20 @@ class StableBrowser {
|
|
|
2268
2111
|
await this._highlightElements(frame, dataAttribute);
|
|
2269
2112
|
const element = await frame.$(dataAttribute);
|
|
2270
2113
|
if (element) {
|
|
2271
|
-
await this.scrollIfNeeded(element, info);
|
|
2114
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2272
2115
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2273
2116
|
}
|
|
2274
2117
|
}
|
|
2275
|
-
|
|
2276
|
-
return info;
|
|
2118
|
+
await _screenshot(state, this);
|
|
2119
|
+
return state.info;
|
|
2277
2120
|
}
|
|
2278
2121
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2279
2122
|
}
|
|
2280
2123
|
catch (e) {
|
|
2281
|
-
|
|
2282
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2283
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2284
|
-
info.screenshotPath = screenshotPath;
|
|
2285
|
-
Object.assign(e, { info: info });
|
|
2286
|
-
error = e;
|
|
2287
|
-
throw e;
|
|
2124
|
+
await _commandError(state, e, this);
|
|
2288
2125
|
}
|
|
2289
2126
|
finally {
|
|
2290
|
-
|
|
2291
|
-
this._reportToWorld(world, {
|
|
2292
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
2293
|
-
text: "Verify text exists in page",
|
|
2294
|
-
screenshotId,
|
|
2295
|
-
result: error
|
|
2296
|
-
? {
|
|
2297
|
-
status: "FAILED",
|
|
2298
|
-
startTime,
|
|
2299
|
-
endTime,
|
|
2300
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2301
|
-
}
|
|
2302
|
-
: {
|
|
2303
|
-
status: "PASSED",
|
|
2304
|
-
startTime,
|
|
2305
|
-
endTime,
|
|
2306
|
-
},
|
|
2307
|
-
info: info,
|
|
2308
|
-
});
|
|
2127
|
+
_commandFinally(state, this);
|
|
2309
2128
|
}
|
|
2310
2129
|
}
|
|
2311
2130
|
_getServerUrl() {
|
|
@@ -2368,7 +2187,8 @@ class StableBrowser {
|
|
|
2368
2187
|
info.screenshotPath = screenshotPath;
|
|
2369
2188
|
Object.assign(e, { info: info });
|
|
2370
2189
|
error = e;
|
|
2371
|
-
throw e;
|
|
2190
|
+
// throw e;
|
|
2191
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2372
2192
|
}
|
|
2373
2193
|
finally {
|
|
2374
2194
|
const endTime = Date.now();
|
|
@@ -2381,7 +2201,7 @@ class StableBrowser {
|
|
|
2381
2201
|
status: "FAILED",
|
|
2382
2202
|
startTime,
|
|
2383
2203
|
endTime,
|
|
2384
|
-
message: error
|
|
2204
|
+
message: error?.message,
|
|
2385
2205
|
}
|
|
2386
2206
|
: {
|
|
2387
2207
|
status: "PASSED",
|
|
@@ -2413,7 +2233,7 @@ class StableBrowser {
|
|
|
2413
2233
|
this.logger.info("Table data verified");
|
|
2414
2234
|
}
|
|
2415
2235
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2416
|
-
|
|
2236
|
+
_validateSelectors(selectors);
|
|
2417
2237
|
const startTime = Date.now();
|
|
2418
2238
|
let error = null;
|
|
2419
2239
|
let screenshotId = null;
|
|
@@ -2435,7 +2255,8 @@ class StableBrowser {
|
|
|
2435
2255
|
info.screenshotPath = screenshotPath;
|
|
2436
2256
|
Object.assign(e, { info: info });
|
|
2437
2257
|
error = e;
|
|
2438
|
-
throw e;
|
|
2258
|
+
// throw e;
|
|
2259
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2439
2260
|
}
|
|
2440
2261
|
finally {
|
|
2441
2262
|
const endTime = Date.now();
|
|
@@ -2449,7 +2270,7 @@ class StableBrowser {
|
|
|
2449
2270
|
status: "FAILED",
|
|
2450
2271
|
startTime,
|
|
2451
2272
|
endTime,
|
|
2452
|
-
message: error
|
|
2273
|
+
message: error?.message,
|
|
2453
2274
|
}
|
|
2454
2275
|
: {
|
|
2455
2276
|
status: "PASSED",
|
|
@@ -2461,7 +2282,7 @@ class StableBrowser {
|
|
|
2461
2282
|
}
|
|
2462
2283
|
}
|
|
2463
2284
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2464
|
-
|
|
2285
|
+
_validateSelectors(selectors);
|
|
2465
2286
|
if (!query) {
|
|
2466
2287
|
throw new Error("query is null");
|
|
2467
2288
|
}
|
|
@@ -2600,7 +2421,8 @@ class StableBrowser {
|
|
|
2600
2421
|
info.screenshotPath = screenshotPath;
|
|
2601
2422
|
Object.assign(e, { info: info });
|
|
2602
2423
|
error = e;
|
|
2603
|
-
throw e;
|
|
2424
|
+
// throw e;
|
|
2425
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2604
2426
|
}
|
|
2605
2427
|
finally {
|
|
2606
2428
|
const endTime = Date.now();
|
|
@@ -2614,7 +2436,7 @@ class StableBrowser {
|
|
|
2614
2436
|
status: "FAILED",
|
|
2615
2437
|
startTime,
|
|
2616
2438
|
endTime,
|
|
2617
|
-
message: error
|
|
2439
|
+
message: error?.message,
|
|
2618
2440
|
}
|
|
2619
2441
|
: {
|
|
2620
2442
|
status: "PASSED",
|
|
@@ -2626,27 +2448,7 @@ class StableBrowser {
|
|
|
2626
2448
|
}
|
|
2627
2449
|
}
|
|
2628
2450
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2629
|
-
|
|
2630
|
-
return value;
|
|
2631
|
-
}
|
|
2632
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2633
|
-
let regex = /{{(.*?)}}/g;
|
|
2634
|
-
let matches = value.match(regex);
|
|
2635
|
-
if (matches) {
|
|
2636
|
-
const testData = this.getTestData(world);
|
|
2637
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2638
|
-
let match = matches[i];
|
|
2639
|
-
let key = match.substring(2, match.length - 2);
|
|
2640
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2641
|
-
if (newValue !== null) {
|
|
2642
|
-
value = value.replace(match, newValue);
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2647
|
-
return await decrypt(value, null, totpWait);
|
|
2648
|
-
}
|
|
2649
|
-
return value;
|
|
2451
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2650
2452
|
}
|
|
2651
2453
|
_getLoadTimeout(options) {
|
|
2652
2454
|
let timeout = 15000;
|
|
@@ -2706,7 +2508,7 @@ class StableBrowser {
|
|
|
2706
2508
|
status: "FAILED",
|
|
2707
2509
|
startTime,
|
|
2708
2510
|
endTime,
|
|
2709
|
-
message: error
|
|
2511
|
+
message: error?.message,
|
|
2710
2512
|
}
|
|
2711
2513
|
: {
|
|
2712
2514
|
status: "PASSED",
|
|
@@ -2717,39 +2519,28 @@ class StableBrowser {
|
|
|
2717
2519
|
}
|
|
2718
2520
|
}
|
|
2719
2521
|
async closePage(options = {}, world = null) {
|
|
2720
|
-
const
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2522
|
+
const state = {
|
|
2523
|
+
options,
|
|
2524
|
+
world,
|
|
2525
|
+
locate: false,
|
|
2526
|
+
scroll: false,
|
|
2527
|
+
highlight: false,
|
|
2528
|
+
type: Types.CLOSE_PAGE,
|
|
2529
|
+
text: `Close page`,
|
|
2530
|
+
operation: "closePage",
|
|
2531
|
+
log: "***** close page *****\n",
|
|
2532
|
+
throwError: false,
|
|
2533
|
+
};
|
|
2725
2534
|
try {
|
|
2535
|
+
await _preCommand(state, this);
|
|
2726
2536
|
await this.page.close();
|
|
2727
2537
|
}
|
|
2728
2538
|
catch (e) {
|
|
2729
2539
|
console.log(".");
|
|
2540
|
+
await _commandError(state, e, this);
|
|
2730
2541
|
}
|
|
2731
2542
|
finally {
|
|
2732
|
-
|
|
2733
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2734
|
-
const endTime = Date.now();
|
|
2735
|
-
this._reportToWorld(world, {
|
|
2736
|
-
type: Types.CLOSE_PAGE,
|
|
2737
|
-
text: "close page",
|
|
2738
|
-
screenshotId,
|
|
2739
|
-
result: error
|
|
2740
|
-
? {
|
|
2741
|
-
status: "FAILED",
|
|
2742
|
-
startTime,
|
|
2743
|
-
endTime,
|
|
2744
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2745
|
-
}
|
|
2746
|
-
: {
|
|
2747
|
-
status: "PASSED",
|
|
2748
|
-
startTime,
|
|
2749
|
-
endTime,
|
|
2750
|
-
},
|
|
2751
|
-
info: info,
|
|
2752
|
-
});
|
|
2543
|
+
_commandFinally(state, this);
|
|
2753
2544
|
}
|
|
2754
2545
|
}
|
|
2755
2546
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
@@ -2769,6 +2560,7 @@ class StableBrowser {
|
|
|
2769
2560
|
}
|
|
2770
2561
|
catch (e) {
|
|
2771
2562
|
console.log(".");
|
|
2563
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2772
2564
|
}
|
|
2773
2565
|
finally {
|
|
2774
2566
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -2783,7 +2575,7 @@ class StableBrowser {
|
|
|
2783
2575
|
status: "FAILED",
|
|
2784
2576
|
startTime,
|
|
2785
2577
|
endTime,
|
|
2786
|
-
message: error
|
|
2578
|
+
message: error?.message,
|
|
2787
2579
|
}
|
|
2788
2580
|
: {
|
|
2789
2581
|
status: "PASSED",
|
|
@@ -2805,6 +2597,7 @@ class StableBrowser {
|
|
|
2805
2597
|
}
|
|
2806
2598
|
catch (e) {
|
|
2807
2599
|
console.log(".");
|
|
2600
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2808
2601
|
}
|
|
2809
2602
|
finally {
|
|
2810
2603
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -2819,7 +2612,7 @@ class StableBrowser {
|
|
|
2819
2612
|
status: "FAILED",
|
|
2820
2613
|
startTime,
|
|
2821
2614
|
endTime,
|
|
2822
|
-
message: error
|
|
2615
|
+
message: error?.message,
|
|
2823
2616
|
}
|
|
2824
2617
|
: {
|
|
2825
2618
|
status: "PASSED",
|