@dev-blinq/cucumber_client 1.0.1266-dev → 1.0.1268-dev
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.
|
@@ -60,7 +60,6 @@ async function loadUserData(user) {
|
|
|
60
60
|
async function verifyTextExistsInPage(text) {
|
|
61
61
|
await context.web.verifyTextExistInPage(text, null, this);
|
|
62
62
|
}
|
|
63
|
-
|
|
64
63
|
Then("Verify the text {string} can be found in the page", verifyTextExistsInPage);
|
|
65
64
|
|
|
66
65
|
/**
|
|
@@ -87,6 +86,7 @@ async function fillElement(elementDescription, value) {
|
|
|
87
86
|
}
|
|
88
87
|
When("fill {string} with {string}", fillElement);
|
|
89
88
|
When("Fill {string} with {string}", fillElement);
|
|
89
|
+
|
|
90
90
|
/**
|
|
91
91
|
* Verify text does not exist in page
|
|
92
92
|
* @param {string} text the text to verify does not exist in page
|
|
@@ -107,6 +107,24 @@ async function navigateTo(url) {
|
|
|
107
107
|
}
|
|
108
108
|
When("Navigate to {string}", navigateTo);
|
|
109
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Navigate to the current page
|
|
112
|
+
* @protect
|
|
113
|
+
*/
|
|
114
|
+
async function browserNavigateBack() {
|
|
115
|
+
await context.web.goBack({}, this);
|
|
116
|
+
}
|
|
117
|
+
Then("Browser navigate back", browserNavigateBack);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Navigate forward in browser history
|
|
121
|
+
* @protect
|
|
122
|
+
*/
|
|
123
|
+
async function browserNavigateForward() {
|
|
124
|
+
await context.web.goForward({}, this);
|
|
125
|
+
}
|
|
126
|
+
Then("Browser navigate forward", browserNavigateForward);
|
|
127
|
+
|
|
110
128
|
/**
|
|
111
129
|
* Store browser session "<path>"
|
|
112
130
|
* @param {string} filePath the file path or empty to store in the test data file
|
|
@@ -141,6 +159,7 @@ Then(
|
|
|
141
159
|
"Identify the text {string}, climb {string} levels in the page, validate text {string} can be found in the context",
|
|
142
160
|
verifyTextRelatedToText
|
|
143
161
|
);
|
|
162
|
+
|
|
144
163
|
/**
|
|
145
164
|
* execute bruno single request given the bruno project is placed in a folder called bruno under the root of the cucumber project
|
|
146
165
|
* @requestName the name of the bruno request file
|
|
@@ -149,7 +168,6 @@ Then(
|
|
|
149
168
|
async function runBrunoRequest(requestName) {
|
|
150
169
|
await executeBrunoRequest(requestName, {}, context, this);
|
|
151
170
|
}
|
|
152
|
-
|
|
153
171
|
When("Bruno - {string}", runBrunoRequest);
|
|
154
172
|
When("bruno - {string}", runBrunoRequest);
|
|
155
173
|
|
|
@@ -163,41 +181,41 @@ async function verify_the_downloaded_file_exists(fileName) {
|
|
|
163
181
|
const downloadFile = path.join(downloadFolder, fileName);
|
|
164
182
|
await verifyFileExists(downloadFile, {}, context, this);
|
|
165
183
|
}
|
|
166
|
-
|
|
167
184
|
Then("Verify the file {string} exists", { timeout: 60000 }, verify_the_downloaded_file_exists);
|
|
168
185
|
|
|
169
|
-
When("Noop", async function(){})
|
|
170
|
-
|
|
171
186
|
/**
|
|
172
|
-
*
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
*/
|
|
187
|
+
* Noop step for running only hooks
|
|
188
|
+
*/
|
|
189
|
+
When("Noop", async function () {});
|
|
176
190
|
|
|
177
|
-
|
|
178
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Verify the page url is "<url>"
|
|
193
|
+
* @param {string} url URL to be verified against current URL
|
|
194
|
+
* @protect
|
|
195
|
+
*/
|
|
196
|
+
async function verify_page_url(url) {
|
|
197
|
+
await context.web.verifyPagePath(url, {}, this);
|
|
179
198
|
}
|
|
180
199
|
Then("Verify the page url is {string}", verify_page_url);
|
|
181
200
|
|
|
182
201
|
/**
|
|
183
|
-
* Verify the page title is "<title>"
|
|
184
|
-
* @param {string} title Title to be verified against current Title
|
|
185
|
-
* @protect
|
|
186
|
-
*/
|
|
187
|
-
async function verify_page_title(title){
|
|
202
|
+
* Verify the page title is "<title>"
|
|
203
|
+
* @param {string} title Title to be verified against current Title
|
|
204
|
+
* @protect
|
|
205
|
+
*/
|
|
206
|
+
async function verify_page_title(title) {
|
|
188
207
|
await context.web.verifyPageTitle(title, {}, this);
|
|
189
208
|
}
|
|
190
|
-
Then("Verify the page title is {string}",verify_page_title)
|
|
209
|
+
Then("Verify the page title is {string}", verify_page_title);
|
|
191
210
|
|
|
192
211
|
/**
|
|
193
|
-
* Explicit wait/sleep function that pauses execution for a specified duration
|
|
194
|
-
* @param {duration} - Duration to sleep in milliseconds (default: 1000ms)
|
|
195
|
-
* @param {options} - Optional configuration object
|
|
196
|
-
* @param {world} - Optional world context
|
|
197
|
-
* @returns Promise that resolves after the specified duration
|
|
198
|
-
*/
|
|
212
|
+
* Explicit wait/sleep function that pauses execution for a specified duration
|
|
213
|
+
* @param {duration} - Duration to sleep in milliseconds (default: 1000ms)
|
|
214
|
+
* @param {options} - Optional configuration object
|
|
215
|
+
* @param {world} - Optional world context
|
|
216
|
+
* @returns Promise that resolves after the specified duration
|
|
217
|
+
*/
|
|
199
218
|
async function sleep(duration) {
|
|
200
219
|
await context.web.sleep(duration, {}, null);
|
|
201
220
|
}
|
|
202
|
-
|
|
203
|
-
Then("Sleep for {string} ms", {timeout: -1}, sleep);
|
|
221
|
+
Then("Sleep for {string} ms", { timeout: -1 }, sleep);
|
|
@@ -310,6 +310,14 @@ const invertStableCommand = (call, elements, stepParams) => {
|
|
|
310
310
|
break;
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
case "goBack":
|
|
314
|
+
step.type = Types.GO_BACK;
|
|
315
|
+
break;
|
|
316
|
+
|
|
317
|
+
case "goForward":
|
|
318
|
+
step.type = Types.GO_FORWARD;
|
|
319
|
+
break;
|
|
320
|
+
|
|
313
321
|
case "reloadPage":
|
|
314
322
|
step.type = Types.RELOAD;
|
|
315
323
|
break;
|
|
@@ -24,11 +24,11 @@ export function getInitScript(config, options) {
|
|
|
24
24
|
window.__bvt_Recorder_config = ${JSON.stringify(config ?? null)};
|
|
25
25
|
window.__PW_options = ${JSON.stringify(options ?? null)};
|
|
26
26
|
`;
|
|
27
|
-
const recorderScript = readFileSync(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
recorderScript
|
|
27
|
+
const recorderScript = readFileSync(
|
|
28
|
+
path.join(__dirname, "..", "..", "assets", "bundled_scripts", "recorder.js"),
|
|
29
|
+
"utf8"
|
|
31
30
|
);
|
|
31
|
+
return preScript + recorderScript;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async function evaluate(frame, script) {
|
|
@@ -190,7 +190,7 @@ export class BVTRecorder {
|
|
|
190
190
|
|
|
191
191
|
this.lastKnownUrlPath = "";
|
|
192
192
|
// TODO: what is world?
|
|
193
|
-
this.world = { attach: () => {
|
|
193
|
+
this.world = { attach: () => {} };
|
|
194
194
|
this.shouldTakeScreenshot = true;
|
|
195
195
|
this.watcher = null;
|
|
196
196
|
}
|
|
@@ -294,7 +294,7 @@ export class BVTRecorder {
|
|
|
294
294
|
process.env.CDP_LISTEN_PORT = this.#remoteDebuggerPort;
|
|
295
295
|
|
|
296
296
|
this.stepRunner.setRemoteDebugPort(this.#remoteDebuggerPort);
|
|
297
|
-
this.world = { attach: () => {
|
|
297
|
+
this.world = { attach: () => {} };
|
|
298
298
|
|
|
299
299
|
const ai_config_file = path.join(this.projectDir, "ai_config.json");
|
|
300
300
|
let ai_config = {};
|
|
@@ -433,6 +433,75 @@ export class BVTRecorder {
|
|
|
433
433
|
}
|
|
434
434
|
});
|
|
435
435
|
}
|
|
436
|
+
|
|
437
|
+
hasHistoryReplacementAtIndex(previousEntries, currentEntries, index) {
|
|
438
|
+
if (!previousEntries || !currentEntries) return false;
|
|
439
|
+
if (index >= previousEntries.length || index >= currentEntries.length) return false;
|
|
440
|
+
|
|
441
|
+
const prevEntry = previousEntries[index];
|
|
442
|
+
// console.log("prevEntry", prevEntry);
|
|
443
|
+
const currEntry = currentEntries[index];
|
|
444
|
+
// console.log("currEntry", currEntry);
|
|
445
|
+
|
|
446
|
+
// Check if the entry at this index has been replaced
|
|
447
|
+
return prevEntry.id !== currEntry.id;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Even simpler approach for your specific case
|
|
451
|
+
analyzeTransitionType(entries, currentIndex, currentEntry) {
|
|
452
|
+
// console.log("Analyzing transition type");
|
|
453
|
+
// console.log("===========================");
|
|
454
|
+
// console.log("Current Index:", currentIndex);
|
|
455
|
+
// console.log("Current Entry:", currentEntry);
|
|
456
|
+
// console.log("Current Entries:", entries);
|
|
457
|
+
// console.log("Current entries length:", entries.length);
|
|
458
|
+
// console.log("===========================");
|
|
459
|
+
// console.log("Previous Index:", this.previousIndex);
|
|
460
|
+
// // console.log("Previous Entry:", this.previousEntries[this.previousIndex]);
|
|
461
|
+
// console.log("Previous Entries:", this.previousEntries);
|
|
462
|
+
// console.log("Previous entries length:", this.previousHistoryLength);
|
|
463
|
+
|
|
464
|
+
if (this.previousIndex === null || this.previousHistoryLength === null || !this.previousEntries) {
|
|
465
|
+
return {
|
|
466
|
+
action: "initial",
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const indexDiff = currentIndex - this.previousIndex;
|
|
471
|
+
const lengthDiff = entries.length - this.previousHistoryLength;
|
|
472
|
+
|
|
473
|
+
// Backward navigation
|
|
474
|
+
if (indexDiff < 0) {
|
|
475
|
+
return { action: "back" };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Forward navigation
|
|
479
|
+
if (indexDiff > 0 && lengthDiff === 0) {
|
|
480
|
+
// Check if the entry at current index is the same as before
|
|
481
|
+
const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
|
|
482
|
+
|
|
483
|
+
if (entryReplaced) {
|
|
484
|
+
return { action: "navigate" }; // New navigation that replaced forward history
|
|
485
|
+
} else {
|
|
486
|
+
return { action: "forward" }; // True forward navigation
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// New navigation (history grew)
|
|
491
|
+
if (lengthDiff > 0) {
|
|
492
|
+
return { action: "navigate" };
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Same position, same length
|
|
496
|
+
if (lengthDiff <= 0) {
|
|
497
|
+
const entryReplaced = this.hasHistoryReplacementAtIndex(this.previousEntries, entries, currentIndex);
|
|
498
|
+
|
|
499
|
+
return entryReplaced ? { action: "navigate" } : { action: "reload" };
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return { action: "unknown" };
|
|
503
|
+
}
|
|
504
|
+
|
|
436
505
|
async getCurrentTransition() {
|
|
437
506
|
if (this?.web?.browser?._name !== "chromium") {
|
|
438
507
|
return;
|
|
@@ -441,35 +510,70 @@ export class BVTRecorder {
|
|
|
441
510
|
|
|
442
511
|
try {
|
|
443
512
|
const result = await client.send("Page.getNavigationHistory");
|
|
444
|
-
// console.log("
|
|
513
|
+
// console.log("Navigation History:", result);
|
|
445
514
|
const entries = result.entries;
|
|
446
515
|
const currentIndex = result.currentIndex;
|
|
447
516
|
|
|
448
517
|
// ignore if currentIndex is not the last entry
|
|
449
|
-
if (currentIndex !== entries.length - 1) return;
|
|
518
|
+
// if (currentIndex !== entries.length - 1) return;
|
|
450
519
|
|
|
451
520
|
const currentEntry = entries[currentIndex];
|
|
452
|
-
|
|
521
|
+
const transitionInfo = this.analyzeTransitionType(entries, currentIndex, currentEntry);
|
|
522
|
+
this.previousIndex = currentIndex;
|
|
523
|
+
this.previousHistoryLength = entries.length;
|
|
524
|
+
this.previousUrl = currentEntry.url;
|
|
525
|
+
this.previousEntries = [...entries]; // Store a copy of current entries
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
currentEntry,
|
|
529
|
+
navigationAction: transitionInfo.action,
|
|
530
|
+
};
|
|
453
531
|
} catch (error) {
|
|
454
|
-
console.error("Error in getTransistionType event");
|
|
532
|
+
console.error("Error in getTransistionType event", error);
|
|
455
533
|
} finally {
|
|
456
534
|
await client.detach();
|
|
457
535
|
}
|
|
458
536
|
}
|
|
459
|
-
|
|
537
|
+
userInitiatedTransitionTypes = ["typed", "address_bar"];
|
|
460
538
|
async handlePageTransition() {
|
|
461
539
|
const transition = await this.getCurrentTransition();
|
|
462
540
|
if (!transition) return;
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
541
|
+
|
|
542
|
+
const { currentEntry, navigationAction } = transition;
|
|
543
|
+
|
|
544
|
+
switch (navigationAction) {
|
|
545
|
+
case "initial":
|
|
546
|
+
// console.log("Initial navigation, no action taken");
|
|
547
|
+
return;
|
|
548
|
+
case "navigate":
|
|
549
|
+
// console.log("transitionType", transition.transitionType);
|
|
550
|
+
// console.log("sending onGoto event", { url: currentEntry.url,
|
|
551
|
+
// type: "navigate", });
|
|
552
|
+
if (this.userInitiatedTransitionTypes.includes(currentEntry.transitionType)) {
|
|
553
|
+
const env = JSON.parse(readFileSync(this.envName), "utf8");
|
|
554
|
+
const baseUrl = env.baseUrl;
|
|
555
|
+
let url = currentEntry.userTypedURL;
|
|
556
|
+
if (baseUrl && url.startsWith(baseUrl)) {
|
|
557
|
+
url = url.replace(baseUrl, "{{env.baseUrl}}");
|
|
558
|
+
}
|
|
559
|
+
// console.log("User initiated transition");
|
|
560
|
+
this.sendEvent(this.events.onGoto, { url, type: "navigate" });
|
|
561
|
+
}
|
|
562
|
+
return;
|
|
563
|
+
case "back":
|
|
564
|
+
// console.log("User navigated back");
|
|
565
|
+
// console.log("sending onGoto event", {
|
|
566
|
+
// type: "back",
|
|
567
|
+
// });
|
|
568
|
+
this.sendEvent(this.events.onGoto, { type: "back" });
|
|
569
|
+
return;
|
|
570
|
+
case "forward":
|
|
571
|
+
// console.log("User navigated forward"); console.log("sending onGoto event", { type: "forward", });
|
|
572
|
+
this.sendEvent(this.events.onGoto, { type: "forward" });
|
|
573
|
+
return;
|
|
574
|
+
default:
|
|
575
|
+
this.sendEvent(this.events.onGoto, { type: "unknown" });
|
|
576
|
+
return;
|
|
473
577
|
}
|
|
474
578
|
}
|
|
475
579
|
|
|
@@ -596,8 +700,14 @@ export class BVTRecorder {
|
|
|
596
700
|
}
|
|
597
701
|
async closeBrowser() {
|
|
598
702
|
delete process.env.TEMP_RUN;
|
|
599
|
-
await this.watcher.close().then(() => {
|
|
703
|
+
await this.watcher.close().then(() => {});
|
|
704
|
+
|
|
600
705
|
this.watcher = null;
|
|
706
|
+
this.previousIndex = null;
|
|
707
|
+
this.previousHistoryLength = null;
|
|
708
|
+
this.previousUrl = null;
|
|
709
|
+
this.previousEntries = null;
|
|
710
|
+
|
|
601
711
|
await closeContext();
|
|
602
712
|
this.pageSet.clear();
|
|
603
713
|
}
|
package/bin/client/recording.js
CHANGED
|
@@ -11,6 +11,8 @@ const Types = {
|
|
|
11
11
|
PARAMETERIZED_CLICK: "parameterized_click",
|
|
12
12
|
CONTEXT_CLICK: "context_click",
|
|
13
13
|
NAVIGATE: "navigate",
|
|
14
|
+
GO_BACK: "browser_go_back",
|
|
15
|
+
GO_FORWARD: "browser_go_forward",
|
|
14
16
|
FILL: "fill_element",
|
|
15
17
|
FILL_SIMPLE: "fill_simple",
|
|
16
18
|
EXECUTE: "execute_page_method",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dev-blinq/cucumber_client",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1268-dev",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "bin/index.js",
|
|
6
6
|
"types": "bin/index.d.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@cucumber/tag-expressions": "^6.1.1",
|
|
32
32
|
"@dev-blinq/cucumber-js": "1.0.174-dev",
|
|
33
33
|
"@faker-js/faker": "^8.1.0",
|
|
34
|
-
"automation_model": "1.0.
|
|
34
|
+
"automation_model": "1.0.753-dev",
|
|
35
35
|
"axios": "^1.7.4",
|
|
36
36
|
"chokidar": "^3.6.0",
|
|
37
37
|
"create-require": "^1.1.1",
|