automation_model 1.0.773-dev → 1.0.773-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 +28 -11
- package/lib/api.js.map +1 -1
- package/lib/auto_page.js +60 -24
- package/lib/auto_page.js.map +1 -1
- package/lib/browser_manager.js +56 -34
- 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 +1 -1
- package/lib/command_common.js +30 -16
- package/lib/command_common.js.map +1 -1
- package/lib/file_checker.js +136 -25
- 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 +122 -126
- 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 +346 -168
- package/lib/network.js.map +1 -1
- package/lib/route.d.ts +64 -15
- package/lib/route.js +529 -188
- package/lib/route.js.map +1 -1
- package/lib/scripts/axe.mini.js +23994 -1
- package/lib/snapshot_validation.js.map +1 -1
- package/lib/stable_browser.d.ts +13 -7
- package/lib/stable_browser.js +421 -95
- 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 +6 -2
- package/lib/utils.js +121 -14
- package/lib/utils.js.map +1 -1
- package/package.json +17 -10
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,13 +96,17 @@ class StableBrowser {
|
|
|
92
96
|
tags = null;
|
|
93
97
|
isRecording = false;
|
|
94
98
|
initSnapshotTaken = false;
|
|
95
|
-
|
|
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 = []) {
|
|
96
103
|
this.browser = browser;
|
|
97
104
|
this.page = page;
|
|
98
105
|
this.logger = logger;
|
|
99
106
|
this.context = context;
|
|
100
107
|
this.world = world;
|
|
101
108
|
this.fastMode = fastMode;
|
|
109
|
+
this.stepTags = stepTags;
|
|
102
110
|
if (!this.logger) {
|
|
103
111
|
this.logger = console;
|
|
104
112
|
}
|
|
@@ -131,6 +139,7 @@ class StableBrowser {
|
|
|
131
139
|
this.fastMode = true;
|
|
132
140
|
}
|
|
133
141
|
if (process.env.FAST_MODE === "true") {
|
|
142
|
+
// console.log("Fast mode enabled from environment variable");
|
|
134
143
|
this.fastMode = true;
|
|
135
144
|
}
|
|
136
145
|
if (process.env.FAST_MODE === "false") {
|
|
@@ -173,6 +182,7 @@ class StableBrowser {
|
|
|
173
182
|
registerNetworkEvents(this.world, this, context, this.page);
|
|
174
183
|
registerDownloadEvent(this.page, this.world, context);
|
|
175
184
|
page.on("close", async () => {
|
|
185
|
+
// return if browser context is already closed
|
|
176
186
|
if (this.context && this.context.pages && this.context.pages.length > 1) {
|
|
177
187
|
this.context.pages.pop();
|
|
178
188
|
this.page = this.context.pages[this.context.pages.length - 1];
|
|
@@ -182,7 +192,12 @@ class StableBrowser {
|
|
|
182
192
|
console.log("Switched to page " + title);
|
|
183
193
|
}
|
|
184
194
|
catch (error) {
|
|
185
|
-
|
|
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
|
+
}
|
|
186
201
|
}
|
|
187
202
|
}
|
|
188
203
|
});
|
|
@@ -191,7 +206,12 @@ class StableBrowser {
|
|
|
191
206
|
console.log("Switch page: " + (await page.title()));
|
|
192
207
|
}
|
|
193
208
|
catch (e) {
|
|
194
|
-
|
|
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
|
+
}
|
|
195
215
|
}
|
|
196
216
|
context.pageLoading.status = false;
|
|
197
217
|
}.bind(this));
|
|
@@ -219,7 +239,7 @@ class StableBrowser {
|
|
|
219
239
|
if (newContextCreated) {
|
|
220
240
|
this.registerEventListeners(this.context);
|
|
221
241
|
await this.goto(this.context.environment.baseUrl);
|
|
222
|
-
if (!this.fastMode) {
|
|
242
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
223
243
|
await this.waitForPageLoad();
|
|
224
244
|
}
|
|
225
245
|
}
|
|
@@ -311,7 +331,7 @@ class StableBrowser {
|
|
|
311
331
|
// async closeUnexpectedPopups() {
|
|
312
332
|
// await closeUnexpectedPopups(this.page);
|
|
313
333
|
// }
|
|
314
|
-
async goto(url, world = null) {
|
|
334
|
+
async goto(url, world = null, options = {}) {
|
|
315
335
|
if (!url) {
|
|
316
336
|
throw new Error("url is null, verify that the environment file is correct");
|
|
317
337
|
}
|
|
@@ -332,10 +352,14 @@ class StableBrowser {
|
|
|
332
352
|
screenshot: false,
|
|
333
353
|
highlight: false,
|
|
334
354
|
};
|
|
355
|
+
let timeout = 60000;
|
|
356
|
+
if (options && options["timeout"]) {
|
|
357
|
+
timeout = options["timeout"];
|
|
358
|
+
}
|
|
335
359
|
try {
|
|
336
360
|
await _preCommand(state, this);
|
|
337
361
|
await this.page.goto(url, {
|
|
338
|
-
timeout:
|
|
362
|
+
timeout: timeout,
|
|
339
363
|
});
|
|
340
364
|
await _screenshot(state, this);
|
|
341
365
|
}
|
|
@@ -503,12 +527,6 @@ class StableBrowser {
|
|
|
503
527
|
if (!el.setAttribute) {
|
|
504
528
|
el = el.parentElement;
|
|
505
529
|
}
|
|
506
|
-
// remove any attributes start with data-blinq-id
|
|
507
|
-
// for (let i = 0; i < el.attributes.length; i++) {
|
|
508
|
-
// if (el.attributes[i].name.startsWith("data-blinq-id")) {
|
|
509
|
-
// el.removeAttribute(el.attributes[i].name);
|
|
510
|
-
// }
|
|
511
|
-
// }
|
|
512
530
|
el.setAttribute("data-blinq-id-" + randomToken, "");
|
|
513
531
|
return true;
|
|
514
532
|
}, [tag1, randomToken]))) {
|
|
@@ -678,40 +696,186 @@ class StableBrowser {
|
|
|
678
696
|
}
|
|
679
697
|
return { rerun: false };
|
|
680
698
|
}
|
|
699
|
+
getFilePath() {
|
|
700
|
+
const stackFrames = errorStackParser.parse(new Error());
|
|
701
|
+
const stackFrame = stackFrames.findLast((frame) => frame.fileName && frame.fileName.endsWith(".mjs"));
|
|
702
|
+
// return stackFrame?.fileName || null;
|
|
703
|
+
const filepath = stackFrame?.fileName;
|
|
704
|
+
if (filepath) {
|
|
705
|
+
let jsonFilePath = filepath.replace(".mjs", ".json");
|
|
706
|
+
if (existsSync(jsonFilePath)) {
|
|
707
|
+
return jsonFilePath;
|
|
708
|
+
}
|
|
709
|
+
const config = this.configuration ?? {};
|
|
710
|
+
if (!config?.locatorsMetadataDir) {
|
|
711
|
+
config.locatorsMetadataDir = "features/step_definitions/locators";
|
|
712
|
+
}
|
|
713
|
+
if (config && config.locatorsMetadataDir) {
|
|
714
|
+
jsonFilePath = path.join(config.locatorsMetadataDir, path.basename(jsonFilePath));
|
|
715
|
+
}
|
|
716
|
+
if (existsSync(jsonFilePath)) {
|
|
717
|
+
return jsonFilePath;
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
getFullElementLocators(selectors, filePath) {
|
|
724
|
+
if (!filePath || !existsSync(filePath)) {
|
|
725
|
+
return null;
|
|
726
|
+
}
|
|
727
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
728
|
+
try {
|
|
729
|
+
const allElements = JSON.parse(content);
|
|
730
|
+
const element_key = selectors?.element_key;
|
|
731
|
+
if (element_key && allElements[element_key]) {
|
|
732
|
+
return allElements[element_key];
|
|
733
|
+
}
|
|
734
|
+
for (const elementKey in allElements) {
|
|
735
|
+
const element = allElements[elementKey];
|
|
736
|
+
let foundStrategy = null;
|
|
737
|
+
for (const key in element) {
|
|
738
|
+
if (key === "strategy") {
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
const locators = element[key];
|
|
742
|
+
if (!locators || !locators.length) {
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
for (const locator of locators) {
|
|
746
|
+
delete locator.score;
|
|
747
|
+
}
|
|
748
|
+
if (JSON.stringify(locators) === JSON.stringify(selectors.locators)) {
|
|
749
|
+
foundStrategy = key;
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (foundStrategy) {
|
|
754
|
+
return element;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
console.error("Error parsing locators from file: " + filePath, error);
|
|
760
|
+
}
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
681
763
|
async _locate(selectors, info, _params, timeout, allowDisabled = false) {
|
|
682
764
|
if (!timeout) {
|
|
683
765
|
timeout = 30000;
|
|
684
766
|
}
|
|
767
|
+
let element = null;
|
|
768
|
+
let allStrategyLocators = null;
|
|
769
|
+
let selectedStrategy = null;
|
|
770
|
+
if (this.tryAllStrategies) {
|
|
771
|
+
allStrategyLocators = this.getFullElementLocators(selectors, this.getFilePath());
|
|
772
|
+
selectedStrategy = allStrategyLocators?.strategy;
|
|
773
|
+
}
|
|
685
774
|
for (let i = 0; i < 3; i++) {
|
|
686
775
|
info.log += "attempt " + i + ": total locators " + selectors.locators.length + "\n";
|
|
687
776
|
for (let j = 0; j < selectors.locators.length; j++) {
|
|
688
777
|
let selector = selectors.locators[j];
|
|
689
778
|
info.log += "searching for locator " + j + ":" + JSON.stringify(selector) + "\n";
|
|
690
779
|
}
|
|
691
|
-
|
|
780
|
+
if (this.tryAllStrategies && selectedStrategy) {
|
|
781
|
+
const strategyLocators = allStrategyLocators[selectedStrategy];
|
|
782
|
+
let err;
|
|
783
|
+
if (strategyLocators && strategyLocators.length) {
|
|
784
|
+
try {
|
|
785
|
+
selectors.locators = strategyLocators;
|
|
786
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
787
|
+
info.selectedStrategy = selectedStrategy;
|
|
788
|
+
info.log += "element found using strategy " + selectedStrategy + "\n";
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
err = error;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
if (!element) {
|
|
795
|
+
for (const key in allStrategyLocators) {
|
|
796
|
+
if (key === "strategy" || key === selectedStrategy) {
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
const strategyLocators = allStrategyLocators[key];
|
|
800
|
+
if (strategyLocators && strategyLocators.length) {
|
|
801
|
+
try {
|
|
802
|
+
info.log += "using strategy " + key + " with locators " + JSON.stringify(strategyLocators) + "\n";
|
|
803
|
+
selectors.locators = strategyLocators;
|
|
804
|
+
element = await this._locate_internal(selectors, info, _params, 10_000, allowDisabled);
|
|
805
|
+
err = null;
|
|
806
|
+
info.selectedStrategy = key;
|
|
807
|
+
info.log += "element found using strategy " + key + "\n";
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
catch (error) {
|
|
811
|
+
err = error;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (err) {
|
|
817
|
+
throw err;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
else {
|
|
821
|
+
element = await this._locate_internal(selectors, info, _params, timeout, allowDisabled);
|
|
822
|
+
}
|
|
692
823
|
if (!element.rerun) {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
824
|
+
let newElementSelector = "";
|
|
825
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "csschain") {
|
|
826
|
+
const cssSelector = await element.evaluate((el) => {
|
|
827
|
+
function getCssSelector(el) {
|
|
828
|
+
if (!el || el.nodeType !== 1 || el === document.body)
|
|
829
|
+
return el.tagName.toLowerCase();
|
|
830
|
+
const parent = el.parentElement;
|
|
831
|
+
const tag = el.tagName.toLowerCase();
|
|
832
|
+
// Find the index of the element among its siblings of the same tag
|
|
833
|
+
let index = 1;
|
|
834
|
+
for (let sibling = el.previousElementSibling; sibling; sibling = sibling.previousElementSibling) {
|
|
835
|
+
if (sibling.tagName === el.tagName) {
|
|
836
|
+
index++;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// Use nth-child if necessary (i.e., if there's more than one of the same tag)
|
|
840
|
+
const siblings = Array.from(parent.children).filter((child) => child.tagName === el.tagName);
|
|
841
|
+
const needsNthChild = siblings.length > 1;
|
|
842
|
+
const selector = needsNthChild ? `${tag}:nth-child(${[...parent.children].indexOf(el) + 1})` : tag;
|
|
843
|
+
return getCssSelector(parent) + " > " + selector;
|
|
844
|
+
}
|
|
845
|
+
const cssSelector = getCssSelector(el);
|
|
846
|
+
return cssSelector;
|
|
847
|
+
});
|
|
848
|
+
newElementSelector = cssSelector;
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
852
|
+
if (this.configuration && this.configuration.stableLocatorStrategy === "data-attribute") {
|
|
853
|
+
const dataAttribute = "data-blinq-id";
|
|
854
|
+
await element.evaluate((el, [dataAttribute, randomToken]) => {
|
|
855
|
+
el.setAttribute(dataAttribute, randomToken);
|
|
856
|
+
}, [dataAttribute, randomToken]);
|
|
857
|
+
newElementSelector = `[${dataAttribute}="${randomToken}"]`;
|
|
858
|
+
}
|
|
859
|
+
else {
|
|
860
|
+
// the default case just return the located element
|
|
861
|
+
// will not work for click and type if the locator is placeholder and the placeholder change due to the click event
|
|
862
|
+
return element;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
700
865
|
const scope = element._frame ?? element.page();
|
|
701
|
-
let newElementSelector = "[data-blinq-id-" + randomToken + "]";
|
|
702
866
|
let prefixSelector = "";
|
|
703
867
|
const frameControlSelector = " >> internal:control=enter-frame";
|
|
704
868
|
const frameSelectorIndex = element._selector.lastIndexOf(frameControlSelector);
|
|
705
869
|
if (frameSelectorIndex !== -1) {
|
|
706
870
|
// remove everything after the >> internal:control=enter-frame
|
|
707
871
|
const frameSelector = element._selector.substring(0, frameSelectorIndex);
|
|
708
|
-
prefixSelector = frameSelector + " >> internal:control=enter-frame >>";
|
|
872
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
709
873
|
}
|
|
710
874
|
// if (element?._frame?._selector) {
|
|
711
875
|
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
712
876
|
// }
|
|
713
877
|
const newSelector = prefixSelector + newElementSelector;
|
|
714
|
-
return scope.locator(newSelector);
|
|
878
|
+
return scope.locator(newSelector).first();
|
|
715
879
|
}
|
|
716
880
|
}
|
|
717
881
|
throw new Error("unable to locate element " + JSON.stringify(selectors));
|
|
@@ -732,7 +896,7 @@ class StableBrowser {
|
|
|
732
896
|
for (let i = 0; i < frame.selectors.length; i++) {
|
|
733
897
|
let frameLocator = frame.selectors[i];
|
|
734
898
|
if (frameLocator.css) {
|
|
735
|
-
let testframescope = framescope.frameLocator(frameLocator.css);
|
|
899
|
+
let testframescope = framescope.frameLocator(`${frameLocator.css} >> visible=true`);
|
|
736
900
|
if (frameLocator.index) {
|
|
737
901
|
testframescope = framescope.nth(frameLocator.index);
|
|
738
902
|
}
|
|
@@ -744,7 +908,7 @@ class StableBrowser {
|
|
|
744
908
|
break;
|
|
745
909
|
}
|
|
746
910
|
catch (error) {
|
|
747
|
-
console.error("frame not found " + frameLocator.css);
|
|
911
|
+
// console.error("frame not found " + frameLocator.css);
|
|
748
912
|
}
|
|
749
913
|
}
|
|
750
914
|
}
|
|
@@ -810,6 +974,15 @@ class StableBrowser {
|
|
|
810
974
|
});
|
|
811
975
|
}
|
|
812
976
|
async _locate_internal(selectors, info, _params, timeout = 30000, allowDisabled = false) {
|
|
977
|
+
if (selectors.locators && Array.isArray(selectors.locators)) {
|
|
978
|
+
selectors.locators.forEach((locator) => {
|
|
979
|
+
locator.index = locator.index ?? 0;
|
|
980
|
+
locator.visible = locator.visible ?? true;
|
|
981
|
+
if (locator.visible && locator.css && !locator.css.endsWith(">> visible=true")) {
|
|
982
|
+
locator.css = locator.css + " >> visible=true";
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
}
|
|
813
986
|
if (!info) {
|
|
814
987
|
info = {};
|
|
815
988
|
info.failCause = {};
|
|
@@ -822,7 +995,6 @@ class StableBrowser {
|
|
|
822
995
|
let locatorsCount = 0;
|
|
823
996
|
let lazy_scroll = false;
|
|
824
997
|
//let arrayMode = Array.isArray(selectors);
|
|
825
|
-
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
826
998
|
let selectorsLocators = null;
|
|
827
999
|
selectorsLocators = selectors.locators;
|
|
828
1000
|
// group selectors by priority
|
|
@@ -850,6 +1022,7 @@ class StableBrowser {
|
|
|
850
1022
|
let highPriorityOnly = true;
|
|
851
1023
|
let visibleOnly = true;
|
|
852
1024
|
while (true) {
|
|
1025
|
+
let scope = await this._findFrameScope(selectors, timeout, info);
|
|
853
1026
|
locatorsCount = 0;
|
|
854
1027
|
let result = [];
|
|
855
1028
|
let popupResult = await this.closeUnexpectedPopups(info, _params);
|
|
@@ -965,9 +1138,13 @@ class StableBrowser {
|
|
|
965
1138
|
}
|
|
966
1139
|
}
|
|
967
1140
|
if (foundLocators.length === 1) {
|
|
1141
|
+
let box = null;
|
|
1142
|
+
if (!this.onlyFailuresScreenshot) {
|
|
1143
|
+
box = await foundLocators[0].boundingBox();
|
|
1144
|
+
}
|
|
968
1145
|
result.foundElements.push({
|
|
969
1146
|
locator: foundLocators[0],
|
|
970
|
-
box:
|
|
1147
|
+
box: box,
|
|
971
1148
|
unique: true,
|
|
972
1149
|
});
|
|
973
1150
|
result.locatorIndex = i;
|
|
@@ -1122,11 +1299,22 @@ class StableBrowser {
|
|
|
1122
1299
|
operation: "click",
|
|
1123
1300
|
log: "***** click on " + selectors.element_name + " *****\n",
|
|
1124
1301
|
};
|
|
1302
|
+
check_performance("click_all ***", this.context, true);
|
|
1303
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
1304
|
+
if (stepFastMode) {
|
|
1305
|
+
state.onlyFailuresScreenshot = true;
|
|
1306
|
+
state.scroll = false;
|
|
1307
|
+
state.highlight = false;
|
|
1308
|
+
}
|
|
1125
1309
|
try {
|
|
1310
|
+
check_performance("click_preCommand", this.context, true);
|
|
1126
1311
|
await _preCommand(state, this);
|
|
1312
|
+
check_performance("click_preCommand", this.context, false);
|
|
1127
1313
|
await performAction("click", state.element, options, this, state, _params);
|
|
1128
|
-
if (!this.fastMode) {
|
|
1129
|
-
|
|
1314
|
+
if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
1315
|
+
check_performance("click_waitForPageLoad", this.context, true);
|
|
1316
|
+
await this.waitForPageLoad({ noSleep: true });
|
|
1317
|
+
check_performance("click_waitForPageLoad", this.context, false);
|
|
1130
1318
|
}
|
|
1131
1319
|
return state.info;
|
|
1132
1320
|
}
|
|
@@ -1134,7 +1322,13 @@ class StableBrowser {
|
|
|
1134
1322
|
await _commandError(state, e, this);
|
|
1135
1323
|
}
|
|
1136
1324
|
finally {
|
|
1325
|
+
check_performance("click_commandFinally", this.context, true);
|
|
1137
1326
|
await _commandFinally(state, this);
|
|
1327
|
+
check_performance("click_commandFinally", this.context, false);
|
|
1328
|
+
check_performance("click_all ***", this.context, false);
|
|
1329
|
+
if (this.context.profile) {
|
|
1330
|
+
console.log(JSON.stringify(this.context.profile, null, 2));
|
|
1331
|
+
}
|
|
1138
1332
|
}
|
|
1139
1333
|
}
|
|
1140
1334
|
async waitForElement(selectors, _params, options = {}, world = null) {
|
|
@@ -1226,7 +1420,7 @@ class StableBrowser {
|
|
|
1226
1420
|
}
|
|
1227
1421
|
}
|
|
1228
1422
|
}
|
|
1229
|
-
await this.waitForPageLoad();
|
|
1423
|
+
//await this.waitForPageLoad();
|
|
1230
1424
|
return state.info;
|
|
1231
1425
|
}
|
|
1232
1426
|
catch (e) {
|
|
@@ -1252,7 +1446,7 @@ class StableBrowser {
|
|
|
1252
1446
|
await _preCommand(state, this);
|
|
1253
1447
|
await performAction("hover", state.element, options, this, state, _params);
|
|
1254
1448
|
await _screenshot(state, this);
|
|
1255
|
-
await this.waitForPageLoad();
|
|
1449
|
+
//await this.waitForPageLoad();
|
|
1256
1450
|
return state.info;
|
|
1257
1451
|
}
|
|
1258
1452
|
catch (e) {
|
|
@@ -1288,7 +1482,7 @@ class StableBrowser {
|
|
|
1288
1482
|
state.info.log += "selectOption failed, will try force" + "\n";
|
|
1289
1483
|
await state.element.selectOption(values, { timeout: 10000, force: true });
|
|
1290
1484
|
}
|
|
1291
|
-
await this.waitForPageLoad();
|
|
1485
|
+
//await this.waitForPageLoad();
|
|
1292
1486
|
return state.info;
|
|
1293
1487
|
}
|
|
1294
1488
|
catch (e) {
|
|
@@ -1474,6 +1668,14 @@ class StableBrowser {
|
|
|
1474
1668
|
}
|
|
1475
1669
|
try {
|
|
1476
1670
|
await _preCommand(state, this);
|
|
1671
|
+
const randomToken = "blinq_" + Math.random().toString(36).substring(7);
|
|
1672
|
+
// tag the element
|
|
1673
|
+
let newElementSelector = await state.element.evaluate((el, token) => {
|
|
1674
|
+
// use attribute and not id
|
|
1675
|
+
const attrName = `data-blinq-id-${token}`;
|
|
1676
|
+
el.setAttribute(attrName, "");
|
|
1677
|
+
return `[${attrName}]`;
|
|
1678
|
+
}, randomToken);
|
|
1477
1679
|
state.info.value = _value;
|
|
1478
1680
|
if (!options.press) {
|
|
1479
1681
|
try {
|
|
@@ -1499,6 +1701,25 @@ class StableBrowser {
|
|
|
1499
1701
|
}
|
|
1500
1702
|
}
|
|
1501
1703
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1704
|
+
// check if the element exist after the click (no wait)
|
|
1705
|
+
const count = await state.element.count({ timeout: 0 });
|
|
1706
|
+
if (count === 0) {
|
|
1707
|
+
// the locator changed after the click (placeholder) we need to locate the element using the data-blinq-id
|
|
1708
|
+
const scope = state.element._frame ?? element.page();
|
|
1709
|
+
let prefixSelector = "";
|
|
1710
|
+
const frameControlSelector = " >> internal:control=enter-frame";
|
|
1711
|
+
const frameSelectorIndex = state.element._selector.lastIndexOf(frameControlSelector);
|
|
1712
|
+
if (frameSelectorIndex !== -1) {
|
|
1713
|
+
// remove everything after the >> internal:control=enter-frame
|
|
1714
|
+
const frameSelector = state.element._selector.substring(0, frameSelectorIndex);
|
|
1715
|
+
prefixSelector = frameSelector + " >> internal:control=enter-frame >> ";
|
|
1716
|
+
}
|
|
1717
|
+
// if (element?._frame?._selector) {
|
|
1718
|
+
// prefixSelector = element._frame._selector + " >> " + prefixSelector;
|
|
1719
|
+
// }
|
|
1720
|
+
const newSelector = prefixSelector + newElementSelector;
|
|
1721
|
+
state.element = scope.locator(newSelector).first();
|
|
1722
|
+
}
|
|
1502
1723
|
const valueSegment = state.value.split("&&");
|
|
1503
1724
|
for (let i = 0; i < valueSegment.length; i++) {
|
|
1504
1725
|
if (i > 0) {
|
|
@@ -1570,8 +1791,8 @@ class StableBrowser {
|
|
|
1570
1791
|
if (enter) {
|
|
1571
1792
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
1572
1793
|
await this.page.keyboard.press("Enter");
|
|
1794
|
+
await this.waitForPageLoad();
|
|
1573
1795
|
}
|
|
1574
|
-
await this.waitForPageLoad();
|
|
1575
1796
|
return state.info;
|
|
1576
1797
|
}
|
|
1577
1798
|
catch (e) {
|
|
@@ -2029,6 +2250,9 @@ class StableBrowser {
|
|
|
2029
2250
|
return _getTestData(world, this.context, this);
|
|
2030
2251
|
}
|
|
2031
2252
|
async _screenShot(options = {}, world = null, info = null) {
|
|
2253
|
+
if (!options) {
|
|
2254
|
+
options = {};
|
|
2255
|
+
}
|
|
2032
2256
|
// collect url/path/title
|
|
2033
2257
|
if (info) {
|
|
2034
2258
|
if (!info.title) {
|
|
@@ -2057,7 +2281,7 @@ class StableBrowser {
|
|
|
2057
2281
|
const uuidStr = "id_" + randomUUID();
|
|
2058
2282
|
const screenshotPath = path.join(world.screenshotPath, uuidStr + ".png");
|
|
2059
2283
|
try {
|
|
2060
|
-
await this.takeScreenshot(screenshotPath);
|
|
2284
|
+
await this.takeScreenshot(screenshotPath, options.fullPage === true);
|
|
2061
2285
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2062
2286
|
// // save the buffer to the screenshot path asynchrously
|
|
2063
2287
|
// fs.writeFile(screenshotPath, buffer, (err) => {
|
|
@@ -2078,7 +2302,7 @@ class StableBrowser {
|
|
|
2078
2302
|
else if (options && options.screenshot) {
|
|
2079
2303
|
result.screenshotPath = options.screenshotPath;
|
|
2080
2304
|
try {
|
|
2081
|
-
await this.takeScreenshot(options.screenshotPath);
|
|
2305
|
+
await this.takeScreenshot(options.screenshotPath, options.fullPage === true);
|
|
2082
2306
|
// let buffer = await this.page.screenshot({ timeout: 4000 });
|
|
2083
2307
|
// // save the buffer to the screenshot path asynchrously
|
|
2084
2308
|
// fs.writeFile(options.screenshotPath, buffer, (err) => {
|
|
@@ -2096,7 +2320,7 @@ class StableBrowser {
|
|
|
2096
2320
|
}
|
|
2097
2321
|
return result;
|
|
2098
2322
|
}
|
|
2099
|
-
async takeScreenshot(screenshotPath) {
|
|
2323
|
+
async takeScreenshot(screenshotPath, fullPage = false) {
|
|
2100
2324
|
const playContext = this.context.playContext;
|
|
2101
2325
|
// Using CDP to capture the screenshot
|
|
2102
2326
|
const viewportWidth = Math.max(...(await this.page.evaluate(() => [
|
|
@@ -2121,13 +2345,7 @@ class StableBrowser {
|
|
|
2121
2345
|
const client = await playContext.newCDPSession(this.page);
|
|
2122
2346
|
const { data } = await client.send("Page.captureScreenshot", {
|
|
2123
2347
|
format: "png",
|
|
2124
|
-
|
|
2125
|
-
// x: 0,
|
|
2126
|
-
// y: 0,
|
|
2127
|
-
// width: viewportWidth,
|
|
2128
|
-
// height: viewportHeight,
|
|
2129
|
-
// scale: 1,
|
|
2130
|
-
// },
|
|
2348
|
+
captureBeyondViewport: fullPage,
|
|
2131
2349
|
});
|
|
2132
2350
|
await client.detach();
|
|
2133
2351
|
if (!screenshotPath) {
|
|
@@ -2136,7 +2354,7 @@ class StableBrowser {
|
|
|
2136
2354
|
screenshotBuffer = Buffer.from(data, "base64");
|
|
2137
2355
|
}
|
|
2138
2356
|
else {
|
|
2139
|
-
screenshotBuffer = await this.page.screenshot();
|
|
2357
|
+
screenshotBuffer = await this.page.screenshot({ fullPage: fullPage });
|
|
2140
2358
|
}
|
|
2141
2359
|
// if (focusedElement) {
|
|
2142
2360
|
// // console.log(`Focused element ${JSON.stringify(focusedElement._selector)}`)
|
|
@@ -2437,7 +2655,7 @@ class StableBrowser {
|
|
|
2437
2655
|
let expectedValue;
|
|
2438
2656
|
try {
|
|
2439
2657
|
await _preCommand(state, this);
|
|
2440
|
-
expectedValue = await
|
|
2658
|
+
expectedValue = await this._replaceWithLocalData(value, world);
|
|
2441
2659
|
state.info.expectedValue = expectedValue;
|
|
2442
2660
|
switch (property) {
|
|
2443
2661
|
case "innerText":
|
|
@@ -2485,47 +2703,54 @@ class StableBrowser {
|
|
|
2485
2703
|
}
|
|
2486
2704
|
state.info.value = val;
|
|
2487
2705
|
let regex;
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
const
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
state.info.failCause.assertionFailed = true;
|
|
2503
|
-
state.info.failCause.lastError = errorMessage;
|
|
2504
|
-
throw new Error(errorMessage);
|
|
2505
|
-
}
|
|
2706
|
+
state.info.value = val;
|
|
2707
|
+
const isRegex = expectedValue.startsWith("regex:");
|
|
2708
|
+
const isContains = expectedValue.startsWith("contains:");
|
|
2709
|
+
const isExact = expectedValue.startsWith("exact:");
|
|
2710
|
+
let matchPassed = false;
|
|
2711
|
+
if (isRegex) {
|
|
2712
|
+
const rawPattern = expectedValue.slice(6); // remove "regex:"
|
|
2713
|
+
const lastSlashIndex = rawPattern.lastIndexOf("/");
|
|
2714
|
+
if (rawPattern.startsWith("/") && lastSlashIndex > 0) {
|
|
2715
|
+
const patternBody = rawPattern.slice(1, lastSlashIndex).replace(/\n/g, ".*");
|
|
2716
|
+
const flags = rawPattern.slice(lastSlashIndex + 1) || "gs";
|
|
2717
|
+
const regex = new RegExp(patternBody, flags);
|
|
2718
|
+
state.info.regex = true;
|
|
2719
|
+
matchPassed = regex.test(val);
|
|
2506
2720
|
}
|
|
2507
2721
|
else {
|
|
2508
|
-
//
|
|
2509
|
-
const
|
|
2510
|
-
const
|
|
2511
|
-
|
|
2512
|
-
// Check if all expected lines are present in the actual lines
|
|
2513
|
-
const isPart = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine.trim() === expectedLine.trim()));
|
|
2514
|
-
if (!isPart) {
|
|
2515
|
-
let errorMessage = `The ${property} property has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2516
|
-
state.info.failCause.assertionFailed = true;
|
|
2517
|
-
state.info.failCause.lastError = errorMessage;
|
|
2518
|
-
throw new Error(errorMessage);
|
|
2519
|
-
}
|
|
2722
|
+
// Fallback: treat as literal
|
|
2723
|
+
const escapedPattern = rawPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2724
|
+
const regex = new RegExp(escapedPattern, "g");
|
|
2725
|
+
matchPassed = regex.test(val);
|
|
2520
2726
|
}
|
|
2521
2727
|
}
|
|
2728
|
+
else if (isContains) {
|
|
2729
|
+
const containsValue = expectedValue.slice(9); // remove "contains:"
|
|
2730
|
+
matchPassed = val.includes(containsValue);
|
|
2731
|
+
}
|
|
2732
|
+
else if (isExact) {
|
|
2733
|
+
const exactValue = expectedValue.slice(6); // remove "exact:"
|
|
2734
|
+
matchPassed = val === exactValue;
|
|
2735
|
+
}
|
|
2736
|
+
else if (property === "innerText") {
|
|
2737
|
+
// Default innerText logic
|
|
2738
|
+
const normalizedExpectedValue = expectedValue.replace(/\\n/g, "\n");
|
|
2739
|
+
const valLines = val.split("\n");
|
|
2740
|
+
const expectedLines = normalizedExpectedValue.split("\n");
|
|
2741
|
+
matchPassed = expectedLines.every((expectedLine) => valLines.some((valLine) => valLine.trim() === expectedLine.trim()));
|
|
2742
|
+
}
|
|
2522
2743
|
else {
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2744
|
+
// Fallback exact or loose match
|
|
2745
|
+
const escapedPattern = expectedValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2746
|
+
const regex = new RegExp(escapedPattern, "g");
|
|
2747
|
+
matchPassed = regex.test(val);
|
|
2748
|
+
}
|
|
2749
|
+
if (!matchPassed) {
|
|
2750
|
+
let errorMessage = `The ${property} property has a value of "${val}", but the expected value is "${expectedValue}"`;
|
|
2751
|
+
state.info.failCause.assertionFailed = true;
|
|
2752
|
+
state.info.failCause.lastError = errorMessage;
|
|
2753
|
+
throw new Error(errorMessage);
|
|
2529
2754
|
}
|
|
2530
2755
|
return state.info;
|
|
2531
2756
|
}
|
|
@@ -2556,6 +2781,7 @@ class StableBrowser {
|
|
|
2556
2781
|
allowDisabled: true,
|
|
2557
2782
|
info: {},
|
|
2558
2783
|
};
|
|
2784
|
+
state.options ??= { timeout: timeoutMs };
|
|
2559
2785
|
// Initialize startTime outside try block to ensure it's always accessible
|
|
2560
2786
|
const startTime = Date.now();
|
|
2561
2787
|
let conditionMet = false;
|
|
@@ -3131,7 +3357,16 @@ class StableBrowser {
|
|
|
3131
3357
|
text = text.replace(/\\"/g, '"');
|
|
3132
3358
|
}
|
|
3133
3359
|
const timeout = this._getFindElementTimeout(options);
|
|
3134
|
-
|
|
3360
|
+
//if (!this.fastMode && !this.stepTags.includes("fast-mode")) {
|
|
3361
|
+
let stepFastMode = this.stepTags.includes("fast-mode");
|
|
3362
|
+
if (!stepFastMode) {
|
|
3363
|
+
if (!this.fastMode) {
|
|
3364
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3365
|
+
}
|
|
3366
|
+
else {
|
|
3367
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3368
|
+
}
|
|
3369
|
+
}
|
|
3135
3370
|
const newValue = await this._replaceWithLocalData(text, world);
|
|
3136
3371
|
if (newValue !== text) {
|
|
3137
3372
|
this.logger.info(text + "=" + newValue);
|
|
@@ -3139,6 +3374,11 @@ class StableBrowser {
|
|
|
3139
3374
|
}
|
|
3140
3375
|
let dateAlternatives = findDateAlternatives(text);
|
|
3141
3376
|
let numberAlternatives = findNumberAlternatives(text);
|
|
3377
|
+
if (stepFastMode) {
|
|
3378
|
+
state.onlyFailuresScreenshot = true;
|
|
3379
|
+
state.scroll = false;
|
|
3380
|
+
state.highlight = false;
|
|
3381
|
+
}
|
|
3142
3382
|
try {
|
|
3143
3383
|
await _preCommand(state, this);
|
|
3144
3384
|
state.info.text = text;
|
|
@@ -3258,6 +3498,8 @@ class StableBrowser {
|
|
|
3258
3498
|
operation: "verify_text_with_relation",
|
|
3259
3499
|
log: "***** search for " + textAnchor + " climb " + climb + " and verify " + textToVerify + " found *****\n",
|
|
3260
3500
|
};
|
|
3501
|
+
const cmdStartTime = Date.now();
|
|
3502
|
+
let cmdEndTime = null;
|
|
3261
3503
|
const timeout = this._getFindElementTimeout(options);
|
|
3262
3504
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
3263
3505
|
let newValue = await this._replaceWithLocalData(textAnchor, world);
|
|
@@ -3293,6 +3535,17 @@ class StableBrowser {
|
|
|
3293
3535
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
3294
3536
|
continue;
|
|
3295
3537
|
}
|
|
3538
|
+
else {
|
|
3539
|
+
cmdEndTime = Date.now();
|
|
3540
|
+
if (cmdEndTime - cmdStartTime > 55000) {
|
|
3541
|
+
if (foundAncore) {
|
|
3542
|
+
throw new Error(`Text ${textToVerify} not found in page`);
|
|
3543
|
+
}
|
|
3544
|
+
else {
|
|
3545
|
+
throw new Error(`Text ${textAnchor} not found in page`);
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3296
3549
|
try {
|
|
3297
3550
|
for (let i = 0; i < resultWithElementsFound.length; i++) {
|
|
3298
3551
|
foundAncore = true;
|
|
@@ -3431,7 +3684,7 @@ class StableBrowser {
|
|
|
3431
3684
|
Object.assign(e, { info: info });
|
|
3432
3685
|
error = e;
|
|
3433
3686
|
// throw e;
|
|
3434
|
-
await _commandError({ text: "visualVerification", operation: "visualVerification",
|
|
3687
|
+
await _commandError({ text: "visualVerification", operation: "visualVerification", info }, e, this);
|
|
3435
3688
|
}
|
|
3436
3689
|
finally {
|
|
3437
3690
|
const endTime = Date.now();
|
|
@@ -3780,6 +4033,22 @@ class StableBrowser {
|
|
|
3780
4033
|
}
|
|
3781
4034
|
}
|
|
3782
4035
|
async waitForPageLoad(options = {}, world = null) {
|
|
4036
|
+
// try {
|
|
4037
|
+
// let currentPagePath = null;
|
|
4038
|
+
// currentPagePath = new URL(this.page.url()).pathname;
|
|
4039
|
+
// if (this.latestPagePath) {
|
|
4040
|
+
// // get the currect page path and compare with the latest page path
|
|
4041
|
+
// if (this.latestPagePath === currentPagePath) {
|
|
4042
|
+
// // if the page path is the same, do not wait for page load
|
|
4043
|
+
// console.log("No page change: " + currentPagePath);
|
|
4044
|
+
// return;
|
|
4045
|
+
// }
|
|
4046
|
+
// }
|
|
4047
|
+
// this.latestPagePath = currentPagePath;
|
|
4048
|
+
// } catch (e) {
|
|
4049
|
+
// console.debug("Error getting current page path: ", e);
|
|
4050
|
+
// }
|
|
4051
|
+
//console.log("Waiting for page load");
|
|
3783
4052
|
let timeout = this._getLoadTimeout(options);
|
|
3784
4053
|
const promiseArray = [];
|
|
3785
4054
|
// let waitForNetworkIdle = true;
|
|
@@ -3814,7 +4083,10 @@ class StableBrowser {
|
|
|
3814
4083
|
}
|
|
3815
4084
|
}
|
|
3816
4085
|
finally {
|
|
3817
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
4086
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
4087
|
+
if (options && !options.noSleep) {
|
|
4088
|
+
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
4089
|
+
}
|
|
3818
4090
|
({ screenshotId, screenshotPath } = await this._screenShot(options, world));
|
|
3819
4091
|
const endTime = Date.now();
|
|
3820
4092
|
_reportToWorld(world, {
|
|
@@ -3868,7 +4140,7 @@ class StableBrowser {
|
|
|
3868
4140
|
}
|
|
3869
4141
|
operation = options.operation;
|
|
3870
4142
|
// validate operation is one of the supported operations
|
|
3871
|
-
if (operation != "click" && operation != "hover+click") {
|
|
4143
|
+
if (operation != "click" && operation != "hover+click" && operation != "hover") {
|
|
3872
4144
|
throw new Error("operation is not supported");
|
|
3873
4145
|
}
|
|
3874
4146
|
const state = {
|
|
@@ -3937,6 +4209,17 @@ class StableBrowser {
|
|
|
3937
4209
|
state.element = results[0];
|
|
3938
4210
|
await performAction("hover+click", state.element, options, this, state, _params);
|
|
3939
4211
|
break;
|
|
4212
|
+
case "hover":
|
|
4213
|
+
if (!options.css) {
|
|
4214
|
+
throw new Error("css is not defined");
|
|
4215
|
+
}
|
|
4216
|
+
const result1 = await findElementsInArea(options.css, cellArea, this, options);
|
|
4217
|
+
if (result1.length === 0) {
|
|
4218
|
+
throw new Error(`Element not found in cell area`);
|
|
4219
|
+
}
|
|
4220
|
+
state.element = result1[0];
|
|
4221
|
+
await performAction("hover", state.element, options, this, state, _params);
|
|
4222
|
+
break;
|
|
3940
4223
|
default:
|
|
3941
4224
|
throw new Error("operation is not supported");
|
|
3942
4225
|
}
|
|
@@ -3950,6 +4233,12 @@ class StableBrowser {
|
|
|
3950
4233
|
}
|
|
3951
4234
|
saveTestDataAsGlobal(options, world) {
|
|
3952
4235
|
const dataFile = _getDataFile(world, this.context, this);
|
|
4236
|
+
if (process.env.MODE === "executions") {
|
|
4237
|
+
const globalDataFile = path.join(this.project_path, "global_test_data.json");
|
|
4238
|
+
fs.copyFileSync(dataFile, globalDataFile);
|
|
4239
|
+
this.logger.info("Save the scenario test data to " + globalDataFile + " as global for the following scenarios.");
|
|
4240
|
+
return;
|
|
4241
|
+
}
|
|
3953
4242
|
process.env.GLOBAL_TEST_DATA_FILE = dataFile;
|
|
3954
4243
|
this.logger.info("Save the scenario test data as global for the following scenarios.");
|
|
3955
4244
|
}
|
|
@@ -4052,6 +4341,7 @@ class StableBrowser {
|
|
|
4052
4341
|
if (world && world.attach) {
|
|
4053
4342
|
world.attach(this.context.reportFolder, { mediaType: "text/plain" });
|
|
4054
4343
|
}
|
|
4344
|
+
this.context.loadedRoutes = null;
|
|
4055
4345
|
this.beforeScenarioCalled = true;
|
|
4056
4346
|
if (scenario && scenario.pickle && scenario.pickle.name) {
|
|
4057
4347
|
this.scenarioName = scenario.pickle.name;
|
|
@@ -4081,8 +4371,10 @@ class StableBrowser {
|
|
|
4081
4371
|
}
|
|
4082
4372
|
async afterScenario(world, scenario) { }
|
|
4083
4373
|
async beforeStep(world, step) {
|
|
4374
|
+
this.stepTags = [];
|
|
4084
4375
|
if (!this.beforeScenarioCalled) {
|
|
4085
4376
|
this.beforeScenario(world, step);
|
|
4377
|
+
this.context.loadedRoutes = null;
|
|
4086
4378
|
}
|
|
4087
4379
|
if (this.stepIndex === undefined) {
|
|
4088
4380
|
this.stepIndex = 0;
|
|
@@ -4092,7 +4384,12 @@ class StableBrowser {
|
|
|
4092
4384
|
}
|
|
4093
4385
|
if (step && step.pickleStep && step.pickleStep.text) {
|
|
4094
4386
|
this.stepName = step.pickleStep.text;
|
|
4095
|
-
|
|
4387
|
+
let printableStepName = this.stepName;
|
|
4388
|
+
// take the printableStepName and replace quated value with \x1b[33m and \x1b[0m
|
|
4389
|
+
printableStepName = printableStepName.replace(/"([^"]*)"/g, (match, p1) => {
|
|
4390
|
+
return `\x1b[33m"${p1}"\x1b[0m`;
|
|
4391
|
+
});
|
|
4392
|
+
this.logger.info("\x1b[38;5;208mstep:\x1b[0m " + printableStepName);
|
|
4096
4393
|
}
|
|
4097
4394
|
else if (step && step.text) {
|
|
4098
4395
|
this.stepName = step.text;
|
|
@@ -4107,7 +4404,10 @@ class StableBrowser {
|
|
|
4107
4404
|
}
|
|
4108
4405
|
if (this.initSnapshotTaken === false) {
|
|
4109
4406
|
this.initSnapshotTaken = true;
|
|
4110
|
-
if (world &&
|
|
4407
|
+
if (world &&
|
|
4408
|
+
world.attach &&
|
|
4409
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4410
|
+
(!this.fastMode || this.stepTags.includes("fast-mode"))) {
|
|
4111
4411
|
const snapshot = await this.getAriaSnapshot();
|
|
4112
4412
|
if (snapshot) {
|
|
4113
4413
|
await world.attach(JSON.stringify(snapshot), "application/json+snapshot-before");
|
|
@@ -4115,8 +4415,13 @@ class StableBrowser {
|
|
|
4115
4415
|
}
|
|
4116
4416
|
}
|
|
4117
4417
|
this.context.routeResults = null;
|
|
4118
|
-
|
|
4119
|
-
|
|
4418
|
+
this.context.loadedRoutes = null;
|
|
4419
|
+
await registerBeforeStepRoutes(this.context, this.stepName, world);
|
|
4420
|
+
networkBeforeStep(this.stepName, this.context);
|
|
4421
|
+
this.inStepReport = false;
|
|
4422
|
+
}
|
|
4423
|
+
setStepTags(tags) {
|
|
4424
|
+
this.stepTags = tags;
|
|
4120
4425
|
}
|
|
4121
4426
|
async getAriaSnapshot() {
|
|
4122
4427
|
try {
|
|
@@ -4190,7 +4495,7 @@ class StableBrowser {
|
|
|
4190
4495
|
state.payload = payload;
|
|
4191
4496
|
if (commandStatus === "FAILED") {
|
|
4192
4497
|
state.throwError = true;
|
|
4193
|
-
throw new Error(
|
|
4498
|
+
throw new Error(commandText);
|
|
4194
4499
|
}
|
|
4195
4500
|
}
|
|
4196
4501
|
catch (e) {
|
|
@@ -4200,7 +4505,7 @@ class StableBrowser {
|
|
|
4200
4505
|
await _commandFinally(state, this);
|
|
4201
4506
|
}
|
|
4202
4507
|
}
|
|
4203
|
-
async afterStep(world, step) {
|
|
4508
|
+
async afterStep(world, step, result) {
|
|
4204
4509
|
this.stepName = null;
|
|
4205
4510
|
if (this.context && this.context.browserObject && this.context.browserObject.trace === true) {
|
|
4206
4511
|
if (this.context.browserObject.context) {
|
|
@@ -4219,7 +4524,17 @@ class StableBrowser {
|
|
|
4219
4524
|
if (this.context) {
|
|
4220
4525
|
this.context.examplesRow = null;
|
|
4221
4526
|
}
|
|
4222
|
-
if (
|
|
4527
|
+
if (!this.inStepReport) {
|
|
4528
|
+
// check the step result
|
|
4529
|
+
if (result && result.status === "FAILED" && world && world.attach) {
|
|
4530
|
+
await this.addCommandToReport(result.message ? result.message : "Step failed", "FAILED", `${result.message}`, { type: "text", screenshot: true }, world);
|
|
4531
|
+
}
|
|
4532
|
+
}
|
|
4533
|
+
if (world &&
|
|
4534
|
+
world.attach &&
|
|
4535
|
+
!process.env.DISABLE_SNAPSHOT &&
|
|
4536
|
+
!this.fastMode &&
|
|
4537
|
+
!this.stepTags.includes("fast-mode")) {
|
|
4223
4538
|
const snapshot = await this.getAriaSnapshot();
|
|
4224
4539
|
if (snapshot) {
|
|
4225
4540
|
const obj = {};
|
|
@@ -4227,6 +4542,11 @@ class StableBrowser {
|
|
|
4227
4542
|
}
|
|
4228
4543
|
}
|
|
4229
4544
|
this.context.routeResults = await registerAfterStepRoutes(this.context, world);
|
|
4545
|
+
if (this.context.routeResults) {
|
|
4546
|
+
if (world && world.attach) {
|
|
4547
|
+
await world.attach(JSON.stringify(this.context.routeResults), "application/json+intercept-results");
|
|
4548
|
+
}
|
|
4549
|
+
}
|
|
4230
4550
|
if (!process.env.TEMP_RUN) {
|
|
4231
4551
|
const state = {
|
|
4232
4552
|
world,
|
|
@@ -4250,7 +4570,13 @@ class StableBrowser {
|
|
|
4250
4570
|
await _commandFinally(state, this);
|
|
4251
4571
|
}
|
|
4252
4572
|
}
|
|
4253
|
-
networkAfterStep(this.stepName);
|
|
4573
|
+
networkAfterStep(this.stepName, this.context);
|
|
4574
|
+
if (process.env.TEMP_RUN === "true") {
|
|
4575
|
+
// Put a sleep for some time to allow the browser to finish processing
|
|
4576
|
+
if (!this.stepTags.includes("fast-mode")) {
|
|
4577
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
4578
|
+
}
|
|
4579
|
+
}
|
|
4254
4580
|
}
|
|
4255
4581
|
}
|
|
4256
4582
|
function createTimedPromise(promise, label) {
|