automation_model 1.0.457-dev → 1.0.457-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 +185 -7
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +21 -16
- package/lib/auto_page.js.map +1 -1
- package/lib/axe/axe.mini.js +12 -0
- package/lib/browser_manager.d.ts +5 -3
- package/lib/browser_manager.js +44 -19
- 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.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 +20 -4
- 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 -17
- package/lib/stable_browser.js +676 -801
- package/lib/stable_browser.js.map +1 -1
- package/lib/test_context.d.ts +2 -0
- package/lib/test_context.js +12 -11
- 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 +4 -5
package/lib/stable_browser.js
CHANGED
|
@@ -10,12 +10,14 @@ 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",
|
|
@@ -42,20 +44,26 @@ const Types = {
|
|
|
42
44
|
VERIFY_VISUAL: "verify_visual",
|
|
43
45
|
LOAD_DATA: "load_data",
|
|
44
46
|
SET_INPUT: "set_input",
|
|
47
|
+
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
45
48
|
};
|
|
46
49
|
export const apps = {};
|
|
47
50
|
class StableBrowser {
|
|
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";
|
|
48
61
|
constructor(browser, page, logger = null, context = null, world = null) {
|
|
49
62
|
this.browser = browser;
|
|
50
63
|
this.page = page;
|
|
51
64
|
this.logger = logger;
|
|
52
65
|
this.context = context;
|
|
53
66
|
this.world = world;
|
|
54
|
-
this.project_path = null;
|
|
55
|
-
this.webLogFile = null;
|
|
56
|
-
this.networkLogger = null;
|
|
57
|
-
this.configuration = null;
|
|
58
|
-
this.appName = "main";
|
|
59
67
|
if (!this.logger) {
|
|
60
68
|
this.logger = console;
|
|
61
69
|
}
|
|
@@ -80,11 +88,13 @@ class StableBrowser {
|
|
|
80
88
|
catch (e) {
|
|
81
89
|
this.logger.error("unable to read ai_config.json");
|
|
82
90
|
}
|
|
83
|
-
context.pageLoading = { status: false };
|
|
84
|
-
context.pages = [this.page];
|
|
85
91
|
const logFolder = path.join(this.project_path, "logs", "web");
|
|
86
92
|
this.world = world;
|
|
93
|
+
context.pages = [this.page];
|
|
94
|
+
context.pageLoading = { status: false };
|
|
87
95
|
this.registerEventListeners(this.context);
|
|
96
|
+
registerNetworkEvents(this.world, this, this.context, this.page);
|
|
97
|
+
registerDownloadEvent(this.page, this.world, this.context);
|
|
88
98
|
}
|
|
89
99
|
registerEventListeners(context) {
|
|
90
100
|
this.registerConsoleLogListener(this.page, context);
|
|
@@ -93,10 +103,17 @@ class StableBrowser {
|
|
|
93
103
|
context.pageLoading = { status: false };
|
|
94
104
|
}
|
|
95
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
|
+
}
|
|
96
111
|
context.pageLoading.status = true;
|
|
97
112
|
this.page = page;
|
|
98
113
|
context.page = page;
|
|
99
114
|
context.pages.push(page);
|
|
115
|
+
registerNetworkEvents(this.world, this, context, this.page);
|
|
116
|
+
registerDownloadEvent(this.page, this.world, context);
|
|
100
117
|
page.on("close", async () => {
|
|
101
118
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
102
119
|
this.context.pages.pop();
|
|
@@ -126,9 +143,9 @@ class StableBrowser {
|
|
|
126
143
|
if (this.appName === appName) {
|
|
127
144
|
return;
|
|
128
145
|
}
|
|
129
|
-
let
|
|
146
|
+
let navigate = false;
|
|
130
147
|
if (!apps[appName]) {
|
|
131
|
-
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this);
|
|
148
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
132
149
|
newContextCreated = true;
|
|
133
150
|
apps[appName] = {
|
|
134
151
|
context: newContext,
|
|
@@ -141,8 +158,7 @@ class StableBrowser {
|
|
|
141
158
|
this._copyContext(apps[appName], this);
|
|
142
159
|
apps[this.appName] = tempContext;
|
|
143
160
|
this.appName = appName;
|
|
144
|
-
if (
|
|
145
|
-
this.registerEventListeners(this.context);
|
|
161
|
+
if (navigate) {
|
|
146
162
|
await this.goto(this.context.environment.baseUrl);
|
|
147
163
|
await this.waitForPageLoad();
|
|
148
164
|
}
|
|
@@ -168,7 +184,6 @@ class StableBrowser {
|
|
|
168
184
|
this.context.webLogger = [];
|
|
169
185
|
}
|
|
170
186
|
page.on("console", async (msg) => {
|
|
171
|
-
var _a;
|
|
172
187
|
const obj = {
|
|
173
188
|
type: msg.type(),
|
|
174
189
|
text: msg.text(),
|
|
@@ -176,7 +191,9 @@ class StableBrowser {
|
|
|
176
191
|
time: new Date().toISOString(),
|
|
177
192
|
};
|
|
178
193
|
this.context.webLogger.push(obj);
|
|
179
|
-
|
|
194
|
+
if (msg.type() === "error") {
|
|
195
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+log" });
|
|
196
|
+
}
|
|
180
197
|
});
|
|
181
198
|
}
|
|
182
199
|
registerRequestListener(page, context, logFile) {
|
|
@@ -184,7 +201,6 @@ class StableBrowser {
|
|
|
184
201
|
this.context.networkLogger = [];
|
|
185
202
|
}
|
|
186
203
|
page.on("request", async (data) => {
|
|
187
|
-
var _a;
|
|
188
204
|
const startTime = new Date().getTime();
|
|
189
205
|
try {
|
|
190
206
|
const pageUrl = new URL(page.url());
|
|
@@ -209,7 +225,7 @@ class StableBrowser {
|
|
|
209
225
|
startTime,
|
|
210
226
|
};
|
|
211
227
|
context.networkLogger.push(obj);
|
|
212
|
-
|
|
228
|
+
this.world?.attach(JSON.stringify(obj), { mediaType: "application/json+network" });
|
|
213
229
|
}
|
|
214
230
|
catch (error) {
|
|
215
231
|
console.error("Error in request listener", error);
|
|
@@ -234,20 +250,6 @@ class StableBrowser {
|
|
|
234
250
|
timeout: 60000,
|
|
235
251
|
});
|
|
236
252
|
}
|
|
237
|
-
_validateSelectors(selectors) {
|
|
238
|
-
if (!selectors) {
|
|
239
|
-
throw new Error("selectors is null");
|
|
240
|
-
}
|
|
241
|
-
if (!selectors.locators) {
|
|
242
|
-
throw new Error("selectors.locators is null");
|
|
243
|
-
}
|
|
244
|
-
if (!Array.isArray(selectors.locators)) {
|
|
245
|
-
throw new Error("selectors.locators expected to be array");
|
|
246
|
-
}
|
|
247
|
-
if (selectors.locators.length === 0) {
|
|
248
|
-
throw new Error("selectors.locators expected to be non empty array");
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
253
|
_fixUsingParams(text, _params) {
|
|
252
254
|
if (!_params || typeof text !== "string") {
|
|
253
255
|
return text;
|
|
@@ -315,7 +317,7 @@ class StableBrowser {
|
|
|
315
317
|
locatorReturn = scope.getByRole(role, { name }, { exact: flags === "i" });
|
|
316
318
|
}
|
|
317
319
|
}
|
|
318
|
-
if (locator
|
|
320
|
+
if (locator?.engine) {
|
|
319
321
|
if (locator.engine === "css") {
|
|
320
322
|
locatorReturn = scope.locator(locator.selector);
|
|
321
323
|
}
|
|
@@ -339,7 +341,7 @@ class StableBrowser {
|
|
|
339
341
|
if (css && css.locator) {
|
|
340
342
|
css = css.locator;
|
|
341
343
|
}
|
|
342
|
-
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "
|
|
344
|
+
let result = await this._locateElementByText(scope, this._fixUsingParams(text, _params), "*:not(script, style, head)", false, false, _params);
|
|
343
345
|
if (result.elementCount === 0) {
|
|
344
346
|
return;
|
|
345
347
|
}
|
|
@@ -354,7 +356,7 @@ class StableBrowser {
|
|
|
354
356
|
}
|
|
355
357
|
async _locateElementByText(scope, text1, tag1, regex1 = false, partial1, _params) {
|
|
356
358
|
//const stringifyText = JSON.stringify(text);
|
|
357
|
-
return await scope.evaluate(([text, tag, regex, partial]) => {
|
|
359
|
+
return await scope.locator(":root").evaluate((_node, [text, tag, regex, partial]) => {
|
|
358
360
|
function isParent(parent, child) {
|
|
359
361
|
let currentNode = child.parentNode;
|
|
360
362
|
while (currentNode !== null) {
|
|
@@ -366,6 +368,15 @@ class StableBrowser {
|
|
|
366
368
|
return false;
|
|
367
369
|
}
|
|
368
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;
|
|
369
380
|
function collectAllShadowDomElements(element, result = []) {
|
|
370
381
|
// Check and add the element if it has a shadow root
|
|
371
382
|
if (element.shadowRoot) {
|
|
@@ -382,7 +393,11 @@ class StableBrowser {
|
|
|
382
393
|
}
|
|
383
394
|
document.collectAllShadowDomElements = collectAllShadowDomElements;
|
|
384
395
|
if (!tag) {
|
|
385
|
-
tag = "
|
|
396
|
+
tag = "*:not(script, style, head)";
|
|
397
|
+
}
|
|
398
|
+
let regexpSearch = document.getRegex(text);
|
|
399
|
+
if (regexpSearch) {
|
|
400
|
+
regex = true;
|
|
386
401
|
}
|
|
387
402
|
let elements = Array.from(document.querySelectorAll(tag));
|
|
388
403
|
let shadowHosts = [];
|
|
@@ -399,7 +414,9 @@ class StableBrowser {
|
|
|
399
414
|
let randomToken = null;
|
|
400
415
|
const foundElements = [];
|
|
401
416
|
if (regex) {
|
|
402
|
-
|
|
417
|
+
if (!regexpSearch) {
|
|
418
|
+
regexpSearch = new RegExp(text, "im");
|
|
419
|
+
}
|
|
403
420
|
for (let i = 0; i < elements.length; i++) {
|
|
404
421
|
const element = elements[i];
|
|
405
422
|
if ((element.innerText && regexpSearch.test(element.innerText)) ||
|
|
@@ -413,8 +430,8 @@ class StableBrowser {
|
|
|
413
430
|
for (let i = 0; i < elements.length; i++) {
|
|
414
431
|
const element = elements[i];
|
|
415
432
|
if (partial) {
|
|
416
|
-
if ((element.innerText && element.innerText.trim().includes(text)) ||
|
|
417
|
-
(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()))) {
|
|
418
435
|
foundElements.push(element);
|
|
419
436
|
}
|
|
420
437
|
}
|
|
@@ -458,19 +475,39 @@ class StableBrowser {
|
|
|
458
475
|
}, [text1, tag1, regex1, partial1]);
|
|
459
476
|
}
|
|
460
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
|
+
}
|
|
461
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
|
+
}
|
|
462
494
|
//info.log += "searching for locator " + JSON.stringify(locatorSearch) + "\n";
|
|
463
495
|
let locator = null;
|
|
464
496
|
if (locatorSearch.climb && locatorSearch.climb >= 0) {
|
|
465
497
|
let locatorString = await this._locateElmentByTextClimbCss(scope, locatorSearch.text, locatorSearch.climb, locatorSearch.css, _params);
|
|
466
498
|
if (!locatorString) {
|
|
499
|
+
info.failCause.textNotFound = true;
|
|
500
|
+
info.failCause.lastError = "failed to locate element by text: " + locatorSearch.text;
|
|
467
501
|
return;
|
|
468
502
|
}
|
|
469
503
|
locator = this._getLocator({ css: locatorString }, scope, _params);
|
|
470
504
|
}
|
|
471
505
|
else if (locatorSearch.text) {
|
|
472
|
-
let
|
|
506
|
+
let text = this._fixUsingParams(locatorSearch.text, _params);
|
|
507
|
+
let result = await this._locateElementByText(scope, text, locatorSearch.tag, false, locatorSearch.partial === true, _params);
|
|
473
508
|
if (result.elementCount === 0) {
|
|
509
|
+
info.failCause.textNotFound = true;
|
|
510
|
+
info.failCause.lastError = "failed to locate element by text: " + text;
|
|
474
511
|
return;
|
|
475
512
|
}
|
|
476
513
|
locatorSearch.css = "[data-blinq-id='blinq-id-" + result.randomToken + "']";
|
|
@@ -487,6 +524,9 @@ class StableBrowser {
|
|
|
487
524
|
// cssHref = true;
|
|
488
525
|
// }
|
|
489
526
|
let count = await locator.count();
|
|
527
|
+
if (count > 0 && !info.failCause.count) {
|
|
528
|
+
info.failCause.count = count;
|
|
529
|
+
}
|
|
490
530
|
//info.log += "total elements found " + count + "\n";
|
|
491
531
|
//let visibleCount = 0;
|
|
492
532
|
let visibleLocator = null;
|
|
@@ -504,6 +544,8 @@ class StableBrowser {
|
|
|
504
544
|
foundLocators.push(locator.nth(j));
|
|
505
545
|
}
|
|
506
546
|
else {
|
|
547
|
+
info.failCause.visible = visible;
|
|
548
|
+
info.failCause.enabled = enabled;
|
|
507
549
|
if (!info.printMessages) {
|
|
508
550
|
info.printMessages = {};
|
|
509
551
|
}
|
|
@@ -515,6 +557,11 @@ class StableBrowser {
|
|
|
515
557
|
}
|
|
516
558
|
}
|
|
517
559
|
async closeUnexpectedPopups(info, _params) {
|
|
560
|
+
if (!info) {
|
|
561
|
+
info = {};
|
|
562
|
+
info.failCause = {};
|
|
563
|
+
info.log = "";
|
|
564
|
+
}
|
|
518
565
|
if (this.configuration.popupHandlers && this.configuration.popupHandlers.length > 0) {
|
|
519
566
|
if (!info) {
|
|
520
567
|
info = {};
|
|
@@ -555,7 +602,10 @@ class StableBrowser {
|
|
|
555
602
|
}
|
|
556
603
|
return { rerun: false };
|
|
557
604
|
}
|
|
558
|
-
async _locate(selectors, info, _params, timeout
|
|
605
|
+
async _locate(selectors, info, _params, timeout) {
|
|
606
|
+
if (!timeout) {
|
|
607
|
+
timeout = 30000;
|
|
608
|
+
}
|
|
559
609
|
for (let i = 0; i < 3; i++) {
|
|
560
610
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
561
611
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
@@ -569,35 +619,46 @@ class StableBrowser {
|
|
|
569
619
|
}
|
|
570
620
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
571
621
|
}
|
|
572
|
-
async
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
622
|
+
async _findFrameScope(selectors, timeout = 30000, info) {
|
|
623
|
+
if (!info) {
|
|
624
|
+
info = {};
|
|
625
|
+
info.failCause = {};
|
|
626
|
+
info.log = "";
|
|
627
|
+
}
|
|
578
628
|
let scope = this.page;
|
|
629
|
+
if (selectors.frame) {
|
|
630
|
+
return selectors.frame;
|
|
631
|
+
}
|
|
579
632
|
if (selectors.iframe_src || selectors.frameLocators) {
|
|
580
|
-
const findFrame = (frame, framescope) => {
|
|
633
|
+
const findFrame = async (frame, framescope) => {
|
|
581
634
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
582
635
|
let frameLocator = frame.selectors[i];
|
|
583
636
|
if (frameLocator.css) {
|
|
584
|
-
|
|
637
|
+
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
585
638
|
if (frameLocator.index) {
|
|
586
|
-
|
|
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);
|
|
587
650
|
}
|
|
588
|
-
break;
|
|
589
651
|
}
|
|
590
652
|
}
|
|
591
653
|
if (frame.children) {
|
|
592
|
-
return findFrame(frame.children, framescope);
|
|
654
|
+
return await findFrame(frame.children, framescope);
|
|
593
655
|
}
|
|
594
656
|
return framescope;
|
|
595
657
|
};
|
|
596
|
-
info.log += "searching for iframe " + selectors.iframe_src + "/" + selectors.frameLocators + "\n";
|
|
597
658
|
while (true) {
|
|
598
659
|
let frameFound = false;
|
|
599
660
|
if (selectors.nestFrmLoc) {
|
|
600
|
-
scope = findFrame(selectors.nestFrmLoc, scope);
|
|
661
|
+
scope = await findFrame(selectors.nestFrmLoc, scope);
|
|
601
662
|
frameFound = true;
|
|
602
663
|
break;
|
|
603
664
|
}
|
|
@@ -617,6 +678,8 @@ class StableBrowser {
|
|
|
617
678
|
if (!scope) {
|
|
618
679
|
info.log += "unable to locate iframe " + selectors.iframe_src + "\n";
|
|
619
680
|
if (performance.now() - startTime > timeout) {
|
|
681
|
+
info.failCause.iframeNotFound = true;
|
|
682
|
+
info.failCause.lastError = "unable to locate iframe " + selectors.iframe_src;
|
|
620
683
|
throw new Error("unable to locate iframe " + selectors.iframe_src);
|
|
621
684
|
}
|
|
622
685
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -626,6 +689,30 @@ class StableBrowser {
|
|
|
626
689
|
}
|
|
627
690
|
}
|
|
628
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);
|
|
629
716
|
let selectorsLocators = null;
|
|
630
717
|
selectorsLocators = selectors.locators;
|
|
631
718
|
// group selectors by priority
|
|
@@ -727,6 +814,8 @@ class StableBrowser {
|
|
|
727
814
|
}
|
|
728
815
|
this.logger.debug("unable to locate unique element, total elements found " + locatorsCount);
|
|
729
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";
|
|
730
819
|
throw new Error("failed to locate first element no elements found, " + info.log);
|
|
731
820
|
}
|
|
732
821
|
async _scanLocatorsGroup(locatorsGroup, scope, _params, info, visibleOnly) {
|
|
@@ -758,89 +847,129 @@ class StableBrowser {
|
|
|
758
847
|
});
|
|
759
848
|
result.locatorIndex = i;
|
|
760
849
|
}
|
|
850
|
+
if (foundLocators.length > 1) {
|
|
851
|
+
info.failCause.foundMultiple = true;
|
|
852
|
+
}
|
|
761
853
|
}
|
|
762
854
|
return result;
|
|
763
855
|
}
|
|
764
|
-
async
|
|
765
|
-
this._validateSelectors(selectors);
|
|
856
|
+
async simpleClick(elementDescription, _params, options = {}, world = null) {
|
|
766
857
|
const startTime = Date.now();
|
|
767
|
-
|
|
768
|
-
|
|
858
|
+
let timeout = 30000;
|
|
859
|
+
if (options && options.timeout) {
|
|
860
|
+
timeout = options.timeout;
|
|
769
861
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
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
|
+
};
|
|
777
929
|
try {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
930
|
+
await _preCommand(state, this);
|
|
931
|
+
if (state.options && state.options.context) {
|
|
932
|
+
state.selectors.locators[0].text = state.options.context;
|
|
933
|
+
}
|
|
781
934
|
try {
|
|
782
|
-
await
|
|
783
|
-
await
|
|
784
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
935
|
+
await state.element.click();
|
|
936
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
785
937
|
}
|
|
786
938
|
catch (e) {
|
|
787
939
|
// await this.closeUnexpectedPopups();
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
await
|
|
791
|
-
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));
|
|
792
943
|
}
|
|
793
944
|
await this.waitForPageLoad();
|
|
794
|
-
return info;
|
|
945
|
+
return state.info;
|
|
795
946
|
}
|
|
796
947
|
catch (e) {
|
|
797
|
-
|
|
798
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
799
|
-
info.screenshotPath = screenshotPath;
|
|
800
|
-
Object.assign(e, { info: info });
|
|
801
|
-
error = e;
|
|
802
|
-
throw e;
|
|
948
|
+
await _commandError(state, e, this);
|
|
803
949
|
}
|
|
804
950
|
finally {
|
|
805
|
-
|
|
806
|
-
this._reportToWorld(world, {
|
|
807
|
-
element_name: selectors.element_name,
|
|
808
|
-
type: Types.CLICK,
|
|
809
|
-
text: `Click element`,
|
|
810
|
-
screenshotId,
|
|
811
|
-
result: error
|
|
812
|
-
? {
|
|
813
|
-
status: "FAILED",
|
|
814
|
-
startTime,
|
|
815
|
-
endTime,
|
|
816
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
817
|
-
}
|
|
818
|
-
: {
|
|
819
|
-
status: "PASSED",
|
|
820
|
-
startTime,
|
|
821
|
-
endTime,
|
|
822
|
-
},
|
|
823
|
-
info: info,
|
|
824
|
-
});
|
|
951
|
+
_commandFinally(state, this);
|
|
825
952
|
}
|
|
826
953
|
}
|
|
827
954
|
async setCheck(selectors, checked = true, _params, options = {}, world = null) {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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
|
+
};
|
|
838
965
|
try {
|
|
839
|
-
|
|
840
|
-
|
|
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));
|
|
841
970
|
try {
|
|
842
|
-
await this._highlightElements(element);
|
|
843
|
-
await element.setChecked(checked);
|
|
971
|
+
// await this._highlightElements(element);
|
|
972
|
+
await state.element.setChecked(checked);
|
|
844
973
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
845
974
|
}
|
|
846
975
|
catch (e) {
|
|
@@ -849,179 +978,108 @@ class StableBrowser {
|
|
|
849
978
|
}
|
|
850
979
|
else {
|
|
851
980
|
//await this.closeUnexpectedPopups();
|
|
852
|
-
info.log += "setCheck failed, will try again" + "\n";
|
|
853
|
-
element = await this._locate(selectors, info, _params);
|
|
854
|
-
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 });
|
|
855
984
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
856
985
|
}
|
|
857
986
|
}
|
|
858
987
|
await this.waitForPageLoad();
|
|
859
|
-
return info;
|
|
988
|
+
return state.info;
|
|
860
989
|
}
|
|
861
990
|
catch (e) {
|
|
862
|
-
|
|
863
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
864
|
-
info.screenshotPath = screenshotPath;
|
|
865
|
-
Object.assign(e, { info: info });
|
|
866
|
-
error = e;
|
|
867
|
-
throw e;
|
|
991
|
+
await _commandError(state, e, this);
|
|
868
992
|
}
|
|
869
993
|
finally {
|
|
870
|
-
|
|
871
|
-
this._reportToWorld(world, {
|
|
872
|
-
element_name: selectors.element_name,
|
|
873
|
-
type: checked ? Types.CHECK : Types.UNCHECK,
|
|
874
|
-
text: checked ? `Check element` : `Uncheck element`,
|
|
875
|
-
screenshotId,
|
|
876
|
-
result: error
|
|
877
|
-
? {
|
|
878
|
-
status: "FAILED",
|
|
879
|
-
startTime,
|
|
880
|
-
endTime,
|
|
881
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
882
|
-
}
|
|
883
|
-
: {
|
|
884
|
-
status: "PASSED",
|
|
885
|
-
startTime,
|
|
886
|
-
endTime,
|
|
887
|
-
},
|
|
888
|
-
info: info,
|
|
889
|
-
});
|
|
994
|
+
_commandFinally(state, this);
|
|
890
995
|
}
|
|
891
996
|
}
|
|
892
997
|
async hover(selectors, _params, options = {}, world = null) {
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
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
|
+
};
|
|
902
1008
|
try {
|
|
903
|
-
|
|
904
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1009
|
+
await _preCommand(state, this);
|
|
905
1010
|
try {
|
|
906
|
-
await
|
|
907
|
-
await element.hover();
|
|
1011
|
+
await state.element.hover();
|
|
908
1012
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
909
1013
|
}
|
|
910
1014
|
catch (e) {
|
|
911
1015
|
//await this.closeUnexpectedPopups();
|
|
912
|
-
info.log += "hover failed, will try again" + "\n";
|
|
913
|
-
element = await this._locate(selectors, info, _params);
|
|
914
|
-
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 });
|
|
915
1019
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
916
1020
|
}
|
|
917
1021
|
await this.waitForPageLoad();
|
|
918
|
-
return info;
|
|
1022
|
+
return state.info;
|
|
919
1023
|
}
|
|
920
1024
|
catch (e) {
|
|
921
|
-
|
|
922
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
923
|
-
info.screenshotPath = screenshotPath;
|
|
924
|
-
Object.assign(e, { info: info });
|
|
925
|
-
error = e;
|
|
926
|
-
throw e;
|
|
1025
|
+
await _commandError(state, e, this);
|
|
927
1026
|
}
|
|
928
1027
|
finally {
|
|
929
|
-
|
|
930
|
-
this._reportToWorld(world, {
|
|
931
|
-
element_name: selectors.element_name,
|
|
932
|
-
type: Types.HOVER,
|
|
933
|
-
text: `Hover element`,
|
|
934
|
-
screenshotId,
|
|
935
|
-
result: error
|
|
936
|
-
? {
|
|
937
|
-
status: "FAILED",
|
|
938
|
-
startTime,
|
|
939
|
-
endTime,
|
|
940
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
941
|
-
}
|
|
942
|
-
: {
|
|
943
|
-
status: "PASSED",
|
|
944
|
-
startTime,
|
|
945
|
-
endTime,
|
|
946
|
-
},
|
|
947
|
-
info: info,
|
|
948
|
-
});
|
|
1028
|
+
_commandFinally(state, this);
|
|
949
1029
|
}
|
|
950
1030
|
}
|
|
951
1031
|
async selectOption(selectors, values, _params = null, options = {}, world = null) {
|
|
952
|
-
this._validateSelectors(selectors);
|
|
953
1032
|
if (!values) {
|
|
954
1033
|
throw new Error("values is null");
|
|
955
1034
|
}
|
|
956
|
-
const
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
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
|
+
};
|
|
964
1046
|
try {
|
|
965
|
-
|
|
966
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1047
|
+
await _preCommand(state, this);
|
|
967
1048
|
try {
|
|
968
|
-
await
|
|
969
|
-
await element.selectOption(values);
|
|
1049
|
+
await state.element.selectOption(values);
|
|
970
1050
|
}
|
|
971
1051
|
catch (e) {
|
|
972
1052
|
//await this.closeUnexpectedPopups();
|
|
973
|
-
info.log += "selectOption failed, will try force" + "\n";
|
|
974
|
-
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 });
|
|
975
1055
|
}
|
|
976
1056
|
await this.waitForPageLoad();
|
|
977
|
-
return info;
|
|
1057
|
+
return state.info;
|
|
978
1058
|
}
|
|
979
1059
|
catch (e) {
|
|
980
|
-
|
|
981
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
982
|
-
info.screenshotPath = screenshotPath;
|
|
983
|
-
Object.assign(e, { info: info });
|
|
984
|
-
this.logger.info("click failed, will try next selector");
|
|
985
|
-
error = e;
|
|
986
|
-
throw e;
|
|
1060
|
+
await _commandError(state, e, this);
|
|
987
1061
|
}
|
|
988
1062
|
finally {
|
|
989
|
-
|
|
990
|
-
this._reportToWorld(world, {
|
|
991
|
-
element_name: selectors.element_name,
|
|
992
|
-
type: Types.SELECT,
|
|
993
|
-
text: `Select option: ${values}`,
|
|
994
|
-
value: values.toString(),
|
|
995
|
-
screenshotId,
|
|
996
|
-
result: error
|
|
997
|
-
? {
|
|
998
|
-
status: "FAILED",
|
|
999
|
-
startTime,
|
|
1000
|
-
endTime,
|
|
1001
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1002
|
-
}
|
|
1003
|
-
: {
|
|
1004
|
-
status: "PASSED",
|
|
1005
|
-
startTime,
|
|
1006
|
-
endTime,
|
|
1007
|
-
},
|
|
1008
|
-
info: info,
|
|
1009
|
-
});
|
|
1063
|
+
_commandFinally(state, this);
|
|
1010
1064
|
}
|
|
1011
1065
|
}
|
|
1012
1066
|
async type(_value, _params = null, options = {}, world = null) {
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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
|
+
};
|
|
1022
1080
|
try {
|
|
1023
|
-
|
|
1024
|
-
const valueSegment =
|
|
1081
|
+
await _preCommand(state, this);
|
|
1082
|
+
const valueSegment = state.value.split("&&");
|
|
1025
1083
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1026
1084
|
if (i > 0) {
|
|
1027
1085
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1041,134 +1099,76 @@ class StableBrowser {
|
|
|
1041
1099
|
await this.page.keyboard.type(value);
|
|
1042
1100
|
}
|
|
1043
1101
|
}
|
|
1044
|
-
return info;
|
|
1102
|
+
return state.info;
|
|
1045
1103
|
}
|
|
1046
1104
|
catch (e) {
|
|
1047
|
-
|
|
1048
|
-
this.logger.error("type failed " + info.log);
|
|
1049
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1050
|
-
info.screenshotPath = screenshotPath;
|
|
1051
|
-
Object.assign(e, { info: info });
|
|
1052
|
-
error = e;
|
|
1053
|
-
throw e;
|
|
1105
|
+
await _commandError(state, e, this);
|
|
1054
1106
|
}
|
|
1055
1107
|
finally {
|
|
1056
|
-
|
|
1057
|
-
this._reportToWorld(world, {
|
|
1058
|
-
type: Types.TYPE_PRESS,
|
|
1059
|
-
screenshotId,
|
|
1060
|
-
value: _value,
|
|
1061
|
-
text: `type value: ${_value}`,
|
|
1062
|
-
result: error
|
|
1063
|
-
? {
|
|
1064
|
-
status: "FAILED",
|
|
1065
|
-
startTime,
|
|
1066
|
-
endTime,
|
|
1067
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1068
|
-
}
|
|
1069
|
-
: {
|
|
1070
|
-
status: "PASSED",
|
|
1071
|
-
startTime,
|
|
1072
|
-
endTime,
|
|
1073
|
-
},
|
|
1074
|
-
info: info,
|
|
1075
|
-
});
|
|
1108
|
+
_commandFinally(state, this);
|
|
1076
1109
|
}
|
|
1077
1110
|
}
|
|
1078
1111
|
async setInputValue(selectors, value, _params = null, options = {}, world = null) {
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
let screenshotPath = 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
|
+
};
|
|
1091
1123
|
try {
|
|
1092
|
-
|
|
1093
|
-
let
|
|
1094
|
-
await this.scrollIfNeeded(element, info);
|
|
1095
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1096
|
-
await this._highlightElements(element);
|
|
1124
|
+
await _preCommand(state, this);
|
|
1125
|
+
let value = await this._replaceWithLocalData(state.value, this);
|
|
1097
1126
|
try {
|
|
1098
|
-
await element.evaluateHandle((el, value) => {
|
|
1127
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1099
1128
|
el.value = value;
|
|
1100
1129
|
}, value);
|
|
1101
1130
|
}
|
|
1102
1131
|
catch (error) {
|
|
1103
1132
|
this.logger.error("setInputValue failed, will try again");
|
|
1104
|
-
|
|
1105
|
-
info.
|
|
1106
|
-
|
|
1107
|
-
await element.evaluateHandle((el, value) => {
|
|
1133
|
+
await _screenshot(state, this);
|
|
1134
|
+
Object.assign(error, { info: state.info });
|
|
1135
|
+
await state.element.evaluateHandle((el, value) => {
|
|
1108
1136
|
el.value = value;
|
|
1109
1137
|
});
|
|
1110
1138
|
}
|
|
1111
1139
|
}
|
|
1112
1140
|
catch (e) {
|
|
1113
|
-
|
|
1114
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1115
|
-
info.screenshotPath = screenshotPath;
|
|
1116
|
-
Object.assign(e, { info: info });
|
|
1117
|
-
error = e;
|
|
1118
|
-
throw e;
|
|
1141
|
+
await _commandError(state, e, this);
|
|
1119
1142
|
}
|
|
1120
1143
|
finally {
|
|
1121
|
-
|
|
1122
|
-
this._reportToWorld(world, {
|
|
1123
|
-
element_name: selectors.element_name,
|
|
1124
|
-
type: Types.SET_INPUT,
|
|
1125
|
-
text: `Set input value`,
|
|
1126
|
-
value: value,
|
|
1127
|
-
screenshotId,
|
|
1128
|
-
result: error
|
|
1129
|
-
? {
|
|
1130
|
-
status: "FAILED",
|
|
1131
|
-
startTime,
|
|
1132
|
-
endTime,
|
|
1133
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1134
|
-
}
|
|
1135
|
-
: {
|
|
1136
|
-
status: "PASSED",
|
|
1137
|
-
startTime,
|
|
1138
|
-
endTime,
|
|
1139
|
-
},
|
|
1140
|
-
info: info,
|
|
1141
|
-
});
|
|
1144
|
+
_commandFinally(state, this);
|
|
1142
1145
|
}
|
|
1143
1146
|
}
|
|
1144
1147
|
async setDateTime(selectors, value, format = null, enter = false, _params = null, options = {}, world = null) {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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
|
+
};
|
|
1155
1160
|
try {
|
|
1156
|
-
|
|
1157
|
-
let element = await this._locate(selectors, info, _params);
|
|
1158
|
-
//insert red border around the element
|
|
1159
|
-
await this.scrollIfNeeded(element, info);
|
|
1160
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1161
|
-
await this._highlightElements(element);
|
|
1161
|
+
await _preCommand(state, this);
|
|
1162
1162
|
try {
|
|
1163
|
-
await element.click();
|
|
1163
|
+
await state.element.click();
|
|
1164
1164
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1165
1165
|
if (format) {
|
|
1166
|
-
value = dayjs(value).format(format);
|
|
1167
|
-
await element.fill(value);
|
|
1166
|
+
state.value = dayjs(state.value).format(format);
|
|
1167
|
+
await state.element.fill(state.value);
|
|
1168
1168
|
}
|
|
1169
1169
|
else {
|
|
1170
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1171
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1170
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1171
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1172
1172
|
el.value = ""; // clear input
|
|
1173
1173
|
el.value = dateTimeValue;
|
|
1174
1174
|
}, dateTimeValue);
|
|
@@ -1181,20 +1181,19 @@ class StableBrowser {
|
|
|
1181
1181
|
}
|
|
1182
1182
|
catch (err) {
|
|
1183
1183
|
//await this.closeUnexpectedPopups();
|
|
1184
|
-
this.logger.error("setting date time input failed " + JSON.stringify(info));
|
|
1184
|
+
this.logger.error("setting date time input failed " + JSON.stringify(state.info));
|
|
1185
1185
|
this.logger.info("Trying again");
|
|
1186
|
-
|
|
1187
|
-
info.
|
|
1188
|
-
Object.assign(err, { info: info });
|
|
1186
|
+
await _screenshot(state, this);
|
|
1187
|
+
Object.assign(err, { info: state.info });
|
|
1189
1188
|
await element.click();
|
|
1190
1189
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1191
1190
|
if (format) {
|
|
1192
|
-
value = dayjs(value).format(format);
|
|
1193
|
-
await element.fill(value);
|
|
1191
|
+
state.value = dayjs(state.value).format(format);
|
|
1192
|
+
await state.element.fill(state.value);
|
|
1194
1193
|
}
|
|
1195
1194
|
else {
|
|
1196
|
-
const dateTimeValue = await getDateTimeValue({ value, element });
|
|
1197
|
-
await element.evaluateHandle((el, dateTimeValue) => {
|
|
1195
|
+
const dateTimeValue = await getDateTimeValue({ value: state.value, element: state.element });
|
|
1196
|
+
await state.element.evaluateHandle((el, dateTimeValue) => {
|
|
1198
1197
|
el.value = ""; // clear input
|
|
1199
1198
|
el.value = dateTimeValue;
|
|
1200
1199
|
}, dateTimeValue);
|
|
@@ -1207,61 +1206,39 @@ class StableBrowser {
|
|
|
1207
1206
|
}
|
|
1208
1207
|
}
|
|
1209
1208
|
catch (e) {
|
|
1210
|
-
|
|
1211
|
-
throw e;
|
|
1209
|
+
await _commandError(state, e, this);
|
|
1212
1210
|
}
|
|
1213
1211
|
finally {
|
|
1214
|
-
|
|
1215
|
-
this._reportToWorld(world, {
|
|
1216
|
-
element_name: selectors.element_name,
|
|
1217
|
-
type: Types.SET_DATE_TIME,
|
|
1218
|
-
screenshotId,
|
|
1219
|
-
value: value,
|
|
1220
|
-
text: `setDateTime input with value: ${value}`,
|
|
1221
|
-
result: error
|
|
1222
|
-
? {
|
|
1223
|
-
status: "FAILED",
|
|
1224
|
-
startTime,
|
|
1225
|
-
endTime,
|
|
1226
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1227
|
-
}
|
|
1228
|
-
: {
|
|
1229
|
-
status: "PASSED",
|
|
1230
|
-
startTime,
|
|
1231
|
-
endTime,
|
|
1232
|
-
},
|
|
1233
|
-
info: info,
|
|
1234
|
-
});
|
|
1212
|
+
_commandFinally(state, this);
|
|
1235
1213
|
}
|
|
1236
1214
|
}
|
|
1237
1215
|
async clickType(selectors, _value, enter = false, _params = null, options = {}, world = null) {
|
|
1238
1216
|
_value = unEscapeString(_value);
|
|
1239
|
-
this._validateSelectors(selectors);
|
|
1240
|
-
const startTime = Date.now();
|
|
1241
|
-
let error = null;
|
|
1242
|
-
let screenshotId = null;
|
|
1243
|
-
let screenshotPath = null;
|
|
1244
|
-
const info = {};
|
|
1245
|
-
info.log = "***** clickType on " + selectors.element_name + " with value " + _value + "*****\n";
|
|
1246
|
-
info.operation = "clickType";
|
|
1247
|
-
info.selectors = selectors;
|
|
1248
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
|
+
};
|
|
1249
1230
|
if (newValue !== _value) {
|
|
1250
1231
|
//this.logger.info(_value + "=" + newValue);
|
|
1251
1232
|
_value = newValue;
|
|
1252
1233
|
}
|
|
1253
|
-
info.value = _value;
|
|
1254
1234
|
try {
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
await this.scrollIfNeeded(element, info);
|
|
1258
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1259
|
-
await this._highlightElements(element);
|
|
1235
|
+
await _preCommand(state, this);
|
|
1236
|
+
state.info.value = _value;
|
|
1260
1237
|
if (options === null || options === undefined || !options.press) {
|
|
1261
1238
|
try {
|
|
1262
|
-
let currentValue = await element.inputValue();
|
|
1239
|
+
let currentValue = await state.element.inputValue();
|
|
1263
1240
|
if (currentValue) {
|
|
1264
|
-
await element.fill("");
|
|
1241
|
+
await state.element.fill("");
|
|
1265
1242
|
}
|
|
1266
1243
|
}
|
|
1267
1244
|
catch (e) {
|
|
@@ -1270,22 +1247,22 @@ class StableBrowser {
|
|
|
1270
1247
|
}
|
|
1271
1248
|
if (options === null || options === undefined || options.press) {
|
|
1272
1249
|
try {
|
|
1273
|
-
await element.click({ timeout: 5000 });
|
|
1250
|
+
await state.element.click({ timeout: 5000 });
|
|
1274
1251
|
}
|
|
1275
1252
|
catch (e) {
|
|
1276
|
-
await element.dispatchEvent("click");
|
|
1253
|
+
await state.element.dispatchEvent("click");
|
|
1277
1254
|
}
|
|
1278
1255
|
}
|
|
1279
1256
|
else {
|
|
1280
1257
|
try {
|
|
1281
|
-
await element.focus();
|
|
1258
|
+
await state.element.focus();
|
|
1282
1259
|
}
|
|
1283
1260
|
catch (e) {
|
|
1284
|
-
await element.dispatchEvent("focus");
|
|
1261
|
+
await state.element.dispatchEvent("focus");
|
|
1285
1262
|
}
|
|
1286
1263
|
}
|
|
1287
1264
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1288
|
-
const valueSegment =
|
|
1265
|
+
const valueSegment = state.value.split("&&");
|
|
1289
1266
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1290
1267
|
if (i > 0) {
|
|
1291
1268
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -1305,13 +1282,14 @@ class StableBrowser {
|
|
|
1305
1282
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1306
1283
|
}
|
|
1307
1284
|
}
|
|
1285
|
+
await _screenshot(state, this);
|
|
1308
1286
|
if (enter === true) {
|
|
1309
1287
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1310
1288
|
await this.page.keyboard.press("Enter");
|
|
1311
1289
|
await this.waitForPageLoad();
|
|
1312
1290
|
}
|
|
1313
1291
|
else if (enter === false) {
|
|
1314
|
-
await element.dispatchEvent("change");
|
|
1292
|
+
await state.element.dispatchEvent("change");
|
|
1315
1293
|
//await this.page.keyboard.press("Tab");
|
|
1316
1294
|
}
|
|
1317
1295
|
else {
|
|
@@ -1320,104 +1298,50 @@ class StableBrowser {
|
|
|
1320
1298
|
await this.waitForPageLoad();
|
|
1321
1299
|
}
|
|
1322
1300
|
}
|
|
1323
|
-
return info;
|
|
1301
|
+
return state.info;
|
|
1324
1302
|
}
|
|
1325
1303
|
catch (e) {
|
|
1326
|
-
|
|
1327
|
-
this.logger.error("fill failed " + JSON.stringify(info));
|
|
1328
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1329
|
-
info.screenshotPath = screenshotPath;
|
|
1330
|
-
Object.assign(e, { info: info });
|
|
1331
|
-
error = e;
|
|
1332
|
-
throw e;
|
|
1304
|
+
await _commandError(state, e, this);
|
|
1333
1305
|
}
|
|
1334
1306
|
finally {
|
|
1335
|
-
|
|
1336
|
-
this._reportToWorld(world, {
|
|
1337
|
-
element_name: selectors.element_name,
|
|
1338
|
-
type: Types.FILL,
|
|
1339
|
-
screenshotId,
|
|
1340
|
-
value: _value,
|
|
1341
|
-
text: `clickType input with value: ${_value}`,
|
|
1342
|
-
result: error
|
|
1343
|
-
? {
|
|
1344
|
-
status: "FAILED",
|
|
1345
|
-
startTime,
|
|
1346
|
-
endTime,
|
|
1347
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1348
|
-
}
|
|
1349
|
-
: {
|
|
1350
|
-
status: "PASSED",
|
|
1351
|
-
startTime,
|
|
1352
|
-
endTime,
|
|
1353
|
-
},
|
|
1354
|
-
info: info,
|
|
1355
|
-
});
|
|
1307
|
+
_commandFinally(state, this);
|
|
1356
1308
|
}
|
|
1357
1309
|
}
|
|
1358
1310
|
async fill(selectors, value, enter = false, _params = null, options = {}, world = null) {
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
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
|
+
};
|
|
1370
1322
|
try {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
await
|
|
1374
|
-
await element.fill(value);
|
|
1375
|
-
await element.dispatchEvent("change");
|
|
1323
|
+
await _preCommand(state, this);
|
|
1324
|
+
await state.element.fill(value);
|
|
1325
|
+
await state.element.dispatchEvent("change");
|
|
1376
1326
|
if (enter) {
|
|
1377
1327
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1378
|
-
await this.page.keyboard.press("Enter");
|
|
1379
|
-
}
|
|
1380
|
-
await this.waitForPageLoad();
|
|
1381
|
-
return info;
|
|
1382
|
-
}
|
|
1383
|
-
catch (e) {
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
Object.assign(e, { info: info });
|
|
1389
|
-
error = e;
|
|
1390
|
-
throw e;
|
|
1391
|
-
}
|
|
1392
|
-
finally {
|
|
1393
|
-
const endTime = Date.now();
|
|
1394
|
-
this._reportToWorld(world, {
|
|
1395
|
-
element_name: selectors.element_name,
|
|
1396
|
-
type: Types.FILL,
|
|
1397
|
-
screenshotId,
|
|
1398
|
-
value,
|
|
1399
|
-
text: `Fill input with value: ${value}`,
|
|
1400
|
-
result: error
|
|
1401
|
-
? {
|
|
1402
|
-
status: "FAILED",
|
|
1403
|
-
startTime,
|
|
1404
|
-
endTime,
|
|
1405
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1406
|
-
}
|
|
1407
|
-
: {
|
|
1408
|
-
status: "PASSED",
|
|
1409
|
-
startTime,
|
|
1410
|
-
endTime,
|
|
1411
|
-
},
|
|
1412
|
-
info: info,
|
|
1413
|
-
});
|
|
1328
|
+
await this.page.keyboard.press("Enter");
|
|
1329
|
+
}
|
|
1330
|
+
await this.waitForPageLoad();
|
|
1331
|
+
return state.info;
|
|
1332
|
+
}
|
|
1333
|
+
catch (e) {
|
|
1334
|
+
await _commandError(state, e, this);
|
|
1335
|
+
}
|
|
1336
|
+
finally {
|
|
1337
|
+
_commandFinally(state, this);
|
|
1414
1338
|
}
|
|
1415
1339
|
}
|
|
1416
1340
|
async getText(selectors, _params = null, options = {}, info = {}, world = null) {
|
|
1417
1341
|
return await this._getText(selectors, 0, _params, options, info, world);
|
|
1418
1342
|
}
|
|
1419
1343
|
async _getText(selectors, climb, _params = null, options = {}, info = {}, world = null) {
|
|
1420
|
-
|
|
1344
|
+
_validateSelectors(selectors);
|
|
1421
1345
|
let screenshotId = null;
|
|
1422
1346
|
let screenshotPath = null;
|
|
1423
1347
|
if (!info.log) {
|
|
@@ -1461,166 +1385,124 @@ class StableBrowser {
|
|
|
1461
1385
|
}
|
|
1462
1386
|
}
|
|
1463
1387
|
async containsPattern(selectors, pattern, text, _params = null, options = {}, world = null) {
|
|
1464
|
-
var _a;
|
|
1465
|
-
this._validateSelectors(selectors);
|
|
1466
1388
|
if (!pattern) {
|
|
1467
1389
|
throw new Error("pattern is null");
|
|
1468
1390
|
}
|
|
1469
1391
|
if (!text) {
|
|
1470
1392
|
throw new Error("text is null");
|
|
1471
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
|
+
};
|
|
1472
1410
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1473
1411
|
if (newValue !== text) {
|
|
1474
1412
|
this.logger.info(text + "=" + newValue);
|
|
1475
1413
|
text = newValue;
|
|
1476
1414
|
}
|
|
1477
|
-
const startTime = Date.now();
|
|
1478
|
-
let error = null;
|
|
1479
|
-
let screenshotId = null;
|
|
1480
|
-
let screenshotPath = null;
|
|
1481
|
-
const info = {};
|
|
1482
|
-
info.log =
|
|
1483
|
-
"***** verify element " + selectors.element_name + " contains pattern " + pattern + "/" + text + " *****\n";
|
|
1484
|
-
info.operation = "containsPattern";
|
|
1485
|
-
info.selectors = selectors;
|
|
1486
|
-
info.value = text;
|
|
1487
|
-
info.pattern = pattern;
|
|
1488
1415
|
let foundObj = null;
|
|
1489
1416
|
try {
|
|
1490
|
-
|
|
1417
|
+
await _preCommand(state, this);
|
|
1418
|
+
state.info.pattern = pattern;
|
|
1419
|
+
foundObj = await this._getText(selectors, 0, _params, options, state.info, world);
|
|
1491
1420
|
if (foundObj && foundObj.element) {
|
|
1492
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1421
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1493
1422
|
}
|
|
1494
|
-
|
|
1423
|
+
await _screenshot(state, this);
|
|
1495
1424
|
let escapedText = text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
|
|
1496
1425
|
pattern = pattern.replace("{text}", escapedText);
|
|
1497
1426
|
let regex = new RegExp(pattern, "im");
|
|
1498
|
-
if (!regex.test(foundObj
|
|
1499
|
-
info.foundText = foundObj
|
|
1427
|
+
if (!regex.test(foundObj?.text) && !foundObj?.value?.includes(text)) {
|
|
1428
|
+
state.info.foundText = foundObj?.text;
|
|
1500
1429
|
throw new Error("element doesn't contain text " + text);
|
|
1501
1430
|
}
|
|
1502
|
-
return info;
|
|
1431
|
+
return state.info;
|
|
1503
1432
|
}
|
|
1504
1433
|
catch (e) {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
this.logger.error("found text " + (foundObj === null || foundObj === void 0 ? void 0 : foundObj.text) + " pattern " + pattern);
|
|
1508
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1509
|
-
info.screenshotPath = screenshotPath;
|
|
1510
|
-
Object.assign(e, { info: info });
|
|
1511
|
-
error = e;
|
|
1512
|
-
throw e;
|
|
1434
|
+
this.logger.error("found text " + foundObj?.text + " pattern " + pattern);
|
|
1435
|
+
await _commandError(state, e, this);
|
|
1513
1436
|
}
|
|
1514
1437
|
finally {
|
|
1515
|
-
|
|
1516
|
-
this._reportToWorld(world, {
|
|
1517
|
-
element_name: selectors.element_name,
|
|
1518
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1519
|
-
value: pattern,
|
|
1520
|
-
text: `Verify element contains pattern: ${pattern}`,
|
|
1521
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1522
|
-
result: error
|
|
1523
|
-
? {
|
|
1524
|
-
status: "FAILED",
|
|
1525
|
-
startTime,
|
|
1526
|
-
endTime,
|
|
1527
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1528
|
-
}
|
|
1529
|
-
: {
|
|
1530
|
-
status: "PASSED",
|
|
1531
|
-
startTime,
|
|
1532
|
-
endTime,
|
|
1533
|
-
},
|
|
1534
|
-
info: info,
|
|
1535
|
-
});
|
|
1438
|
+
_commandFinally(state, this);
|
|
1536
1439
|
}
|
|
1537
1440
|
}
|
|
1538
1441
|
async containsText(selectors, text, climb, _params = null, options = {}, world = null) {
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
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
|
+
};
|
|
1542
1457
|
if (!text) {
|
|
1543
1458
|
throw new Error("text is null");
|
|
1544
1459
|
}
|
|
1545
|
-
|
|
1546
|
-
let error = null;
|
|
1547
|
-
let screenshotId = null;
|
|
1548
|
-
let screenshotPath = null;
|
|
1549
|
-
const info = {};
|
|
1550
|
-
info.log = "***** verify element " + selectors.element_name + " contains text " + text + " *****\n";
|
|
1551
|
-
info.operation = "containsText";
|
|
1552
|
-
info.selectors = selectors;
|
|
1460
|
+
text = unEscapeString(text);
|
|
1553
1461
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
1554
1462
|
if (newValue !== text) {
|
|
1555
1463
|
this.logger.info(text + "=" + newValue);
|
|
1556
1464
|
text = newValue;
|
|
1557
1465
|
}
|
|
1558
|
-
info.value = text;
|
|
1559
1466
|
let foundObj = null;
|
|
1560
1467
|
try {
|
|
1561
|
-
|
|
1468
|
+
await _preCommand(state, this);
|
|
1469
|
+
foundObj = await this._getText(selectors, climb, _params, options, state.info, world);
|
|
1562
1470
|
if (foundObj && foundObj.element) {
|
|
1563
|
-
await this.scrollIfNeeded(foundObj.element, info);
|
|
1471
|
+
await this.scrollIfNeeded(foundObj.element, state.info);
|
|
1564
1472
|
}
|
|
1565
|
-
|
|
1473
|
+
await _screenshot(state, this);
|
|
1566
1474
|
const dateAlternatives = findDateAlternatives(text);
|
|
1567
1475
|
const numberAlternatives = findNumberAlternatives(text);
|
|
1568
1476
|
if (dateAlternatives.date) {
|
|
1569
1477
|
for (let i = 0; i < dateAlternatives.dates.length; i++) {
|
|
1570
|
-
if (
|
|
1571
|
-
|
|
1572
|
-
return info;
|
|
1478
|
+
if (foundObj?.text.includes(dateAlternatives.dates[i]) ||
|
|
1479
|
+
foundObj?.value?.includes(dateAlternatives.dates[i])) {
|
|
1480
|
+
return state.info;
|
|
1573
1481
|
}
|
|
1574
1482
|
}
|
|
1575
1483
|
throw new Error("element doesn't contain text " + text);
|
|
1576
1484
|
}
|
|
1577
1485
|
else if (numberAlternatives.number) {
|
|
1578
1486
|
for (let i = 0; i < numberAlternatives.numbers.length; i++) {
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1581
|
-
return info;
|
|
1487
|
+
if (foundObj?.text.includes(numberAlternatives.numbers[i]) ||
|
|
1488
|
+
foundObj?.value?.includes(numberAlternatives.numbers[i])) {
|
|
1489
|
+
return state.info;
|
|
1582
1490
|
}
|
|
1583
1491
|
}
|
|
1584
1492
|
throw new Error("element doesn't contain text " + text);
|
|
1585
1493
|
}
|
|
1586
|
-
else if (!
|
|
1587
|
-
info.foundText = foundObj
|
|
1588
|
-
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;
|
|
1589
1497
|
throw new Error("element doesn't contain text " + text);
|
|
1590
1498
|
}
|
|
1591
|
-
return info;
|
|
1499
|
+
return state.info;
|
|
1592
1500
|
}
|
|
1593
1501
|
catch (e) {
|
|
1594
|
-
|
|
1595
|
-
this.logger.error("verify element contains text failed " + info.log);
|
|
1596
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1597
|
-
info.screenshotPath = screenshotPath;
|
|
1598
|
-
Object.assign(e, { info: info });
|
|
1599
|
-
error = e;
|
|
1600
|
-
throw e;
|
|
1502
|
+
await _commandError(state, e, this);
|
|
1601
1503
|
}
|
|
1602
1504
|
finally {
|
|
1603
|
-
|
|
1604
|
-
this._reportToWorld(world, {
|
|
1605
|
-
element_name: selectors.element_name,
|
|
1606
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1607
|
-
text: `Verify element contains text: ${text}`,
|
|
1608
|
-
value: text,
|
|
1609
|
-
screenshotId: foundObj === null || foundObj === void 0 ? void 0 : foundObj.screenshotId,
|
|
1610
|
-
result: error
|
|
1611
|
-
? {
|
|
1612
|
-
status: "FAILED",
|
|
1613
|
-
startTime,
|
|
1614
|
-
endTime,
|
|
1615
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1616
|
-
}
|
|
1617
|
-
: {
|
|
1618
|
-
status: "PASSED",
|
|
1619
|
-
startTime,
|
|
1620
|
-
endTime,
|
|
1621
|
-
},
|
|
1622
|
-
info: info,
|
|
1623
|
-
});
|
|
1505
|
+
_commandFinally(state, this);
|
|
1624
1506
|
}
|
|
1625
1507
|
}
|
|
1626
1508
|
_getDataFile(world = null) {
|
|
@@ -1849,7 +1731,6 @@ class StableBrowser {
|
|
|
1849
1731
|
}
|
|
1850
1732
|
async takeScreenshot(screenshotPath) {
|
|
1851
1733
|
const playContext = this.context.playContext;
|
|
1852
|
-
const client = await playContext.newCDPSession(this.page);
|
|
1853
1734
|
// Using CDP to capture the screenshot
|
|
1854
1735
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
1855
1736
|
document.body.scrollWidth,
|
|
@@ -1859,163 +1740,105 @@ class StableBrowser {
|
|
|
1859
1740
|
document.body.clientWidth,
|
|
1860
1741
|
document.documentElement.clientWidth,
|
|
1861
1742
|
])));
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
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();
|
|
1882
1764
|
}
|
|
1883
|
-
let screenshotBuffer = Buffer.from(data, "base64");
|
|
1884
1765
|
let image = await Jimp.read(screenshotBuffer);
|
|
1885
1766
|
// Get the image dimensions
|
|
1886
1767
|
const { width, height } = image.bitmap;
|
|
1768
|
+
const resizeRatio = viewportWidth / width;
|
|
1887
1769
|
// Resize the image to fit within the viewport dimensions without enlarging
|
|
1888
|
-
if (width > viewportWidth
|
|
1889
|
-
image = image.resize({ w: viewportWidth, h:
|
|
1770
|
+
if (width > viewportWidth) {
|
|
1771
|
+
image = image.resize({ w: viewportWidth, h: height * resizeRatio }); // Resize the image while maintaining aspect ratio
|
|
1890
1772
|
await image.write(screenshotPath);
|
|
1891
1773
|
}
|
|
1892
1774
|
else {
|
|
1893
1775
|
fs.writeFileSync(screenshotPath, screenshotBuffer);
|
|
1894
1776
|
}
|
|
1895
|
-
await client.detach();
|
|
1896
1777
|
}
|
|
1897
1778
|
async verifyElementExistInPage(selectors, _params = null, options = {}, world = null) {
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
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
|
+
};
|
|
1903
1789
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1904
|
-
const info = {};
|
|
1905
|
-
info.log = "***** verify element " + selectors.element_name + " exists in page *****\n";
|
|
1906
|
-
info.operation = "verify";
|
|
1907
|
-
info.selectors = selectors;
|
|
1908
1790
|
try {
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
}
|
|
1913
|
-
await this._highlightElements(element);
|
|
1914
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1915
|
-
await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
1916
|
-
return info;
|
|
1791
|
+
await _preCommand(state, this);
|
|
1792
|
+
await expect(state.element).toHaveCount(1, { timeout: 10000 });
|
|
1793
|
+
return state.info;
|
|
1917
1794
|
}
|
|
1918
1795
|
catch (e) {
|
|
1919
|
-
|
|
1920
|
-
this.logger.error("verify failed " + info.log);
|
|
1921
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1922
|
-
info.screenshotPath = screenshotPath;
|
|
1923
|
-
Object.assign(e, { info: info });
|
|
1924
|
-
error = e;
|
|
1925
|
-
throw e;
|
|
1796
|
+
await _commandError(state, e, this);
|
|
1926
1797
|
}
|
|
1927
1798
|
finally {
|
|
1928
|
-
|
|
1929
|
-
this._reportToWorld(world, {
|
|
1930
|
-
element_name: selectors.element_name,
|
|
1931
|
-
type: Types.VERIFY_ELEMENT_CONTAINS_TEXT,
|
|
1932
|
-
text: "Verify element exists in page",
|
|
1933
|
-
screenshotId,
|
|
1934
|
-
result: error
|
|
1935
|
-
? {
|
|
1936
|
-
status: "FAILED",
|
|
1937
|
-
startTime,
|
|
1938
|
-
endTime,
|
|
1939
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
1940
|
-
}
|
|
1941
|
-
: {
|
|
1942
|
-
status: "PASSED",
|
|
1943
|
-
startTime,
|
|
1944
|
-
endTime,
|
|
1945
|
-
},
|
|
1946
|
-
info: info,
|
|
1947
|
-
});
|
|
1799
|
+
_commandFinally(state, this);
|
|
1948
1800
|
}
|
|
1949
1801
|
}
|
|
1950
1802
|
async extractAttribute(selectors, attribute, variable, _params = null, options = {}, world = null) {
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
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
|
+
};
|
|
1956
1815
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1957
|
-
const info = {};
|
|
1958
|
-
info.log = "***** extract attribute " + attribute + " from " + selectors.element_name + " *****\n";
|
|
1959
|
-
info.operation = "extract";
|
|
1960
|
-
info.selectors = selectors;
|
|
1961
1816
|
try {
|
|
1962
|
-
|
|
1963
|
-
await this._highlightElements(element);
|
|
1964
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1817
|
+
await _preCommand(state, this);
|
|
1965
1818
|
switch (attribute) {
|
|
1966
1819
|
case "inner_text":
|
|
1967
|
-
|
|
1820
|
+
state.value = await state.element.innerText();
|
|
1968
1821
|
break;
|
|
1969
1822
|
case "href":
|
|
1970
|
-
|
|
1823
|
+
state.value = await state.element.getAttribute("href");
|
|
1971
1824
|
break;
|
|
1972
1825
|
case "value":
|
|
1973
|
-
|
|
1826
|
+
state.value = await state.element.inputValue();
|
|
1974
1827
|
break;
|
|
1975
1828
|
default:
|
|
1976
|
-
|
|
1829
|
+
state.value = await state.element.getAttribute(attribute);
|
|
1977
1830
|
break;
|
|
1978
1831
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
this.setTestData({ [variable]: info.value }, world);
|
|
1984
|
-
this.logger.info("set test data: " + variable + "=" + info.value);
|
|
1985
|
-
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;
|
|
1986
1836
|
}
|
|
1987
1837
|
catch (e) {
|
|
1988
|
-
|
|
1989
|
-
this.logger.error("extract failed " + info.log);
|
|
1990
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
1991
|
-
info.screenshotPath = screenshotPath;
|
|
1992
|
-
Object.assign(e, { info: info });
|
|
1993
|
-
error = e;
|
|
1994
|
-
throw e;
|
|
1838
|
+
await _commandError(state, e, this);
|
|
1995
1839
|
}
|
|
1996
1840
|
finally {
|
|
1997
|
-
|
|
1998
|
-
this._reportToWorld(world, {
|
|
1999
|
-
element_name: selectors.element_name,
|
|
2000
|
-
type: Types.EXTRACT_ATTRIBUTE,
|
|
2001
|
-
variable: variable,
|
|
2002
|
-
value: info.value,
|
|
2003
|
-
text: "Extract attribute from element",
|
|
2004
|
-
screenshotId,
|
|
2005
|
-
result: error
|
|
2006
|
-
? {
|
|
2007
|
-
status: "FAILED",
|
|
2008
|
-
startTime,
|
|
2009
|
-
endTime,
|
|
2010
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2011
|
-
}
|
|
2012
|
-
: {
|
|
2013
|
-
status: "PASSED",
|
|
2014
|
-
startTime,
|
|
2015
|
-
endTime,
|
|
2016
|
-
},
|
|
2017
|
-
info: info,
|
|
2018
|
-
});
|
|
1841
|
+
_commandFinally(state, this);
|
|
2019
1842
|
}
|
|
2020
1843
|
}
|
|
2021
1844
|
async extractEmailData(emailAddress, options, world) {
|
|
@@ -2092,7 +1915,8 @@ class StableBrowser {
|
|
|
2092
1915
|
catch (e) {
|
|
2093
1916
|
errorCount++;
|
|
2094
1917
|
if (errorCount > 3) {
|
|
2095
|
-
throw e;
|
|
1918
|
+
// throw e;
|
|
1919
|
+
await _commandError({ text: "extractEmailData", operation: "extractEmailData", emailAddress, info: {} }, e, this);
|
|
2096
1920
|
}
|
|
2097
1921
|
// ignore
|
|
2098
1922
|
}
|
|
@@ -2203,7 +2027,8 @@ class StableBrowser {
|
|
|
2203
2027
|
info.screenshotPath = screenshotPath;
|
|
2204
2028
|
Object.assign(e, { info: info });
|
|
2205
2029
|
error = e;
|
|
2206
|
-
throw e;
|
|
2030
|
+
// throw e;
|
|
2031
|
+
await _commandError({ text: "verifyPagePath", operation: "verifyPagePath", pathPart, info }, e, this);
|
|
2207
2032
|
}
|
|
2208
2033
|
finally {
|
|
2209
2034
|
const endTime = Date.now();
|
|
@@ -2216,7 +2041,7 @@ class StableBrowser {
|
|
|
2216
2041
|
status: "FAILED",
|
|
2217
2042
|
startTime,
|
|
2218
2043
|
endTime,
|
|
2219
|
-
message: error
|
|
2044
|
+
message: error?.message,
|
|
2220
2045
|
}
|
|
2221
2046
|
: {
|
|
2222
2047
|
status: "PASSED",
|
|
@@ -2229,52 +2054,58 @@ class StableBrowser {
|
|
|
2229
2054
|
}
|
|
2230
2055
|
async verifyTextExistInPage(text, options = {}, world = null) {
|
|
2231
2056
|
text = unEscapeString(text);
|
|
2232
|
-
const
|
|
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
|
+
};
|
|
2233
2069
|
const timeout = this._getLoadTimeout(options);
|
|
2234
|
-
let error = null;
|
|
2235
|
-
let screenshotId = null;
|
|
2236
|
-
let screenshotPath = null;
|
|
2237
2070
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
2238
|
-
const info = {};
|
|
2239
|
-
info.log = "***** verify text " + text + " exists in page *****\n";
|
|
2240
|
-
info.operation = "verifyTextExistInPage";
|
|
2241
2071
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
2242
2072
|
if (newValue !== text) {
|
|
2243
2073
|
this.logger.info(text + "=" + newValue);
|
|
2244
2074
|
text = newValue;
|
|
2245
2075
|
}
|
|
2246
|
-
info.text = text;
|
|
2247
2076
|
let dateAlternatives = findDateAlternatives(text);
|
|
2248
2077
|
let numberAlternatives = findNumberAlternatives(text);
|
|
2249
2078
|
try {
|
|
2079
|
+
await _preCommand(state, this);
|
|
2080
|
+
state.info.text = text;
|
|
2250
2081
|
while (true) {
|
|
2251
2082
|
const frames = this.page.frames();
|
|
2252
2083
|
let results = [];
|
|
2253
2084
|
for (let i = 0; i < frames.length; i++) {
|
|
2254
2085
|
if (dateAlternatives.date) {
|
|
2255
2086
|
for (let j = 0; j < dateAlternatives.dates.length; j++) {
|
|
2256
|
-
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, {});
|
|
2257
2088
|
result.frame = frames[i];
|
|
2258
2089
|
results.push(result);
|
|
2259
2090
|
}
|
|
2260
2091
|
}
|
|
2261
2092
|
else if (numberAlternatives.number) {
|
|
2262
2093
|
for (let j = 0; j < numberAlternatives.numbers.length; j++) {
|
|
2263
|
-
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, {});
|
|
2264
2095
|
result.frame = frames[i];
|
|
2265
2096
|
results.push(result);
|
|
2266
2097
|
}
|
|
2267
2098
|
}
|
|
2268
2099
|
else {
|
|
2269
|
-
const result = await this._locateElementByText(frames[i], text, "
|
|
2100
|
+
const result = await this._locateElementByText(frames[i], text, "*:not(script, style, head)", true, true, {});
|
|
2270
2101
|
result.frame = frames[i];
|
|
2271
2102
|
results.push(result);
|
|
2272
2103
|
}
|
|
2273
2104
|
}
|
|
2274
|
-
info.results = results;
|
|
2105
|
+
state.info.results = results;
|
|
2275
2106
|
const resultWithElementsFound = results.filter((result) => result.elementCount > 0);
|
|
2276
2107
|
if (resultWithElementsFound.length === 0) {
|
|
2277
|
-
if (Date.now() - startTime > timeout) {
|
|
2108
|
+
if (Date.now() - state.startTime > timeout) {
|
|
2278
2109
|
throw new Error(`Text ${text} not found in page`);
|
|
2279
2110
|
}
|
|
2280
2111
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
@@ -2286,44 +2117,89 @@ class StableBrowser {
|
|
|
2286
2117
|
await this._highlightElements(frame, dataAttribute);
|
|
2287
2118
|
const element = await frame.$(dataAttribute);
|
|
2288
2119
|
if (element) {
|
|
2289
|
-
await this.scrollIfNeeded(element, info);
|
|
2120
|
+
await this.scrollIfNeeded(element, state.info);
|
|
2290
2121
|
await element.dispatchEvent("bvt_verify_page_contains_text");
|
|
2291
2122
|
}
|
|
2292
2123
|
}
|
|
2293
|
-
|
|
2294
|
-
return info;
|
|
2124
|
+
await _screenshot(state, this);
|
|
2125
|
+
return state.info;
|
|
2295
2126
|
}
|
|
2296
2127
|
// await expect(element).toHaveCount(1, { timeout: 10000 });
|
|
2297
2128
|
}
|
|
2298
2129
|
catch (e) {
|
|
2299
|
-
|
|
2300
|
-
this.logger.error("verify text exist in page failed " + info.log);
|
|
2301
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world, info));
|
|
2302
|
-
info.screenshotPath = screenshotPath;
|
|
2303
|
-
Object.assign(e, { info: info });
|
|
2304
|
-
error = e;
|
|
2305
|
-
throw e;
|
|
2130
|
+
await _commandError(state, e, this);
|
|
2306
2131
|
}
|
|
2307
2132
|
finally {
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
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
|
+
}
|
|
2319
2172
|
}
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
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);
|
|
2327
2203
|
}
|
|
2328
2204
|
}
|
|
2329
2205
|
_getServerUrl() {
|
|
@@ -2386,7 +2262,8 @@ class StableBrowser {
|
|
|
2386
2262
|
info.screenshotPath = screenshotPath;
|
|
2387
2263
|
Object.assign(e, { info: info });
|
|
2388
2264
|
error = e;
|
|
2389
|
-
throw e;
|
|
2265
|
+
// throw e;
|
|
2266
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", text, info }, e, this);
|
|
2390
2267
|
}
|
|
2391
2268
|
finally {
|
|
2392
2269
|
const endTime = Date.now();
|
|
@@ -2399,7 +2276,7 @@ class StableBrowser {
|
|
|
2399
2276
|
status: "FAILED",
|
|
2400
2277
|
startTime,
|
|
2401
2278
|
endTime,
|
|
2402
|
-
message: error
|
|
2279
|
+
message: error?.message,
|
|
2403
2280
|
}
|
|
2404
2281
|
: {
|
|
2405
2282
|
status: "PASSED",
|
|
@@ -2431,7 +2308,7 @@ class StableBrowser {
|
|
|
2431
2308
|
this.logger.info("Table data verified");
|
|
2432
2309
|
}
|
|
2433
2310
|
async getTableData(selectors, _params = null, options = {}, world = null) {
|
|
2434
|
-
|
|
2311
|
+
_validateSelectors(selectors);
|
|
2435
2312
|
const startTime = Date.now();
|
|
2436
2313
|
let error = null;
|
|
2437
2314
|
let screenshotId = null;
|
|
@@ -2453,7 +2330,8 @@ class StableBrowser {
|
|
|
2453
2330
|
info.screenshotPath = screenshotPath;
|
|
2454
2331
|
Object.assign(e, { info: info });
|
|
2455
2332
|
error = e;
|
|
2456
|
-
throw e;
|
|
2333
|
+
// throw e;
|
|
2334
|
+
await _commandError({ text: "getTableData", operation: "getTableData", selectors, info }, e, this);
|
|
2457
2335
|
}
|
|
2458
2336
|
finally {
|
|
2459
2337
|
const endTime = Date.now();
|
|
@@ -2467,7 +2345,7 @@ class StableBrowser {
|
|
|
2467
2345
|
status: "FAILED",
|
|
2468
2346
|
startTime,
|
|
2469
2347
|
endTime,
|
|
2470
|
-
message: error
|
|
2348
|
+
message: error?.message,
|
|
2471
2349
|
}
|
|
2472
2350
|
: {
|
|
2473
2351
|
status: "PASSED",
|
|
@@ -2479,7 +2357,7 @@ class StableBrowser {
|
|
|
2479
2357
|
}
|
|
2480
2358
|
}
|
|
2481
2359
|
async analyzeTable(selectors, query, operator, value, _params = null, options = {}, world = null) {
|
|
2482
|
-
|
|
2360
|
+
_validateSelectors(selectors);
|
|
2483
2361
|
if (!query) {
|
|
2484
2362
|
throw new Error("query is null");
|
|
2485
2363
|
}
|
|
@@ -2618,7 +2496,8 @@ class StableBrowser {
|
|
|
2618
2496
|
info.screenshotPath = screenshotPath;
|
|
2619
2497
|
Object.assign(e, { info: info });
|
|
2620
2498
|
error = e;
|
|
2621
|
-
throw e;
|
|
2499
|
+
// throw e;
|
|
2500
|
+
await _commandError({ text: "analyzeTable", operation: "analyzeTable", selectors, query, operator, value }, e, this);
|
|
2622
2501
|
}
|
|
2623
2502
|
finally {
|
|
2624
2503
|
const endTime = Date.now();
|
|
@@ -2632,7 +2511,7 @@ class StableBrowser {
|
|
|
2632
2511
|
status: "FAILED",
|
|
2633
2512
|
startTime,
|
|
2634
2513
|
endTime,
|
|
2635
|
-
message: error
|
|
2514
|
+
message: error?.message,
|
|
2636
2515
|
}
|
|
2637
2516
|
: {
|
|
2638
2517
|
status: "PASSED",
|
|
@@ -2644,27 +2523,7 @@ class StableBrowser {
|
|
|
2644
2523
|
}
|
|
2645
2524
|
}
|
|
2646
2525
|
async _replaceWithLocalData(value, world, _decrypt = true, totpWait = true) {
|
|
2647
|
-
|
|
2648
|
-
return value;
|
|
2649
|
-
}
|
|
2650
|
-
// find all the accurance of {{(.*?)}} and replace with the value
|
|
2651
|
-
let regex = /{{(.*?)}}/g;
|
|
2652
|
-
let matches = value.match(regex);
|
|
2653
|
-
if (matches) {
|
|
2654
|
-
const testData = this.getTestData(world);
|
|
2655
|
-
for (let i = 0; i < matches.length; i++) {
|
|
2656
|
-
let match = matches[i];
|
|
2657
|
-
let key = match.substring(2, match.length - 2);
|
|
2658
|
-
let newValue = objectPath.get(testData, key, null);
|
|
2659
|
-
if (newValue !== null) {
|
|
2660
|
-
value = value.replace(match, newValue);
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
if ((value.startsWith("secret:") || value.startsWith("totp:")) && _decrypt) {
|
|
2665
|
-
return await decrypt(value, null, totpWait);
|
|
2666
|
-
}
|
|
2667
|
-
return value;
|
|
2526
|
+
return await replaceWithLocalTestData(value, world, _decrypt, totpWait, this.context, this);
|
|
2668
2527
|
}
|
|
2669
2528
|
_getLoadTimeout(options) {
|
|
2670
2529
|
let timeout = 15000;
|
|
@@ -2724,7 +2583,7 @@ class StableBrowser {
|
|
|
2724
2583
|
status: "FAILED",
|
|
2725
2584
|
startTime,
|
|
2726
2585
|
endTime,
|
|
2727
|
-
message: error
|
|
2586
|
+
message: error?.message,
|
|
2728
2587
|
}
|
|
2729
2588
|
: {
|
|
2730
2589
|
status: "PASSED",
|
|
@@ -2735,39 +2594,28 @@ class StableBrowser {
|
|
|
2735
2594
|
}
|
|
2736
2595
|
}
|
|
2737
2596
|
async closePage(options = {}, world = null) {
|
|
2738
|
-
const
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
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
|
+
};
|
|
2743
2609
|
try {
|
|
2610
|
+
await _preCommand(state, this);
|
|
2744
2611
|
await this.page.close();
|
|
2745
2612
|
}
|
|
2746
2613
|
catch (e) {
|
|
2747
2614
|
console.log(".");
|
|
2615
|
+
await _commandError(state, e, this);
|
|
2748
2616
|
}
|
|
2749
2617
|
finally {
|
|
2750
|
-
|
|
2751
|
-
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
2752
|
-
const endTime = Date.now();
|
|
2753
|
-
this._reportToWorld(world, {
|
|
2754
|
-
type: Types.CLOSE_PAGE,
|
|
2755
|
-
text: "close page",
|
|
2756
|
-
screenshotId,
|
|
2757
|
-
result: error
|
|
2758
|
-
? {
|
|
2759
|
-
status: "FAILED",
|
|
2760
|
-
startTime,
|
|
2761
|
-
endTime,
|
|
2762
|
-
message: error === null || error === void 0 ? void 0 : error.message,
|
|
2763
|
-
}
|
|
2764
|
-
: {
|
|
2765
|
-
status: "PASSED",
|
|
2766
|
-
startTime,
|
|
2767
|
-
endTime,
|
|
2768
|
-
},
|
|
2769
|
-
info: info,
|
|
2770
|
-
});
|
|
2618
|
+
_commandFinally(state, this);
|
|
2771
2619
|
}
|
|
2772
2620
|
}
|
|
2773
2621
|
async setViewportSize(width, hight, options = {}, world = null) {
|
|
@@ -2787,6 +2635,7 @@ class StableBrowser {
|
|
|
2787
2635
|
}
|
|
2788
2636
|
catch (e) {
|
|
2789
2637
|
console.log(".");
|
|
2638
|
+
await _commandError({ text: "setViewportSize", operation: "setViewportSize", width, hight, info }, e, this);
|
|
2790
2639
|
}
|
|
2791
2640
|
finally {
|
|
2792
2641
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -2801,7 +2650,7 @@ class StableBrowser {
|
|
|
2801
2650
|
status: "FAILED",
|
|
2802
2651
|
startTime,
|
|
2803
2652
|
endTime,
|
|
2804
|
-
message: error
|
|
2653
|
+
message: error?.message,
|
|
2805
2654
|
}
|
|
2806
2655
|
: {
|
|
2807
2656
|
status: "PASSED",
|
|
@@ -2823,6 +2672,7 @@ class StableBrowser {
|
|
|
2823
2672
|
}
|
|
2824
2673
|
catch (e) {
|
|
2825
2674
|
console.log(".");
|
|
2675
|
+
await _commandError({ text: "reloadPage", operation: "reloadPage", info }, e, this);
|
|
2826
2676
|
}
|
|
2827
2677
|
finally {
|
|
2828
2678
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
@@ -2837,7 +2687,7 @@ class StableBrowser {
|
|
|
2837
2687
|
status: "FAILED",
|
|
2838
2688
|
startTime,
|
|
2839
2689
|
endTime,
|
|
2840
|
-
message: error
|
|
2690
|
+
message: error?.message,
|
|
2841
2691
|
}
|
|
2842
2692
|
: {
|
|
2843
2693
|
status: "PASSED",
|
|
@@ -2870,6 +2720,31 @@ class StableBrowser {
|
|
|
2870
2720
|
}
|
|
2871
2721
|
world.attach(JSON.stringify(properties), { mediaType: "application/json" });
|
|
2872
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
|
+
}
|
|
2873
2748
|
}
|
|
2874
2749
|
function createTimedPromise(promise, label) {
|
|
2875
2750
|
return promise
|