playwright-cucumber-ts-steps 1.1.6 → 1.1.8
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/README.md +10 -15
- package/dist/backend/actions/click.d.ts +2 -0
- package/dist/backend/actions/click.d.ts.map +1 -0
- package/dist/backend/actions/click.js +179 -0
- package/dist/backend/actions/find.d.ts +2 -0
- package/dist/backend/actions/find.d.ts.map +1 -0
- package/dist/backend/actions/find.js +291 -0
- package/dist/backend/actions/form.d.ts +2 -0
- package/dist/backend/actions/form.d.ts.map +1 -0
- package/dist/backend/actions/form.js +185 -0
- package/dist/backend/actions/formTable.js +1 -1
- package/dist/backend/actions/frames.d.ts +2 -0
- package/dist/backend/actions/frames.d.ts.map +1 -0
- package/dist/backend/actions/frames.js +60 -0
- package/dist/backend/actions/index.d.ts +10 -0
- package/dist/backend/actions/index.d.ts.map +1 -1
- package/dist/backend/actions/index.js +10 -0
- package/dist/backend/actions/inputs.d.ts +2 -0
- package/dist/backend/actions/inputs.d.ts.map +1 -0
- package/dist/backend/actions/inputs.js +177 -0
- package/dist/backend/actions/keyboard.d.ts +2 -0
- package/dist/backend/actions/keyboard.d.ts.map +1 -0
- package/dist/backend/actions/keyboard.js +62 -0
- package/dist/backend/actions/misc.d.ts +2 -0
- package/dist/backend/actions/misc.d.ts.map +1 -0
- package/dist/backend/actions/misc.js +144 -0
- package/dist/backend/actions/mobile.d.ts +2 -0
- package/dist/backend/actions/mobile.d.ts.map +1 -0
- package/dist/backend/actions/mobile.js +87 -0
- package/dist/backend/actions/mouse.d.ts +2 -0
- package/dist/backend/actions/mouse.d.ts.map +1 -0
- package/dist/backend/actions/mouse.js +105 -0
- package/dist/backend/actions/navigation.js +31 -7
- package/dist/backend/actions/waits.d.ts +2 -0
- package/dist/backend/actions/waits.d.ts.map +1 -0
- package/dist/backend/actions/waits.js +51 -0
- package/dist/backend/api/index.d.ts +1 -0
- package/dist/backend/api/index.d.ts.map +1 -1
- package/dist/backend/api/index.js +1 -0
- package/dist/backend/api/network.d.ts +2 -0
- package/dist/backend/api/network.d.ts.map +1 -0
- package/dist/backend/api/network.js +145 -0
- package/dist/backend/assertions/pageState.js +25 -14
- package/dist/backend/assertions/visibility.js +116 -12
- package/dist/backend/utils/state.d.ts +18 -0
- package/dist/backend/utils/state.d.ts.map +1 -0
- package/dist/backend/utils/state.js +84 -0
- package/dist/core/registry.d.ts +14 -14
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +13 -4
- package/dist/core/runner.d.ts.map +1 -1
- package/dist/core/runner.js +91 -37
- package/package.json +1 -1
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const registry_1 = require("../../core/registry");
|
|
4
|
+
const state_1 = require("../utils/state");
|
|
5
|
+
// ==================================================
|
|
6
|
+
// 1. TIMING & WAITS
|
|
7
|
+
// ==================================================
|
|
8
|
+
/**
|
|
9
|
+
* Pauses execution for a set amount of time.
|
|
10
|
+
* Pattern: When I wait for 1000 milliseconds
|
|
11
|
+
*/
|
|
12
|
+
(0, registry_1.Step)("I wait for {int} milliseconds", async (page, ms) => {
|
|
13
|
+
await page.waitForTimeout(ms);
|
|
14
|
+
console.log(`⏳ Waited for ${ms}ms`);
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Pauses execution for a set amount of seconds.
|
|
18
|
+
* Pattern: When I wait for 5 seconds
|
|
19
|
+
*/
|
|
20
|
+
(0, registry_1.Step)("I wait for {int} seconds", async (page, seconds) => {
|
|
21
|
+
await page.waitForTimeout(seconds * 1000);
|
|
22
|
+
console.log(`⏳ Waited for ${seconds}s`);
|
|
23
|
+
});
|
|
24
|
+
// ==================================================
|
|
25
|
+
// 2. DEBUGGING
|
|
26
|
+
// ==================================================
|
|
27
|
+
/**
|
|
28
|
+
* Pauses the test execution and opens the Playwright Inspector.
|
|
29
|
+
* Useful for manual debugging during a run.
|
|
30
|
+
* Pattern: When I pause
|
|
31
|
+
*/
|
|
32
|
+
(0, registry_1.Step)("I pause", async (page) => {
|
|
33
|
+
console.log("⏸️ Pausing test execution...");
|
|
34
|
+
await page.pause();
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Alias for pause.
|
|
38
|
+
* Pattern: When I debug
|
|
39
|
+
*/
|
|
40
|
+
(0, registry_1.Step)("I debug", async (page) => {
|
|
41
|
+
console.log("🐞 Debugging...");
|
|
42
|
+
await page.pause();
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Prints a message to the console logs.
|
|
46
|
+
* Pattern: When I log "Hello World"
|
|
47
|
+
*/
|
|
48
|
+
(0, registry_1.Step)("I log {string}", async (page, message) => {
|
|
49
|
+
console.log(`📝 LOG: ${message}`);
|
|
50
|
+
});
|
|
51
|
+
// ==================================================
|
|
52
|
+
// 3. FOCUS & BLUR
|
|
53
|
+
// ==================================================
|
|
54
|
+
/**
|
|
55
|
+
* Focuses on the currently stored element.
|
|
56
|
+
* Pattern: When I focus
|
|
57
|
+
*/
|
|
58
|
+
(0, registry_1.Step)("I focus", async (page) => {
|
|
59
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
60
|
+
await element.focus();
|
|
61
|
+
console.log("👀 Focused on stored element");
|
|
62
|
+
});
|
|
63
|
+
/**
|
|
64
|
+
* Blurs (un-focuses) the currently stored element.
|
|
65
|
+
* Pattern: When I blur
|
|
66
|
+
*/
|
|
67
|
+
(0, registry_1.Step)("I blur", async (page) => {
|
|
68
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
69
|
+
// Playwright doesn't have a direct .blur(), so we use JS evaluation
|
|
70
|
+
await element.evaluate((el) => {
|
|
71
|
+
if (el instanceof HTMLElement)
|
|
72
|
+
el.blur();
|
|
73
|
+
});
|
|
74
|
+
console.log("🌫️ Blurred stored element");
|
|
75
|
+
});
|
|
76
|
+
// ==================================================
|
|
77
|
+
// 4. BROWSER STORAGE (Cookies / Local Storage)
|
|
78
|
+
// ==================================================
|
|
79
|
+
/**
|
|
80
|
+
* Sets a cookie for the current context.
|
|
81
|
+
* Pattern: When I set cookie "session_id" to "12345"
|
|
82
|
+
*/
|
|
83
|
+
(0, registry_1.Step)("I set cookie {string} to {string}", async (page, name, value) => {
|
|
84
|
+
const context = page.context();
|
|
85
|
+
const url = page.url();
|
|
86
|
+
// We need a domain or url to set cookies. We use the current page URL.
|
|
87
|
+
await context.addCookies([{ name, value, url }]);
|
|
88
|
+
console.log(`🍪 Set cookie "${name}"`);
|
|
89
|
+
});
|
|
90
|
+
/**
|
|
91
|
+
* Clears all cookies.
|
|
92
|
+
* Pattern: When I clear all cookies
|
|
93
|
+
*/
|
|
94
|
+
(0, registry_1.Step)("I clear all cookies", async (page) => {
|
|
95
|
+
const context = page.context();
|
|
96
|
+
await context.clearCookies();
|
|
97
|
+
console.log("🍪 Cleared all cookies");
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* Sets a Local Storage item.
|
|
101
|
+
* Pattern: When I set local storage item "theme" to "dark"
|
|
102
|
+
*/
|
|
103
|
+
(0, registry_1.Step)("I set local storage item {string} to {string}", async (page, key, value) => {
|
|
104
|
+
await page.evaluate(({ k, v }) => localStorage.setItem(k, v), {
|
|
105
|
+
k: key,
|
|
106
|
+
v: value,
|
|
107
|
+
});
|
|
108
|
+
console.log(`📦 Set local storage "${key}" = "${value}"`);
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* Gets a Local Storage item and prints it.
|
|
112
|
+
* Pattern: When I get local storage item "token"
|
|
113
|
+
*/
|
|
114
|
+
(0, registry_1.Step)("I get local storage item {string}", async (page, key) => {
|
|
115
|
+
const value = await page.evaluate((k) => localStorage.getItem(k), key);
|
|
116
|
+
console.log(`📦 Local Storage "${key}": ${value}`);
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* Clears all Local Storage.
|
|
120
|
+
* Pattern: When I clear local storage
|
|
121
|
+
*/
|
|
122
|
+
(0, registry_1.Step)("I clear local storage", async (page) => {
|
|
123
|
+
await page.evaluate(() => localStorage.clear());
|
|
124
|
+
console.log("📦 Cleared local storage");
|
|
125
|
+
});
|
|
126
|
+
/**
|
|
127
|
+
* Sets a Session Storage item.
|
|
128
|
+
* Pattern: When I set session storage item "user" to "admin"
|
|
129
|
+
*/
|
|
130
|
+
(0, registry_1.Step)("I set session storage item {string} to {string}", async (page, key, value) => {
|
|
131
|
+
await page.evaluate(({ k, v }) => sessionStorage.setItem(k, v), {
|
|
132
|
+
k: key,
|
|
133
|
+
v: value,
|
|
134
|
+
});
|
|
135
|
+
console.log(`📦 Set session storage "${key}" = "${value}"`);
|
|
136
|
+
});
|
|
137
|
+
/**
|
|
138
|
+
* Clears all Session Storage.
|
|
139
|
+
* Pattern: When I clear session storage
|
|
140
|
+
*/
|
|
141
|
+
(0, registry_1.Step)("I clear session storage", async (page) => {
|
|
142
|
+
await page.evaluate(() => sessionStorage.clear());
|
|
143
|
+
console.log("📦 Cleared session storage");
|
|
144
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/mobile.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const registry_1 = require("../../core/registry");
|
|
4
|
+
const state_1 = require("../utils/state");
|
|
5
|
+
/**
|
|
6
|
+
* Helper: Tries to tap. If the context doesn't support touch, falls back to click.
|
|
7
|
+
*/
|
|
8
|
+
async function safeTap(target) {
|
|
9
|
+
try {
|
|
10
|
+
// Try native touch tap
|
|
11
|
+
await target.tap();
|
|
12
|
+
return "tapped";
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error.message.includes("does not support tap")) {
|
|
16
|
+
// Fallback for Desktop Contexts
|
|
17
|
+
await target.click();
|
|
18
|
+
return "clicked (fallback)";
|
|
19
|
+
}
|
|
20
|
+
throw error;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// ==================================================
|
|
24
|
+
// 1. TOUCH INTERACTIONS
|
|
25
|
+
// ==================================================
|
|
26
|
+
/**
|
|
27
|
+
* Taps on the currently stored element.
|
|
28
|
+
* Pattern: When I tap
|
|
29
|
+
*/
|
|
30
|
+
(0, registry_1.Step)("I tap", async (page) => {
|
|
31
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
32
|
+
const action = await safeTap(element);
|
|
33
|
+
console.log(`👆 ${action === "tapped" ? "Tapped" : "Clicked"} stored element`);
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Finds an element by selector and taps it.
|
|
37
|
+
* Pattern: When I tap element "#submit-btn"
|
|
38
|
+
*/
|
|
39
|
+
(0, registry_1.Step)("I tap element {string}", async (page, selector) => {
|
|
40
|
+
const element = page.locator(selector);
|
|
41
|
+
const action = await safeTap(element);
|
|
42
|
+
console.log(`👆 ${action === "tapped" ? "Tapped" : "Clicked"} element "${selector}"`);
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Taps at specific coordinates.
|
|
46
|
+
* Pattern: When I tap coordinates x:100 y:200
|
|
47
|
+
*/
|
|
48
|
+
(0, registry_1.Step)("I tap coordinates x:{int} y:{int}", async (page, x, y) => {
|
|
49
|
+
// page.mouse.click works for both desktop and mobile viewports
|
|
50
|
+
await page.mouse.click(x, y);
|
|
51
|
+
console.log(`👆 Tapped at (${x}, ${y})`);
|
|
52
|
+
});
|
|
53
|
+
// ==================================================
|
|
54
|
+
// 2. VIEWPORT & EMULATION
|
|
55
|
+
// ==================================================
|
|
56
|
+
(0, registry_1.Step)("I resize window to width {int} and height {int}", async (page, width, height) => {
|
|
57
|
+
await page.setViewportSize({ width, height });
|
|
58
|
+
console.log(`📱 Resized viewport to ${width}x${height}`);
|
|
59
|
+
});
|
|
60
|
+
(0, registry_1.Step)("I simulate device {string}", async (page, deviceName) => {
|
|
61
|
+
const devices = {
|
|
62
|
+
"iPhone 12": { width: 390, height: 844 },
|
|
63
|
+
"iPhone SE": { width: 375, height: 667 },
|
|
64
|
+
iPad: { width: 768, height: 1024 },
|
|
65
|
+
"Pixel 5": { width: 393, height: 851 },
|
|
66
|
+
"Samsung Galaxy S8": { width: 360, height: 740 },
|
|
67
|
+
Desktop: { width: 1920, height: 1080 },
|
|
68
|
+
};
|
|
69
|
+
const size = devices[deviceName];
|
|
70
|
+
if (!size) {
|
|
71
|
+
throw new Error(`❌ Unknown device preset: "${deviceName}".`);
|
|
72
|
+
}
|
|
73
|
+
await page.setViewportSize(size);
|
|
74
|
+
console.log(`📱 Simulated device "${deviceName}" (${size.width}x${size.height})`);
|
|
75
|
+
});
|
|
76
|
+
// ==================================================
|
|
77
|
+
// 3. GEOLOCATION & PERMISSIONS
|
|
78
|
+
// ==================================================
|
|
79
|
+
(0, registry_1.Step)("I set geolocation to lat: {float} long: {float}", async (page, lat, long) => {
|
|
80
|
+
await page.context().setGeolocation({ latitude: lat, longitude: long });
|
|
81
|
+
await page.context().grantPermissions(["geolocation"]);
|
|
82
|
+
console.log(`🌍 Geolocation set to ${lat}, ${long}`);
|
|
83
|
+
});
|
|
84
|
+
(0, registry_1.Step)("I grant permission {string}", async (page, permission) => {
|
|
85
|
+
await page.context().grantPermissions([permission]);
|
|
86
|
+
console.log(`🛡️ Granted permission: "${permission}"`);
|
|
87
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mouse.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/mouse.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const registry_1 = require("../../core/registry");
|
|
4
|
+
const state_1 = require("../utils/state");
|
|
5
|
+
// ===================================================================================
|
|
6
|
+
// MOUSE ACTIONS: SCROLLING
|
|
7
|
+
// ===================================================================================
|
|
8
|
+
/**
|
|
9
|
+
* Scrolls the element matching the selector into view.
|
|
10
|
+
* Pattern: When I scroll ".footer" into view
|
|
11
|
+
*/
|
|
12
|
+
(0, registry_1.Step)("I scroll {string} into view", async (page, selector) => {
|
|
13
|
+
const locator = page.locator(selector);
|
|
14
|
+
await locator.scrollIntoViewIfNeeded();
|
|
15
|
+
console.log(`🖱️ Scrolled element "${selector}" into view.`);
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Scrolls a specific element to internal X, Y coordinates.
|
|
19
|
+
* Pattern: When I scroll "#box" to position x:100 y:200
|
|
20
|
+
*/
|
|
21
|
+
(0, registry_1.Step)("I scroll {string} to position x:{int} y:{int}", async (page, selector, x, y) => {
|
|
22
|
+
const locator = page.locator(selector);
|
|
23
|
+
await locator.evaluate((el, coords) => {
|
|
24
|
+
el.scrollTo(coords.x, coords.y);
|
|
25
|
+
}, { x, y });
|
|
26
|
+
console.log(`🖱️ Scrolled element "${selector}" to position x:${x} y:${y}.`);
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Scrolls the entire window to specific coordinates.
|
|
30
|
+
* Pattern: When I scroll to coordinates x:0 y:500
|
|
31
|
+
*/
|
|
32
|
+
(0, registry_1.Step)("I scroll to coordinates x:{int} y:{int}", async (page, x, y) => {
|
|
33
|
+
await page.evaluate((coords) => {
|
|
34
|
+
window.scrollTo(coords.x, coords.y);
|
|
35
|
+
}, { x, y });
|
|
36
|
+
console.log(`🖱️ Scrolled window to coordinates x:${x} y:${y}.`);
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Scrolls the window smoothly to specific coordinates.
|
|
40
|
+
* Pattern: When I scroll mouse window to position top:0 left:100
|
|
41
|
+
*/
|
|
42
|
+
(0, registry_1.Step)("I scroll mouse window to position top:{int} left:{int}", async (page, top, left) => {
|
|
43
|
+
await page.evaluate((coords) => {
|
|
44
|
+
window.scrollTo({
|
|
45
|
+
top: coords.top,
|
|
46
|
+
left: coords.left,
|
|
47
|
+
behavior: "smooth",
|
|
48
|
+
});
|
|
49
|
+
}, { top, left });
|
|
50
|
+
console.log(`🖱️ Scrolled window to position top:${top} left:${left} (smooth).`);
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* Scrolls to a general direction (top, bottom, left, right).
|
|
54
|
+
* Pattern: When I scroll to "bottom"
|
|
55
|
+
*/
|
|
56
|
+
(0, registry_1.Step)("I scroll to {string}", async (page, direction) => {
|
|
57
|
+
const validDirections = ["top", "bottom", "left", "right"];
|
|
58
|
+
const dir = direction.toLowerCase();
|
|
59
|
+
if (!validDirections.includes(dir)) {
|
|
60
|
+
throw new Error(`Invalid scroll direction "${direction}". Must be one of: ${validDirections.join(", ")}.`);
|
|
61
|
+
}
|
|
62
|
+
await page.evaluate((d) => {
|
|
63
|
+
const scrollOptions = { behavior: "smooth" };
|
|
64
|
+
switch (d) {
|
|
65
|
+
case "top":
|
|
66
|
+
scrollOptions.top = 0;
|
|
67
|
+
break;
|
|
68
|
+
case "bottom":
|
|
69
|
+
scrollOptions.top = document.body.scrollHeight;
|
|
70
|
+
break;
|
|
71
|
+
case "left":
|
|
72
|
+
scrollOptions.left = 0;
|
|
73
|
+
break;
|
|
74
|
+
case "right":
|
|
75
|
+
scrollOptions.left = document.body.scrollWidth;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
window.scrollTo(scrollOptions);
|
|
79
|
+
}, dir);
|
|
80
|
+
console.log(`🖱️ Scrolled to "${dir}".`);
|
|
81
|
+
// Wait a moment for smooth scroll to finish
|
|
82
|
+
await page.waitForTimeout(500);
|
|
83
|
+
});
|
|
84
|
+
// ===================================================================================
|
|
85
|
+
// MOUSE ACTIONS: HOVERING / MOVEMENT
|
|
86
|
+
// ===================================================================================
|
|
87
|
+
/**
|
|
88
|
+
* Hovers over an element and sets it as the active element.
|
|
89
|
+
* Pattern: When I hover over the element ".menu-item"
|
|
90
|
+
*/
|
|
91
|
+
(0, registry_1.Step)("I hover over the element {string}", async (page, selector) => {
|
|
92
|
+
const element = page.locator(selector);
|
|
93
|
+
await element.hover();
|
|
94
|
+
// 🔥 Store the hovered element so we can click/assert on it immediately after
|
|
95
|
+
(0, state_1.setActiveElement)(page, element);
|
|
96
|
+
console.log(`🖱️ Hovered over: "${selector}".`);
|
|
97
|
+
});
|
|
98
|
+
/**
|
|
99
|
+
* Moves mouse to absolute coordinates.
|
|
100
|
+
* Pattern: When I move mouse to coordinates 100, 200
|
|
101
|
+
*/
|
|
102
|
+
(0, registry_1.Step)("I move mouse to coordinates {int}, {int}", async (page, x, y) => {
|
|
103
|
+
await page.mouse.move(x, y);
|
|
104
|
+
console.log(`🧭 Mouse moved to (${x}, ${y}).`);
|
|
105
|
+
});
|
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const registry_1 = require("../../core/registry");
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Visits a specific URL.
|
|
6
|
+
* Pattern: Given I visit "https://google.com"
|
|
7
|
+
*/
|
|
5
8
|
(0, registry_1.Step)("I visit {string}", async (page, url) => {
|
|
6
9
|
await page.goto(url);
|
|
10
|
+
console.log(`🌍 Visiting: ${url}`);
|
|
7
11
|
});
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// Reload page
|
|
12
|
+
/**
|
|
13
|
+
* Reloads the current page.
|
|
14
|
+
* Pattern: When I reload the page
|
|
15
|
+
*/
|
|
13
16
|
(0, registry_1.Step)("I reload the page", async (page) => {
|
|
14
17
|
await page.reload();
|
|
18
|
+
console.log("🔄 Page reloaded");
|
|
15
19
|
});
|
|
16
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Navigates back in browser history.
|
|
22
|
+
* Pattern: When I go back
|
|
23
|
+
*/
|
|
17
24
|
(0, registry_1.Step)("I go back", async (page) => {
|
|
18
25
|
await page.goBack();
|
|
26
|
+
console.log("⬅️ Went back");
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Navigates forward in browser history.
|
|
30
|
+
* Pattern: When I go forward
|
|
31
|
+
*/
|
|
32
|
+
(0, registry_1.Step)("I go forward", async (page) => {
|
|
33
|
+
await page.goForward();
|
|
34
|
+
console.log("➡️ Went forward");
|
|
35
|
+
});
|
|
36
|
+
/**
|
|
37
|
+
* Navigates to a specific path (relative to base URL if set).
|
|
38
|
+
* Pattern: When I navigate to "/login"
|
|
39
|
+
*/
|
|
40
|
+
(0, registry_1.Step)("I navigate to {string}", async (page, path) => {
|
|
41
|
+
await page.goto(path);
|
|
42
|
+
console.log(`🌍 Navigated to path: ${path}`);
|
|
19
43
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"waits.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/waits.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const registry_1 = require("../../core/registry");
|
|
4
|
+
const state_1 = require("../utils/state");
|
|
5
|
+
/**
|
|
6
|
+
* Waits for the network to be idle (no active connections for 500ms).
|
|
7
|
+
* Useful after clicking a button that triggers complex API calls.
|
|
8
|
+
* Pattern: When I wait for network idle
|
|
9
|
+
*/
|
|
10
|
+
(0, registry_1.Step)("I wait for network idle", async (page) => {
|
|
11
|
+
await page.waitForLoadState("networkidle");
|
|
12
|
+
console.log("⏳ Network is idle");
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Waits for the page to reach a specific load state.
|
|
16
|
+
* Options: "load", "domcontentloaded", "networkidle".
|
|
17
|
+
* Pattern: When I wait for load state "domcontentloaded"
|
|
18
|
+
*/
|
|
19
|
+
(0, registry_1.Step)("I wait for load state {string}", async (page, state) => {
|
|
20
|
+
if (!["load", "domcontentloaded", "networkidle"].includes(state)) {
|
|
21
|
+
throw new Error(`❌ Invalid load state: "${state}". Use load, domcontentloaded, or networkidle.`);
|
|
22
|
+
}
|
|
23
|
+
await page.waitForLoadState(state);
|
|
24
|
+
console.log(`⏳ Reached load state: "${state}"`);
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Explicitly waits for the stored element to be visible.
|
|
28
|
+
* Pattern: When I wait for element to be visible
|
|
29
|
+
*/
|
|
30
|
+
(0, registry_1.Step)("I wait for element to be visible", async (page) => {
|
|
31
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
32
|
+
await element.waitFor({ state: "visible" });
|
|
33
|
+
console.log("⏳ Element is now visible");
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Explicitly waits for the stored element to be hidden/detached.
|
|
37
|
+
* Pattern: When I wait for element to be hidden
|
|
38
|
+
*/
|
|
39
|
+
(0, registry_1.Step)("I wait for element to be hidden", async (page) => {
|
|
40
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
41
|
+
await element.waitFor({ state: "hidden" });
|
|
42
|
+
console.log("⏳ Element is now hidden");
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* Waits for a specific URL pattern.
|
|
46
|
+
* Pattern: When I wait for URL to contain "dashboard"
|
|
47
|
+
*/
|
|
48
|
+
(0, registry_1.Step)("I wait for URL to contain {string}", async (page, urlPart) => {
|
|
49
|
+
await page.waitForURL(new RegExp(urlPart));
|
|
50
|
+
console.log(`⏳ URL now contains: "${urlPart}"`);
|
|
51
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/backend/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AACpB,OAAO,cAAc,CAAC;AACtB,OAAO,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/backend/api/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,CAAC;AACpB,OAAO,cAAc,CAAC;AACtB,OAAO,QAAQ,CAAC;AAChB,OAAO,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../../src/backend/api/network.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const registry_1 = require("../../core/registry");
|
|
4
|
+
const state_1 = require("../utils/state");
|
|
5
|
+
// Helper: Safely parse JSON even if it comes with DocString quotes or is undefined
|
|
6
|
+
function safeJsonParse(input, context) {
|
|
7
|
+
if (!input) {
|
|
8
|
+
throw new Error(`❌ Missing JSON input for ${context}. Did you forget the DocString?`);
|
|
9
|
+
}
|
|
10
|
+
let text = typeof input === "string" ? input : JSON.stringify(input);
|
|
11
|
+
// Clean up DocString artifacts if the runner passes them
|
|
12
|
+
// 1. Remove surrounding triple quotes """ if present
|
|
13
|
+
text = text.replace(/^"""/g, "").replace(/"""$/g, "");
|
|
14
|
+
// 2. Trim whitespace/newlines
|
|
15
|
+
text = text.trim();
|
|
16
|
+
// Debug Log: Show exactly what we are trying to parse
|
|
17
|
+
// console.log(`🔍 [Debug] Parsing JSON for ${context}:`, text);
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(text);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
// If simple parse fails, it might be due to unquoted keys or single quotes
|
|
23
|
+
// You could add a 'loose JSON' parser here if needed, but for now, strict JSON is best.
|
|
24
|
+
throw new Error(`❌ Invalid JSON for ${context}.\nReceived: ${text.slice(0, 50)}...\nError: ${e.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// =============================
|
|
28
|
+
// 1. INTERCEPTION / MOCKING
|
|
29
|
+
// =============================
|
|
30
|
+
/**
|
|
31
|
+
* Intercepts a URL and returns a JSON response (Mocking).
|
|
32
|
+
* Uses Regex to support optional trailing colon.
|
|
33
|
+
*/
|
|
34
|
+
(0, registry_1.Step)(/^I intercept URL "([^"]+)" and stub body:?$/, async (page, url, body) => {
|
|
35
|
+
const parsedBody = safeJsonParse(body, `stubbing "${url}"`);
|
|
36
|
+
await page.route(url, (route) => {
|
|
37
|
+
route.fulfill({
|
|
38
|
+
status: 200,
|
|
39
|
+
contentType: "application/json",
|
|
40
|
+
body: JSON.stringify(parsedBody),
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
console.log(`📡 Stubbed "${url}" with JSON response.`);
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* Intercepts a URL and stubs it with a raw string (non-JSON).
|
|
47
|
+
*/
|
|
48
|
+
(0, registry_1.Step)("I intercept URL {string} and stub body {string}", async (page, url, body) => {
|
|
49
|
+
await page.route(url, (route) => {
|
|
50
|
+
route.fulfill({
|
|
51
|
+
status: 200,
|
|
52
|
+
contentType: "text/plain",
|
|
53
|
+
body: body,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
console.log(`📡 Stubbed "${url}" with raw text: "${body}"`);
|
|
57
|
+
});
|
|
58
|
+
/**
|
|
59
|
+
* Intercepts a URL but allows it to continue (Spying).
|
|
60
|
+
*/
|
|
61
|
+
(0, registry_1.Step)("I intercept URL {string}", async (page, url) => {
|
|
62
|
+
await page.route(url, async (route) => {
|
|
63
|
+
await route.continue();
|
|
64
|
+
});
|
|
65
|
+
console.log(`📡 Spying on URL "${url}" (allowed to continue).`);
|
|
66
|
+
});
|
|
67
|
+
// =============================
|
|
68
|
+
// 2. MAKING API REQUESTS
|
|
69
|
+
// =============================
|
|
70
|
+
/**
|
|
71
|
+
* Makes a GET request and stores the response.
|
|
72
|
+
*/
|
|
73
|
+
(0, registry_1.Step)("I make request to {string}", async (page, url) => {
|
|
74
|
+
console.log(`⚡ GET request to: ${url}`);
|
|
75
|
+
const response = await page.request.get(url);
|
|
76
|
+
const status = response.status();
|
|
77
|
+
const body = await response.text();
|
|
78
|
+
let jsonBody;
|
|
79
|
+
try {
|
|
80
|
+
jsonBody = JSON.parse(body);
|
|
81
|
+
}
|
|
82
|
+
catch { }
|
|
83
|
+
(0, state_1.setVariable)(page, "lastResponse", { status, body, json: jsonBody });
|
|
84
|
+
(0, state_1.setVariable)(page, "lastStatusCode", status);
|
|
85
|
+
console.log(`✅ Status: ${status}`);
|
|
86
|
+
});
|
|
87
|
+
/**
|
|
88
|
+
* Makes a POST request with a JSON body.
|
|
89
|
+
* Uses Regex to support optional trailing colon.
|
|
90
|
+
*/
|
|
91
|
+
(0, registry_1.Step)(/^I make a POST request to "([^"]+)" with JSON body:?$/, async (page, url, docString) => {
|
|
92
|
+
const payload = safeJsonParse(docString, `POST to "${url}"`);
|
|
93
|
+
console.log(`⚡ POST request to: ${url}`);
|
|
94
|
+
const response = await page.request.post(url, { data: payload });
|
|
95
|
+
const status = response.status();
|
|
96
|
+
const body = await response.text();
|
|
97
|
+
let jsonBody;
|
|
98
|
+
try {
|
|
99
|
+
jsonBody = JSON.parse(body);
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
(0, state_1.setVariable)(page, "lastResponse", { status, body, json: jsonBody });
|
|
103
|
+
(0, state_1.setVariable)(page, "lastStatusCode", status);
|
|
104
|
+
console.log(`✅ Status: ${status}`);
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* Makes a generic browser-context HTTP request (fetch).
|
|
108
|
+
*/
|
|
109
|
+
(0, registry_1.Step)('I make a "{word}" request to {string}', async (page, method, url, table) => {
|
|
110
|
+
const options = { method: method.toUpperCase() };
|
|
111
|
+
const rows = table && typeof table.rows === "function" ? table.rows() : table || [];
|
|
112
|
+
if (Array.isArray(rows)) {
|
|
113
|
+
rows.forEach((row) => {
|
|
114
|
+
const key = Array.isArray(row) ? row[0] : row.header || row.key;
|
|
115
|
+
const val = Array.isArray(row) ? row[1] : row.value;
|
|
116
|
+
if (!key)
|
|
117
|
+
return;
|
|
118
|
+
if (key.toLowerCase() === "body") {
|
|
119
|
+
options.body = val;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
if (!options.headers)
|
|
123
|
+
options.headers = {};
|
|
124
|
+
options.headers[key] = val;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
console.log(`⚡ Browser Fetch: ${method} ${url}`);
|
|
129
|
+
const res = await page.evaluate(async ({ url, options }) => {
|
|
130
|
+
const response = await fetch(url, options);
|
|
131
|
+
return {
|
|
132
|
+
status: response.status,
|
|
133
|
+
body: await response.text(),
|
|
134
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
135
|
+
};
|
|
136
|
+
}, { url, options });
|
|
137
|
+
let jsonBody;
|
|
138
|
+
try {
|
|
139
|
+
jsonBody = JSON.parse(res.body);
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
(0, state_1.setVariable)(page, "lastResponse", { ...res, json: jsonBody });
|
|
143
|
+
(0, state_1.setVariable)(page, "lastStatusCode", res.status);
|
|
144
|
+
console.log(`✅ Status: ${res.status}`);
|
|
145
|
+
});
|