automation_model 1.0.796-dev → 1.0.796-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.js +40 -12
- package/lib/api.js.map +1 -1
- package/lib/auto_page.d.ts +1 -1
- package/lib/auto_page.js +103 -75
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.d.ts +2 -3
- package/lib/browser_manager.js +115 -71
- package/lib/browser_manager.js.map +1 -1
- package/lib/bruno.js.map +1 -1
- package/lib/check_performance.d.ts +1 -0
- package/lib/check_performance.js +57 -0
- package/lib/check_performance.js.map +1 -0
- package/lib/command_common.d.ts +2 -2
- package/lib/command_common.js +30 -23
- package/lib/command_common.js.map +1 -1
- package/lib/file_checker.js +7 -0
- package/lib/file_checker.js.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/init_browser.d.ts +1 -2
- package/lib/init_browser.js +137 -128
- package/lib/init_browser.js.map +1 -1
- package/lib/locator_log.js.map +1 -1
- package/lib/network.d.ts +2 -2
- package/lib/network.js +183 -120
- package/lib/network.js.map +1 -1
- package/lib/route.d.ts +64 -2
- package/lib/route.js +492 -244
- package/lib/route.js.map +1 -1
- package/lib/scripts/axe.mini.js +23978 -1
- package/lib/snapshot_validation.js +3 -0
- package/lib/snapshot_validation.js.map +1 -1
- package/lib/stable_browser.d.ts +14 -8
- package/lib/stable_browser.js +453 -87
- package/lib/stable_browser.js.map +1 -1
- package/lib/table_helper.js +14 -0
- package/lib/table_helper.js.map +1 -1
- package/lib/test_context.d.ts +1 -0
- package/lib/test_context.js +1 -0
- package/lib/test_context.js.map +1 -1
- package/lib/utils.d.ts +7 -3
- package/lib/utils.js +162 -25
- package/lib/utils.js.map +1 -1
- package/package.json +18 -11
package/lib/stable_browser.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
|
+
import { check_performance } from "./check_performance.js";
|
|
2
3
|
import { expect } from "@playwright/test";
|
|
3
4
|
import dayjs from "dayjs";
|
|
4
5
|
import fs from "fs";
|
|
@@ -10,6 +11,7 @@ import { getDateTimeValue } from "./date_time.js";
|
|
|
10
11
|
import drawRectangle from "./drawRect.js";
|
|
11
12
|
//import { closeUnexpectedPopups } from "./popups.js";
|
|
12
13
|
import { getTableCells, getTableData } from "./table_analyze.js";
|
|
14
|
+
import errorStackParser from "error-stack-parser";
|
|
13
15
|
import { _convertToRegexQuery, _copyContext, _fixLocatorUsingParams, _fixUsingParams, _getServerUrl, extractStepExampleParameters, KEYBOARD_EVENTS, maskValue, replaceWithLocalTestData, scrollPageToLoadLazyElements, unEscapeString, _getDataFile, testForRegex, performAction, _getTestData, } from "./utils.js";
|
|
14
16
|
import csv from "csv-parser";
|
|
15
17
|
import { Readable } from "node:stream";
|
|
@@ -26,6 +28,7 @@ import { _findCellArea, findElementsInArea } from "./table_helper.js";
|
|
|
26
28
|
import { highlightSnapshot, snapshotValidation } from "./snapshot_validation.js";
|
|
27
29
|
import { loadBrunoParams } from "./bruno.js";
|
|
28
30
|
import { registerAfterStepRoutes, registerBeforeStepRoutes } from "./route.js";
|
|
31
|
+
import { existsSync } from "node:fs";
|
|
29
32
|
export const Types = {
|
|
30
33
|
CLICK: "click_element",
|
|
31
34
|
WAIT_ELEMENT: "wait_element",
|
|
@@ -44,6 +47,7 @@ export const Types = {
|
|
|
44
47
|
VERIFY_PAGE_CONTAINS_NO_TEXT: "verify_page_contains_no_text",
|
|
45
48
|
ANALYZE_TABLE: "analyze_table",
|
|
46
49
|
SELECT: "select_combobox", //
|
|
50
|
+
VERIFY_PROPERTY: "verify_element_property",
|
|
47
51
|
VERIFY_PAGE_PATH: "verify_page_path",
|
|
48
52
|
VERIFY_PAGE_TITLE: "verify_page_title",
|
|
49
53
|
TYPE_PRESS: "type_press",
|
|
@@ -62,12 +66,11 @@ export const Types = {
|
|
|
62
66
|
SET_INPUT: "set_input",
|
|
63
67
|
WAIT_FOR_TEXT_TO_DISAPPEAR: "wait_for_text_to_disappear",
|
|
64
68
|
VERIFY_ATTRIBUTE: "verify_element_attribute",
|
|
65
|
-
VERIFY_PROPERTY: "verify_element_property",
|
|
66
69
|
VERIFY_TEXT_WITH_RELATION: "verify_text_with_relation",
|
|
67
70
|
BRUNO: "bruno",
|
|
68
|
-
SNAPSHOT_VALIDATION: "snapshot_validation",
|
|
69
71
|
VERIFY_FILE_EXISTS: "verify_file_exists",
|
|
70
72
|
SET_INPUT_FILES: "set_input_files",
|
|
73
|
+
SNAPSHOT_VALIDATION: "snapshot_validation",
|
|
71
74
|
REPORT_COMMAND: "report_command",
|
|
72
75
|
STEP_COMPLETE: "step_complete",
|
|
73
76
|
SLEEP: "sleep",
|
|
@@ -84,6 +87,7 @@ class StableBrowser {
|
|
|
84
87
|
context;
|
|
85
88
|
world;
|
|
86
89
|
fastMode;
|
|
90
|
+
stepTags;
|
|
87
91
|
project_path = null;
|
|
88
92
|
webLogFile = null;
|
|
89
93
|
networkLogger = null;
|
|
@@ -92,14 +96,17 @@ class StableBrowser {
|
|
|
92
96
|
tags = null;
|
|
93
97
|
isRecording = false;
|
|
94
98
|
initSnapshotTaken = false;
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
onlyFailuresScreenshot = process.env.SCREENSHOT_ON_FAILURE_ONLY === "true";
|
|
100
|
+
// set to true if the step issue a report
|
|
101
|
+
inStepReport = false;
|
|
102
|
+
constructor(browser, page, logger = null, context = null, world = null, fastMode = false, stepTags = []) {
|
|
97
103
|
this.browser = browser;
|
|
98
104
|
this.page = page;
|
|
99
105
|
this.logger = logger;
|
|
100
106
|
this.context = context;
|
|
101
107
|
this.world = world;
|
|
102
108
|
this.fastMode = fastMode;
|
|
109
|
+
this.stepTags = stepTags;
|
|
103
110
|
if (!this.logger) {
|
|
104
111
|
this.logger = console;
|
|
105
112
|
}
|
|
@@ -132,7 +139,7 @@ class StableBrowser {
|
|
|
132
139
|
this.fastMode = true;
|
|
133
140
|
}
|
|
134
141
|
if (process.env.FAST_MODE === "true") {
|
|
135
|
-
console.log("Fast mode enabled from environment variable");
|
|
142
|
+
// console.log("Fast mode enabled from environment variable");
|
|
136
143
|
this.fastMode = true;
|
|
137
144
|
}
|
|
138
145
|
if (process.env.FAST_MODE === "false") {
|
|
@@ -175,6 +182,7 @@ class StableBrowser {
|
|
|
175
182
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
176
183
|
registerDownloadEvent(this.page, this.world, context);
|
|
177
184
|
page.on("close", async () => {
|
|
185
|
+
// return if browser context is already closed
|
|
178
186
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
179
187
|
this.context.pages.pop();
|
|
180
188
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
@@ -184,7 +192,12 @@ class StableBrowser {
|
|
|
184
192
|
console.log("Switched to page " + title);
|
|
185
193
|
}
|
|
186
194
|
catch (error) {
|
|
187
|
-
|
|
195
|
+
if (error?.message?.includes("Target page, context or browser has been closed")) {
|
|
196
|
+
// Ignore this error
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.error("Error on page close", error);
|
|
200
|
+
}
|
|
188
201
|
}
|
|
189
202
|
}
|
|
190
203
|
});
|
|
@@ -193,7 +206,12 @@ class StableBrowser {
|
|
|
193
206
|
console.log("Switch page: " + (await page.title()));
|
|
194
207
|
}
|
|
195
208
|
catch (e) {
|
|
196
|
-
|
|
209
|
+
if (e?.message?.includes("Target page, context or browser has been closed")) {
|
|
210
|
+
// Ignore this error
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
this.logger.error("error on page load " + e);
|
|
214
|
+
}
|
|
197
215
|
}
|
|
198
216
|
context.pageLoading.status = false;
|
|
199
217
|
}.bind(this));
|
|
@@ -205,7 +223,7 @@ class StableBrowser {
|
|
|
205
223
|
}
|
|
206
224
|
let newContextCreated = false;
|
|
207
225
|
if (!apps[appName]) {
|
|
208
|
-
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder);
|
|
226
|
+
let newContext = await getContext(null, this.context.headless ? this.context.headless : false, this, this.logger, appName, false, this, -1, this.context.reportFolder, null, null, this.tags);
|
|
209
227
|
newContextCreated = true;
|
|
210
228
|
apps[appName] = {
|
|
211
229
|
context: newContext,
|
|
@@ -221,7 +239,7 @@ class StableBrowser {
|
|
|
221
239
|
if (newContextCreated) {
|
|
222
240
|
this.registerEventListeners(this.context);
|
|
223
241
|
await this.goto(this.context.environment.baseUrl);
|
|
224
|
-
if (!this.fastMode) {
|
|
242
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
225
243
|
await this.waitForPageLoad();
|
|
226
244
|
}
|
|
227
245
|
}
|
|
@@ -313,7 +331,7 @@ class StableBrowser {
|
|
|
313
331
|
// async closeUnexpectedPopups() {
|
|
314
332
|
// await closeUnexpectedPopups(this.page);
|
|
315
333
|
// }
|
|
316
|
-
async goto(url, world = null) {
|
|
334
|
+
async goto(url, world = null, options = {}) {
|
|
317
335
|
if (!url) {
|
|
318
336
|
throw new Error("url is null, verify that the environment file is correct");
|
|
319
337
|
}
|
|
@@ -334,16 +352,23 @@ class StableBrowser {
|
|
|
334
352
|
screenshot: false,
|
|
335
353
|
highlight: false,
|
|
336
354
|
};
|
|
355
|
+
let timeout = 60000;
|
|
356
|
+
if (this.configuration && this.configuration.page_timeout) {
|
|
357
|
+
timeout = this.configuration.page_timeout;
|
|
358
|
+
}
|
|
359
|
+
if (options && options["timeout"]) {
|
|
360
|
+
timeout = options["timeout"];
|
|
361
|
+
}
|
|
337
362
|
try {
|
|
338
363
|
await _preCommand(state, this);
|
|
339
364
|
await this.page.goto(url, {
|
|
340
|
-
timeout:
|
|
365
|
+
timeout: timeout,
|
|
341
366
|
});
|
|
342
367
|
await _screenshot(state, this);
|
|
343
368
|
}
|
|
344
369
|
catch (error) {
|
|
345
370
|
console.error("Error on goto", error);
|
|
346
|
-
_commandError(state, error, this);
|
|
371
|
+
await _commandError(state, error, this);
|
|
347
372
|
}
|
|
348
373
|
finally {
|
|
349
374
|
await _commandFinally(state, this);
|
|
@@ -372,7 +397,7 @@ class StableBrowser {
|
|
|
372
397
|
}
|
|
373
398
|
catch (error) {
|
|
374
399
|
console.error("Error on goBack", error);
|
|
375
|
-
_commandError(state, error, this);
|
|
400
|
+
await _commandError(state, error, this);
|
|
376
401
|
}
|
|
377
402
|
finally {
|
|
378
403
|
await _commandFinally(state, this);
|
|
@@ -401,7 +426,7 @@ class StableBrowser {
|
|
|
401
426
|
}
|
|
402
427
|
catch (error) {
|
|
403
428
|
console.error("Error on goForward", error);
|
|
404
|
-
_commandError(state, error, this);
|
|
429
|
+
await _commandError(state, error, this);
|
|
405
430
|
}
|
|
406
431
|
finally {
|
|
407
432
|
await _commandFinally(state, this);
|
|
@@ -505,12 +530,6 @@ class StableBrowser {
|
|
|
505
530
|
if (!el.setAttribute) {
|
|
506
531
|
el = el.parentElement;
|
|
507
532
|
}
|
|
508
|
-
// remove any attributes start with data-blinq-id
|
|
509
|
-
// for (let i = 0; i < el.attributes.length; i++) {
|
|
510
|
-
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
511
|
-
// el.removeAttribute(el.attributes[i].name);
|
|
512
|
-
// }
|
|
513
|
-
// }
|
|
514
533
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
515
534
|
return true;
|
|
516
535
|
}, [tag1, randomToken]))) {
|
|
@@ -680,40 +699,186 @@ class StableBrowser {
|
|
|
680
699
|
}
|
|
681
700
|
return { rerun: false };
|
|
682
701
|
}
|
|
702
|
+
getFilePath() {
|
|
703
|
+
const stackFrames = errorStackParser.parse(new Error());
|
|
704
|
+
const mjsFrames = stackFrames.filter((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
|
|
705
|
+
const stackFrame = mjsFrames[mjsFrames.length - 2];
|
|
706
|
+
const filepath = stackFrame?.fileName;
|
|
707
|
+
if (filepath) {
|
|
708
|
+
let jsonFilePath = filepath.replace(".mjs", ".json");
|
|
709
|
+
if (existsSync(jsonFilePath)) {
|
|
710
|
+
return jsonFilePath;
|
|
711
|
+
}
|
|
712
|
+
const config = this.configuration ?? {};
|
|
713
|
+
if (!config?.locatorsMetadataDir) {
|
|
714
|
+
config.locatorsMetadataDir = "features/step_definitions/locators";
|
|
715
|
+
}
|
|
716
|
+
if (config && config.locatorsMetadataDir) {
|
|
717
|
+
jsonFilePath = path.join(config.locatorsMetadataDir, path.basename(jsonFilePath));
|
|
718
|
+
}
|
|
719
|
+
if (existsSync(jsonFilePath)) {
|
|
720
|
+
return jsonFilePath;
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
getFullElementLocators(selectors, filePath) {
|
|
727
|
+
if (!filePath || !existsSync(filePath)) {
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
731
|
+
try {
|
|
732
|
+
const allElements = JSON.parse(content);
|
|
733
|
+
const element_key = selectors?.element_key;
|
|
734
|
+
if (element_key && allElements[element_key]) {
|
|
735
|
+
return allElements[element_key];
|
|
736
|
+
}
|
|
737
|
+
for (const elementKey in allElements) {
|
|
738
|
+
const element = allElements[elementKey];
|
|
739
|
+
let foundStrategy = null;
|
|
740
|
+
for (const key in element) {
|
|
741
|
+
if (key === "strategy") {
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
const locators = element[key];
|
|
745
|
+
if (!locators || !locators.length) {
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
for (const locator of locators) {
|
|
749
|
+
delete locator.score;
|
|
750
|
+
}
|
|
751
|
+
if (JSON.stringify(locators) === JSON.stringify(selectors.locators)) {
|
|
752
|
+
foundStrategy = key;
|
|
753
|
+
break;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (foundStrategy) {
|
|
757
|
+
return element;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
catch (error) {
|
|
762
|
+
console.error("Error parsing locators from file: " + filePath, error);
|
|
763
|
+
}
|
|
764
|
+
return null;
|
|
765
|
+
}
|
|
683
766
|
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
684
767
|
if (!timeout) {
|
|
685
768
|
timeout = 30000;
|
|
686
769
|
}
|
|
770
|
+
let element = null;
|
|
771
|
+
let allStrategyLocators = null;
|
|
772
|
+
let selectedStrategy = null;
|
|
773
|
+
if (this.tryAllStrategies) {
|
|
774
|
+
allStrategyLocators = this.getFullElementLocators(selectors, this.getFilePath());
|
|
775
|
+
selectedStrategy = allStrategyLocators?.strategy;
|
|
776
|
+
}
|
|
687
777
|
for (let i = 0; i < 3; i++) {
|
|
688
778
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
689
779
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
690
780
|
let selector = selectors.locators[j];
|
|
691
781
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
692
782
|
}
|
|
693
|
-
|
|
783
|
+
if (this.tryAllStrategies && selectedStrategy) {
|
|
784
|
+
const strategyLocators = allStrategyLocators[selectedStrategy];
|
|
785
|
+
let err;
|
|
786
|
+
if (strategyLocators && strategyLocators.length) {
|
|
787
|
+
try {
|
|
788
|
+
selectors.locators = strategyLocators;
|
|
789
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
790
|
+
info.selectedStrategy = selectedStrategy;
|
|
791
|
+
info.log += "element found using strategy " + selectedStrategy + "\n";
|
|
792
|
+
}
|
|
793
|
+
catch (error) {
|
|
794
|
+
err = error;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (!element) {
|
|
798
|
+
for (const key in allStrategyLocators) {
|
|
799
|
+
if (key === "strategy" || key === selectedStrategy) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
const strategyLocators = allStrategyLocators[key];
|
|
803
|
+
if (strategyLocators && strategyLocators.length) {
|
|
804
|
+
try {
|
|
805
|
+
info.log += "using strategy " + key + " with locators " + JSON.stringify(strategyLocators) + "\n";
|
|
806
|
+
selectors.locators = strategyLocators;
|
|
807
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
808
|
+
err = null;
|
|
809
|
+
info.selectedStrategy = key;
|
|
810
|
+
info.log += "element found using strategy " + key + "\n";
|
|
811
|
+
break;
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
err = error;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (err) {
|
|
820
|
+
throw err;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
825
|
+
}
|
|
694
826
|
if (!element.rerun) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
827
|
+
let newElementSelector = "";
|
|
828
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "csschain") {
|
|
829
|
+
const cssSelector = await element.evaluate((el) => {
|
|
830
|
+
function getCssSelector(el) {
|
|
831
|
+
if (!el || el.nodeType !== 1 || el === document.body)
|
|
832
|
+
return el.tagName.toLowerCase();
|
|
833
|
+
const parent = el.parentElement;
|
|
834
|
+
const tag = el.tagName.toLowerCase();
|
|
835
|
+
// Find the index of the element among its siblings of the same tag
|
|
836
|
+
let index = 1;
|
|
837
|
+
for (let sibling = el.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
|
|
838
|
+
if (sibling.tagName === el.tagName) {
|
|
839
|
+
index++;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
// Use nth-child if necessary (i.e., if there's more than one of the same tag)
|
|
843
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
844
|
+
const needsNthChild = siblings.length > 1;
|
|
845
|
+
const selector = needsNthChild ? `${tag}:nth-child(${[...parent.children].indexOf(el) + 1})` : tag;
|
|
846
|
+
return getCssSelector(parent) + " > " + selector;
|
|
847
|
+
}
|
|
848
|
+
const cssSelector = getCssSelector(el);
|
|
849
|
+
return cssSelector;
|
|
850
|
+
});
|
|
851
|
+
newElementSelector = cssSelector;
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
855
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "data-attribute") {
|
|
856
|
+
const dataAttribute = "data-blinq-id";
|
|
857
|
+
await element.evaluate((el, [dataAttribute, randomToken]) => {
|
|
858
|
+
el.setAttribute(dataAttribute, randomToken);
|
|
859
|
+
}, [dataAttribute, randomToken]);
|
|
860
|
+
newElementSelector = `[${dataAttribute}="${randomToken}"]`;
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
// the default case just return the located element
|
|
864
|
+
// will not work for click and type if the locator is placeholder and the placeholder change due to the click event
|
|
865
|
+
return element;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
702
868
|
const scope = element._frame ?? element.page();
|
|
703
|
-
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
704
869
|
let prefixSelector = "";
|
|
705
870
|
const frameControlSelector = " >> internal:control=enter-frame";
|
|
706
871
|
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
707
872
|
if (frameSelectorIndex !== -1) {
|
|
708
873
|
// remove everything after the >> internal:control=enter-frame
|
|
709
874
|
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
710
|
-
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
875
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
711
876
|
}
|
|
712
877
|
// if (element?._frame?._selector) {
|
|
713
878
|
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
714
879
|
// }
|
|
715
880
|
const newSelector = prefixSelector + newElementSelector;
|
|
716
|
-
return scope.locator(newSelector);
|
|
881
|
+
return scope.locator(newSelector).first();
|
|
717
882
|
}
|
|
718
883
|
}
|
|
719
884
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -734,7 +899,7 @@ class StableBrowser {
|
|
|
734
899
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
735
900
|
let frameLocator = frame.selectors[i];
|
|
736
901
|
if (frameLocator.css) {
|
|
737
|
-
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
902
|
+
let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
|
|
738
903
|
if (frameLocator.index) {
|
|
739
904
|
testframescope = framescope.nth(frameLocator.index);
|
|
740
905
|
}
|
|
@@ -812,6 +977,15 @@ class StableBrowser {
|
|
|
812
977
|
});
|
|
813
978
|
}
|
|
814
979
|
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
980
|
+
if (selectors.locators && Array.isArray(selectors.locators)) {
|
|
981
|
+
selectors.locators.forEach((locator) => {
|
|
982
|
+
locator.index = locator.index ?? 0;
|
|
983
|
+
locator.visible = locator.visible ?? true;
|
|
984
|
+
if (locator.visible && locator.css && !locator.css.endsWith(">> visible=true")) {
|
|
985
|
+
locator.css = locator.css + " >> visible=true";
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}
|
|
815
989
|
if (!info) {
|
|
816
990
|
info = {};
|
|
817
991
|
info.failCause = {};
|
|
@@ -967,9 +1141,13 @@ class StableBrowser {
|
|
|
967
1141
|
}
|
|
968
1142
|
}
|
|
969
1143
|
if (foundLocators.length === 1) {
|
|
1144
|
+
let box = null;
|
|
1145
|
+
if (!this.onlyFailuresScreenshot) {
|
|
1146
|
+
box = await foundLocators[0].boundingBox();
|
|
1147
|
+
}
|
|
970
1148
|
result.foundElements.push({
|
|
971
1149
|
locator: foundLocators[0],
|
|
972
|
-
box:
|
|
1150
|
+
box: box,
|
|
973
1151
|
unique: true,
|
|
974
1152
|
});
|
|
975
1153
|
result.locatorIndex = i;
|
|
@@ -1027,7 +1205,7 @@ class StableBrowser {
|
|
|
1027
1205
|
operation: "simpleClick",
|
|
1028
1206
|
log: "***** click on " + elementDescription + " *****\n",
|
|
1029
1207
|
};
|
|
1030
|
-
_preCommand(state, this);
|
|
1208
|
+
await _preCommand(state, this);
|
|
1031
1209
|
const startTime = Date.now();
|
|
1032
1210
|
let timeout = 30000;
|
|
1033
1211
|
if (options && options.timeout) {
|
|
@@ -1076,7 +1254,7 @@ class StableBrowser {
|
|
|
1076
1254
|
operation: "simpleClickType",
|
|
1077
1255
|
log: "***** click type on " + elementDescription + " *****\n",
|
|
1078
1256
|
};
|
|
1079
|
-
_preCommand(state, this);
|
|
1257
|
+
await _preCommand(state, this);
|
|
1080
1258
|
const startTime = Date.now();
|
|
1081
1259
|
let timeout = 30000;
|
|
1082
1260
|
if (options && options.timeout) {
|
|
@@ -1124,11 +1302,22 @@ class StableBrowser {
|
|
|
1124
1302
|
operation: "click",
|
|
1125
1303
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1126
1304
|
};
|
|
1305
|
+
check_performance("click_all ***", this.context, true);
|
|
1306
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
1307
|
+
if (stepFastMode) {
|
|
1308
|
+
state.onlyFailuresScreenshot = true;
|
|
1309
|
+
state.scroll = false;
|
|
1310
|
+
state.highlight = false;
|
|
1311
|
+
}
|
|
1127
1312
|
try {
|
|
1313
|
+
check_performance("click_preCommand", this.context, true);
|
|
1128
1314
|
await _preCommand(state, this);
|
|
1315
|
+
check_performance("click_preCommand", this.context, false);
|
|
1129
1316
|
await performAction("click", state.element, options, this, state, _params);
|
|
1130
|
-
if (!this.fastMode) {
|
|
1131
|
-
|
|
1317
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
1318
|
+
check_performance("click_waitForPageLoad", this.context, true);
|
|
1319
|
+
await this.waitForPageLoad({ noSleep: true });
|
|
1320
|
+
check_performance("click_waitForPageLoad", this.context, false);
|
|
1132
1321
|
}
|
|
1133
1322
|
return state.info;
|
|
1134
1323
|
}
|
|
@@ -1136,7 +1325,13 @@ class StableBrowser {
|
|
|
1136
1325
|
await _commandError(state, e, this);
|
|
1137
1326
|
}
|
|
1138
1327
|
finally {
|
|
1328
|
+
check_performance("click_commandFinally", this.context, true);
|
|
1139
1329
|
await _commandFinally(state, this);
|
|
1330
|
+
check_performance("click_commandFinally", this.context, false);
|
|
1331
|
+
check_performance("click_all ***", this.context, false);
|
|
1332
|
+
if (this.context.profile) {
|
|
1333
|
+
console.log(JSON.stringify(this.context.profile, null, 2));
|
|
1334
|
+
}
|
|
1140
1335
|
}
|
|
1141
1336
|
}
|
|
1142
1337
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1228,7 +1423,7 @@ class StableBrowser {
|
|
|
1228
1423
|
}
|
|
1229
1424
|
}
|
|
1230
1425
|
}
|
|
1231
|
-
await this.waitForPageLoad();
|
|
1426
|
+
//await this.waitForPageLoad();
|
|
1232
1427
|
return state.info;
|
|
1233
1428
|
}
|
|
1234
1429
|
catch (e) {
|
|
@@ -1254,7 +1449,7 @@ class StableBrowser {
|
|
|
1254
1449
|
await _preCommand(state, this);
|
|
1255
1450
|
await performAction("hover", state.element, options, this, state, _params);
|
|
1256
1451
|
await _screenshot(state, this);
|
|
1257
|
-
await this.waitForPageLoad();
|
|
1452
|
+
//await this.waitForPageLoad();
|
|
1258
1453
|
return state.info;
|
|
1259
1454
|
}
|
|
1260
1455
|
catch (e) {
|
|
@@ -1290,7 +1485,7 @@ class StableBrowser {
|
|
|
1290
1485
|
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1291
1486
|
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
1292
1487
|
}
|
|
1293
|
-
await this.waitForPageLoad();
|
|
1488
|
+
//await this.waitForPageLoad();
|
|
1294
1489
|
return state.info;
|
|
1295
1490
|
}
|
|
1296
1491
|
catch (e) {
|
|
@@ -1476,6 +1671,14 @@ class StableBrowser {
|
|
|
1476
1671
|
}
|
|
1477
1672
|
try {
|
|
1478
1673
|
await _preCommand(state, this);
|
|
1674
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
1675
|
+
// tag the element
|
|
1676
|
+
let newElementSelector = await state.element.evaluate((el, token) => {
|
|
1677
|
+
// use attribute and not id
|
|
1678
|
+
const attrName = `data-blinq-id-${token}`;
|
|
1679
|
+
el.setAttribute(attrName, "");
|
|
1680
|
+
return `[${attrName}]`;
|
|
1681
|
+
}, randomToken);
|
|
1479
1682
|
state.info.value = _value;
|
|
1480
1683
|
if (!options.press) {
|
|
1481
1684
|
try {
|
|
@@ -1501,6 +1704,25 @@ class StableBrowser {
|
|
|
1501
1704
|
}
|
|
1502
1705
|
}
|
|
1503
1706
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1707
|
+
// check if the element exist after the click (no wait)
|
|
1708
|
+
const count = await state.element.count({ timeout: 0 });
|
|
1709
|
+
if (count === 0) {
|
|
1710
|
+
// the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
|
|
1711
|
+
const scope = state.element._frame ?? element.page();
|
|
1712
|
+
let prefixSelector = "";
|
|
1713
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
1714
|
+
const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
|
|
1715
|
+
if (frameSelectorIndex !== -1) {
|
|
1716
|
+
// remove everything after the >> internal:control=enter-frame
|
|
1717
|
+
const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
|
|
1718
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
1719
|
+
}
|
|
1720
|
+
// if (element?._frame?._selector) {
|
|
1721
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
1722
|
+
// }
|
|
1723
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
1724
|
+
state.element = scope.locator(newSelector).first();
|
|
1725
|
+
}
|
|
1504
1726
|
const valueSegment = state.value.split("&&");
|
|
1505
1727
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1506
1728
|
if (i > 0) {
|
|
@@ -1572,8 +1794,8 @@ class StableBrowser {
|
|
|
1572
1794
|
if (enter) {
|
|
1573
1795
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1574
1796
|
await this.page.keyboard.press("Enter");
|
|
1797
|
+
await this.waitForPageLoad();
|
|
1575
1798
|
}
|
|
1576
|
-
await this.waitForPageLoad();
|
|
1577
1799
|
return state.info;
|
|
1578
1800
|
}
|
|
1579
1801
|
catch (e) {
|
|
@@ -1832,11 +2054,12 @@ class StableBrowser {
|
|
|
1832
2054
|
throw new Error("referanceSnapshot is null");
|
|
1833
2055
|
}
|
|
1834
2056
|
let text = null;
|
|
1835
|
-
|
|
1836
|
-
|
|
2057
|
+
const snapshotsFolder = process.env.BVT_TEMP_SNAPSHOTS_FOLDER ?? this.context.snapshotFolder; //path .join(this.project_path, "data", "snapshots");
|
|
2058
|
+
if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"))) {
|
|
2059
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yml"), "utf8");
|
|
1837
2060
|
}
|
|
1838
|
-
else if (fs.existsSync(path.join(
|
|
1839
|
-
text = fs.readFileSync(path.join(
|
|
2061
|
+
else if (fs.existsSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"))) {
|
|
2062
|
+
text = fs.readFileSync(path.join(snapshotsFolder, referanceSnapshot + ".yaml"), "utf8");
|
|
1840
2063
|
}
|
|
1841
2064
|
else if (referanceSnapshot.startsWith("yaml:")) {
|
|
1842
2065
|
text = referanceSnapshot.substring(5);
|
|
@@ -1860,7 +2083,13 @@ class StableBrowser {
|
|
|
1860
2083
|
scope = await this._findFrameScope(frameSelectors, timeout, state.info);
|
|
1861
2084
|
}
|
|
1862
2085
|
const snapshot = await scope.locator("body").ariaSnapshot({ timeout });
|
|
2086
|
+
if (snapshot && snapshot.length <= 10) {
|
|
2087
|
+
console.log("Page snapshot length is suspiciously small:", snapshot);
|
|
2088
|
+
}
|
|
1863
2089
|
matchResult = snapshotValidation(snapshot, newValue, referanceSnapshot);
|
|
2090
|
+
if (matchResult === undefined) {
|
|
2091
|
+
console.log("snapshotValidation returned undefined");
|
|
2092
|
+
}
|
|
1864
2093
|
if (matchResult.errorLine !== -1) {
|
|
1865
2094
|
throw new Error("Snapshot validation failed at line " + matchResult.errorLineText);
|
|
1866
2095
|
}
|
|
@@ -2031,6 +2260,9 @@ class StableBrowser {
|
|
|
2031
2260
|
return _getTestData(world, this.context, this);
|
|
2032
2261
|
}
|
|
2033
2262
|
async _screenShot(options = {}, world = null, info = null) {
|
|
2263
|
+
if (!options) {
|
|
2264
|
+
options = {};
|
|
2265
|
+
}
|
|
2034
2266
|
// collect url/path/title
|
|
2035
2267
|
if (info) {
|
|
2036
2268
|
if (!info.title) {
|
|
@@ -2059,7 +2291,7 @@ class StableBrowser {
|
|
|
2059
2291
|
const uuidStr = "id_" + randomUUID();
|
|
2060
2292
|
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
2061
2293
|
try {
|
|
2062
|
-
await this.takeScreenshot(screenshotPath);
|
|
2294
|
+
await this.takeScreenshot(screenshotPath, options.fullPage === true);
|
|
2063
2295
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2064
2296
|
// // save the buffer to the screenshot path asynchrously
|
|
2065
2297
|
// fs.writeFile(screenshotPath, buffer, (err) => {
|
|
@@ -2080,7 +2312,7 @@ class StableBrowser {
|
|
|
2080
2312
|
else if (options && options.screenshot) {
|
|
2081
2313
|
result.screenshotPath = options.screenshotPath;
|
|
2082
2314
|
try {
|
|
2083
|
-
await this.takeScreenshot(options.screenshotPath);
|
|
2315
|
+
await this.takeScreenshot(options.screenshotPath, options.fullPage === true);
|
|
2084
2316
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2085
2317
|
// // save the buffer to the screenshot path asynchrously
|
|
2086
2318
|
// fs.writeFile(options.screenshotPath, buffer, (err) => {
|
|
@@ -2098,7 +2330,7 @@ class StableBrowser {
|
|
|
2098
2330
|
}
|
|
2099
2331
|
return result;
|
|
2100
2332
|
}
|
|
2101
|
-
async takeScreenshot(screenshotPath) {
|
|
2333
|
+
async takeScreenshot(screenshotPath, fullPage = false) {
|
|
2102
2334
|
const playContext = this.context.playContext;
|
|
2103
2335
|
// Using CDP to capture the screenshot
|
|
2104
2336
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
@@ -2123,13 +2355,7 @@ class StableBrowser {
|
|
|
2123
2355
|
const client = await playContext.newCDPSession(this.page);
|
|
2124
2356
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
2125
2357
|
format: "png",
|
|
2126
|
-
|
|
2127
|
-
// x: 0,
|
|
2128
|
-
// y: 0,
|
|
2129
|
-
// width: viewportWidth,
|
|
2130
|
-
// height: viewportHeight,
|
|
2131
|
-
// scale: 1,
|
|
2132
|
-
// },
|
|
2358
|
+
captureBeyondViewport: fullPage,
|
|
2133
2359
|
});
|
|
2134
2360
|
await client.detach();
|
|
2135
2361
|
if (!screenshotPath) {
|
|
@@ -2138,7 +2364,7 @@ class StableBrowser {
|
|
|
2138
2364
|
screenshotBuffer = Buffer.from(data, "base64");
|
|
2139
2365
|
}
|
|
2140
2366
|
else {
|
|
2141
|
-
screenshotBuffer = await this.page.screenshot();
|
|
2367
|
+
screenshotBuffer = await this.page.screenshot({ fullPage: fullPage });
|
|
2142
2368
|
}
|
|
2143
2369
|
// if (focusedElement) {
|
|
2144
2370
|
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
@@ -2238,6 +2464,12 @@ class StableBrowser {
|
|
|
2238
2464
|
state.info.value = state.value;
|
|
2239
2465
|
this.setTestData({ [variable]: state.value }, world);
|
|
2240
2466
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
2467
|
+
if (process.env.MODE === "executions") {
|
|
2468
|
+
const globalDataFile = "global_test_data.json";
|
|
2469
|
+
if (existsSync(globalDataFile)) {
|
|
2470
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2241
2473
|
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2242
2474
|
return state.info;
|
|
2243
2475
|
}
|
|
@@ -2309,6 +2541,12 @@ class StableBrowser {
|
|
|
2309
2541
|
state.info.value = state.value;
|
|
2310
2542
|
this.setTestData({ [variable]: state.value }, world);
|
|
2311
2543
|
this.logger.info("set test data: " + variable + "=" + state.value);
|
|
2544
|
+
if (process.env.MODE === "executions") {
|
|
2545
|
+
const globalDataFile = "global_test_data.json";
|
|
2546
|
+
if (existsSync(globalDataFile)) {
|
|
2547
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2548
|
+
}
|
|
2549
|
+
}
|
|
2312
2550
|
// await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2313
2551
|
return state.info;
|
|
2314
2552
|
}
|
|
@@ -2439,7 +2677,7 @@ class StableBrowser {
|
|
|
2439
2677
|
let expectedValue;
|
|
2440
2678
|
try {
|
|
2441
2679
|
await _preCommand(state, this);
|
|
2442
|
-
expectedValue = await
|
|
2680
|
+
expectedValue = await this._replaceWithLocalData(value, world);
|
|
2443
2681
|
state.info.expectedValue = expectedValue;
|
|
2444
2682
|
switch (property) {
|
|
2445
2683
|
case "innerText":
|
|
@@ -2565,6 +2803,7 @@ class StableBrowser {
|
|
|
2565
2803
|
allowDisabled: true,
|
|
2566
2804
|
info: {},
|
|
2567
2805
|
};
|
|
2806
|
+
state.options ??= { timeout: timeoutMs };
|
|
2568
2807
|
// Initialize startTime outside try block to ensure it's always accessible
|
|
2569
2808
|
const startTime = Date.now();
|
|
2570
2809
|
let conditionMet = false;
|
|
@@ -2734,6 +2973,12 @@ class StableBrowser {
|
|
|
2734
2973
|
emailUrl = url;
|
|
2735
2974
|
codeOrUrlFound = true;
|
|
2736
2975
|
}
|
|
2976
|
+
if (process.env.MODE === "executions") {
|
|
2977
|
+
const globalDataFile = "global_test_data.json";
|
|
2978
|
+
if (existsSync(globalDataFile)) {
|
|
2979
|
+
this.saveTestDataAsGlobal({}, world);
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2737
2982
|
if (codeOrUrlFound) {
|
|
2738
2983
|
return { emailUrl, emailCode };
|
|
2739
2984
|
}
|
|
@@ -3140,7 +3385,16 @@ class StableBrowser {
|
|
|
3140
3385
|
text = text.replace(/\\"/g, '"');
|
|
3141
3386
|
}
|
|
3142
3387
|
const timeout = this._getFindElementTimeout(options);
|
|
3143
|
-
|
|
3388
|
+
//if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
3389
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
3390
|
+
if (!stepFastMode) {
|
|
3391
|
+
if (!this.fastMode) {
|
|
3392
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3393
|
+
}
|
|
3394
|
+
else {
|
|
3395
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3144
3398
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
3145
3399
|
if (newValue !== text) {
|
|
3146
3400
|
this.logger.info(text + "=" + newValue);
|
|
@@ -3148,6 +3402,11 @@ class StableBrowser {
|
|
|
3148
3402
|
}
|
|
3149
3403
|
let dateAlternatives = findDateAlternatives(text);
|
|
3150
3404
|
let numberAlternatives = findNumberAlternatives(text);
|
|
3405
|
+
if (stepFastMode) {
|
|
3406
|
+
state.onlyFailuresScreenshot = true;
|
|
3407
|
+
state.scroll = false;
|
|
3408
|
+
state.highlight = false;
|
|
3409
|
+
}
|
|
3151
3410
|
try {
|
|
3152
3411
|
await _preCommand(state, this);
|
|
3153
3412
|
state.info.text = text;
|
|
@@ -3267,6 +3526,8 @@ class StableBrowser {
|
|
|
3267
3526
|
operation: "verify_text_with_relation",
|
|
3268
3527
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
3269
3528
|
};
|
|
3529
|
+
const cmdStartTime = Date.now();
|
|
3530
|
+
let cmdEndTime = null;
|
|
3270
3531
|
const timeout = this._getFindElementTimeout(options);
|
|
3271
3532
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3272
3533
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
@@ -3302,6 +3563,17 @@ class StableBrowser {
|
|
|
3302
3563
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3303
3564
|
continue;
|
|
3304
3565
|
}
|
|
3566
|
+
else {
|
|
3567
|
+
cmdEndTime = Date.now();
|
|
3568
|
+
if (cmdEndTime - cmdStartTime > 55000) {
|
|
3569
|
+
if (foundAncore) {
|
|
3570
|
+
throw new Error(`Text ${textToVerify} not found in page`);
|
|
3571
|
+
}
|
|
3572
|
+
else {
|
|
3573
|
+
throw new Error(`Text ${textAnchor} not found in page`);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
}
|
|
3305
3577
|
try {
|
|
3306
3578
|
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
3307
3579
|
foundAncore = true;
|
|
@@ -3314,7 +3586,9 @@ class StableBrowser {
|
|
|
3314
3586
|
climbArray1.push("..");
|
|
3315
3587
|
}
|
|
3316
3588
|
let climbXpath = "xpath=" + climbArray1.join("/");
|
|
3317
|
-
|
|
3589
|
+
if (Number(climb) > 0) {
|
|
3590
|
+
css = css + " >> " + climbXpath;
|
|
3591
|
+
}
|
|
3318
3592
|
const count = await frame.locator(css).count();
|
|
3319
3593
|
for (let j = 0; j < count; j++) {
|
|
3320
3594
|
const continer = await frame.locator(css).nth(j);
|
|
@@ -3440,7 +3714,7 @@ class StableBrowser {
|
|
|
3440
3714
|
Object.assign(e, { info: info });
|
|
3441
3715
|
error = e;
|
|
3442
3716
|
// throw e;
|
|
3443
|
-
await _commandError({ text: "visualVerification", operation: "visualVerification",
|
|
3717
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
|
|
3444
3718
|
}
|
|
3445
3719
|
finally {
|
|
3446
3720
|
const endTime = Date.now();
|
|
@@ -3789,6 +4063,22 @@ class StableBrowser {
|
|
|
3789
4063
|
}
|
|
3790
4064
|
}
|
|
3791
4065
|
async waitForPageLoad(options = {}, world = null) {
|
|
4066
|
+
// try {
|
|
4067
|
+
// let currentPagePath = null;
|
|
4068
|
+
// currentPagePath = new URL(this.page.url()).pathname;
|
|
4069
|
+
// if (this.latestPagePath) {
|
|
4070
|
+
// // get the currect page path and compare with the latest page path
|
|
4071
|
+
// if (this.latestPagePath === currentPagePath) {
|
|
4072
|
+
// // if the page path is the same, do not wait for page load
|
|
4073
|
+
// console.log("No page change: " + currentPagePath);
|
|
4074
|
+
// return;
|
|
4075
|
+
// }
|
|
4076
|
+
// }
|
|
4077
|
+
// this.latestPagePath = currentPagePath;
|
|
4078
|
+
// } catch (e) {
|
|
4079
|
+
// console.debug("Error getting current page path: ", e);
|
|
4080
|
+
// }
|
|
4081
|
+
//console.log("Waiting for page load");
|
|
3792
4082
|
let timeout = this._getLoadTimeout(options);
|
|
3793
4083
|
const promiseArray = [];
|
|
3794
4084
|
// let waitForNetworkIdle = true;
|
|
@@ -3823,7 +4113,10 @@ class StableBrowser {
|
|
|
3823
4113
|
}
|
|
3824
4114
|
}
|
|
3825
4115
|
finally {
|
|
3826
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4116
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4117
|
+
if (options && !options.noSleep) {
|
|
4118
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
4119
|
+
}
|
|
3827
4120
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
3828
4121
|
const endTime = Date.now();
|
|
3829
4122
|
_reportToWorld(world, {
|
|
@@ -3877,7 +4170,7 @@ class StableBrowser {
|
|
|
3877
4170
|
}
|
|
3878
4171
|
operation = options.operation;
|
|
3879
4172
|
// validate operation is one of the supported operations
|
|
3880
|
-
if (operation != "click" && operation != "hover+click") {
|
|
4173
|
+
if (operation != "click" && operation != "hover+click" && operation != "hover") {
|
|
3881
4174
|
throw new Error("operation is not supported");
|
|
3882
4175
|
}
|
|
3883
4176
|
const state = {
|
|
@@ -3946,6 +4239,17 @@ class StableBrowser {
|
|
|
3946
4239
|
state.element = results[0];
|
|
3947
4240
|
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3948
4241
|
break;
|
|
4242
|
+
case "hover":
|
|
4243
|
+
if (!options.css) {
|
|
4244
|
+
throw new Error("css is not defined");
|
|
4245
|
+
}
|
|
4246
|
+
const result1 = await findElementsInArea(options.css, cellArea, this, options);
|
|
4247
|
+
if (result1.length === 0) {
|
|
4248
|
+
throw new Error(`Element not found in cell area`);
|
|
4249
|
+
}
|
|
4250
|
+
state.element = result1[0];
|
|
4251
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
4252
|
+
break;
|
|
3949
4253
|
default:
|
|
3950
4254
|
throw new Error("operation is not supported");
|
|
3951
4255
|
}
|
|
@@ -3959,6 +4263,12 @@ class StableBrowser {
|
|
|
3959
4263
|
}
|
|
3960
4264
|
saveTestDataAsGlobal(options, world) {
|
|
3961
4265
|
const dataFile = _getDataFile(world, this.context, this);
|
|
4266
|
+
if (process.env.MODE === "executions") {
|
|
4267
|
+
const globalDataFile = path.join(this.project_path, "global_test_data.json");
|
|
4268
|
+
fs.copyFileSync(dataFile, globalDataFile);
|
|
4269
|
+
this.logger.info("Save the scenario test data to " + globalDataFile + " as global for the following scenarios.");
|
|
4270
|
+
return;
|
|
4271
|
+
}
|
|
3962
4272
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
3963
4273
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
3964
4274
|
}
|
|
@@ -4061,6 +4371,7 @@ class StableBrowser {
|
|
|
4061
4371
|
if (world && world.attach) {
|
|
4062
4372
|
world.attach(this.context.reportFolder, { mediaType: "text/plain" });
|
|
4063
4373
|
}
|
|
4374
|
+
this.context.loadedRoutes = null;
|
|
4064
4375
|
this.beforeScenarioCalled = true;
|
|
4065
4376
|
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
4066
4377
|
this.scenarioName = scenario.pickle.name;
|
|
@@ -4087,14 +4398,51 @@ class StableBrowser {
|
|
|
4087
4398
|
await getTestData(envName, world, undefined, this.featureName, this.scenarioName, this.context);
|
|
4088
4399
|
}
|
|
4089
4400
|
await loadBrunoParams(this.context, this.context.environment.name);
|
|
4401
|
+
if ((process.env.TRACE === "true" || this.configuration.trace === true) && this.context) {
|
|
4402
|
+
this.trace = true;
|
|
4403
|
+
const traceFolder = path.join(this.context.reportFolder, "trace");
|
|
4404
|
+
if (!fs.existsSync(traceFolder)) {
|
|
4405
|
+
fs.mkdirSync(traceFolder, { recursive: true });
|
|
4406
|
+
}
|
|
4407
|
+
this.traceFolder = traceFolder;
|
|
4408
|
+
await this.context.playContext.tracing.start({ screenshots: true, snapshots: true });
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
async afterScenario(world, scenario) {
|
|
4412
|
+
const id = scenario.testCaseStartedId;
|
|
4413
|
+
if (this.trace) {
|
|
4414
|
+
await this.context.playContext.tracing.stop({
|
|
4415
|
+
path: path.join(this.traceFolder, `trace-${id}.zip`),
|
|
4416
|
+
});
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
getGherkinKeyword(step) {
|
|
4420
|
+
if (!step?.type) {
|
|
4421
|
+
return "";
|
|
4422
|
+
}
|
|
4423
|
+
switch (step.type) {
|
|
4424
|
+
case "Context":
|
|
4425
|
+
return "Given";
|
|
4426
|
+
case "Action":
|
|
4427
|
+
return "When";
|
|
4428
|
+
case "Outcome":
|
|
4429
|
+
return "Then";
|
|
4430
|
+
case "Conjunction":
|
|
4431
|
+
return "And";
|
|
4432
|
+
default:
|
|
4433
|
+
return "";
|
|
4434
|
+
}
|
|
4090
4435
|
}
|
|
4091
|
-
async afterScenario(world, scenario) { }
|
|
4092
4436
|
async beforeStep(world, step) {
|
|
4093
|
-
if (this.
|
|
4094
|
-
|
|
4437
|
+
if (step?.pickleStep && this.trace) {
|
|
4438
|
+
const keyword = this.getGherkinKeyword(step.pickleStep);
|
|
4439
|
+
this.traceGroupName = `${keyword} ${step.pickleStep.text}`;
|
|
4440
|
+
await this.context.playContext.tracing.group(this.traceGroupName);
|
|
4095
4441
|
}
|
|
4442
|
+
this.stepTags = [];
|
|
4096
4443
|
if (!this.beforeScenarioCalled) {
|
|
4097
4444
|
this.beforeScenario(world, step);
|
|
4445
|
+
this.context.loadedRoutes = null;
|
|
4098
4446
|
}
|
|
4099
4447
|
if (this.stepIndex === undefined) {
|
|
4100
4448
|
this.stepIndex = 0;
|
|
@@ -4104,7 +4452,12 @@ class StableBrowser {
|
|
|
4104
4452
|
}
|
|
4105
4453
|
if (step && step.pickleStep && step.pickleStep.text) {
|
|
4106
4454
|
this.stepName = step.pickleStep.text;
|
|
4107
|
-
|
|
4455
|
+
let printableStepName = this.stepName;
|
|
4456
|
+
// take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
|
|
4457
|
+
printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
|
|
4458
|
+
return `\x1b[33m"${p1}"\x1b[0m`;
|
|
4459
|
+
});
|
|
4460
|
+
this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
|
|
4108
4461
|
}
|
|
4109
4462
|
else if (step && step.text) {
|
|
4110
4463
|
this.stepName = step.text;
|
|
@@ -4119,7 +4472,10 @@ class StableBrowser {
|
|
|
4119
4472
|
}
|
|
4120
4473
|
if (this.initSnapshotTaken === false) {
|
|
4121
4474
|
this.initSnapshotTaken = true;
|
|
4122
|
-
if (world &&
|
|
4475
|
+
if (world &&
|
|
4476
|
+
world.attach &&
|
|
4477
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4478
|
+
(!this.fastMode || this.stepTags.includes("fast-mode"))) {
|
|
4123
4479
|
const snapshot = await this.getAriaSnapshot();
|
|
4124
4480
|
if (snapshot) {
|
|
4125
4481
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
@@ -4129,7 +4485,11 @@ class StableBrowser {
|
|
|
4129
4485
|
this.context.routeResults = null;
|
|
4130
4486
|
this.context.loadedRoutes = null;
|
|
4131
4487
|
await registerBeforeStepRoutes(this.context, this.stepName, world);
|
|
4132
|
-
networkBeforeStep(this.stepName);
|
|
4488
|
+
networkBeforeStep(this.stepName, this.context);
|
|
4489
|
+
this.inStepReport = false;
|
|
4490
|
+
}
|
|
4491
|
+
setStepTags(tags) {
|
|
4492
|
+
this.stepTags = tags;
|
|
4133
4493
|
}
|
|
4134
4494
|
async getAriaSnapshot() {
|
|
4135
4495
|
try {
|
|
@@ -4203,7 +4563,7 @@ class StableBrowser {
|
|
|
4203
4563
|
state.payload = payload;
|
|
4204
4564
|
if (commandStatus === "FAILED") {
|
|
4205
4565
|
state.throwError = true;
|
|
4206
|
-
throw new Error(
|
|
4566
|
+
throw new Error(commandText);
|
|
4207
4567
|
}
|
|
4208
4568
|
}
|
|
4209
4569
|
catch (e) {
|
|
@@ -4213,26 +4573,22 @@ class StableBrowser {
|
|
|
4213
4573
|
await _commandFinally(state, this);
|
|
4214
4574
|
}
|
|
4215
4575
|
}
|
|
4216
|
-
async afterStep(world, step) {
|
|
4576
|
+
async afterStep(world, step, result) {
|
|
4217
4577
|
this.stepName = null;
|
|
4218
|
-
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
4219
|
-
if (this.context.browserObject.context) {
|
|
4220
|
-
await this.context.browserObject.context.tracing.stopChunk({
|
|
4221
|
-
path: path.join(this.context.browserObject.traceFolder, `trace-${this.stepIndex}.zip`),
|
|
4222
|
-
});
|
|
4223
|
-
if (world && world.attach) {
|
|
4224
|
-
await world.attach(JSON.stringify({
|
|
4225
|
-
type: "trace",
|
|
4226
|
-
traceFilePath: `trace-${this.stepIndex}.zip`,
|
|
4227
|
-
}), "application/json+trace");
|
|
4228
|
-
}
|
|
4229
|
-
// console.log("trace file created", `trace-${this.stepIndex}.zip`);
|
|
4230
|
-
}
|
|
4231
|
-
}
|
|
4232
4578
|
if (this.context) {
|
|
4233
4579
|
this.context.examplesRow = null;
|
|
4234
4580
|
}
|
|
4235
|
-
if (
|
|
4581
|
+
if (!this.inStepReport) {
|
|
4582
|
+
// check the step result
|
|
4583
|
+
if (result && result.status === "FAILED" && world && world.attach) {
|
|
4584
|
+
await this.addCommandToReport(result.message ? result.message : "Step failed", "FAILED", `${result.message}`, { type: "text", screenshot: true }, world);
|
|
4585
|
+
}
|
|
4586
|
+
}
|
|
4587
|
+
if (world &&
|
|
4588
|
+
world.attach &&
|
|
4589
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4590
|
+
!this.fastMode &&
|
|
4591
|
+
!this.stepTags.includes("fast-mode")) {
|
|
4236
4592
|
const snapshot = await this.getAriaSnapshot();
|
|
4237
4593
|
if (snapshot) {
|
|
4238
4594
|
const obj = {};
|
|
@@ -4240,6 +4596,11 @@ class StableBrowser {
|
|
|
4240
4596
|
}
|
|
4241
4597
|
}
|
|
4242
4598
|
this.context.routeResults = await registerAfterStepRoutes(this.context, world);
|
|
4599
|
+
if (this.context.routeResults) {
|
|
4600
|
+
if (world && world.attach) {
|
|
4601
|
+
await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4243
4604
|
if (!process.env.TEMP_RUN) {
|
|
4244
4605
|
const state = {
|
|
4245
4606
|
world,
|
|
@@ -4263,10 +4624,15 @@ class StableBrowser {
|
|
|
4263
4624
|
await _commandFinally(state, this);
|
|
4264
4625
|
}
|
|
4265
4626
|
}
|
|
4266
|
-
networkAfterStep(this.stepName);
|
|
4627
|
+
networkAfterStep(this.stepName, this.context);
|
|
4267
4628
|
if (process.env.TEMP_RUN === "true") {
|
|
4268
4629
|
// Put a sleep for some time to allow the browser to finish processing
|
|
4269
|
-
|
|
4630
|
+
if (!this.stepTags.includes("fast-mode")) {
|
|
4631
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
if (this.trace) {
|
|
4635
|
+
await this.context.playContext.tracing.groupEnd();
|
|
4270
4636
|
}
|
|
4271
4637
|
}
|
|
4272
4638
|
}
|