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