playwright-cucumber-ts-steps 1.1.7 → 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/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,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
|
+
});
|
|
@@ -2,24 +2,35 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const test_1 = require("@playwright/test");
|
|
4
4
|
const registry_1 = require("../../core/registry");
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Checks if the URL contains a specific string.
|
|
7
|
+
* Pattern: Then I expect the url to contain "dashboard"
|
|
8
|
+
*/
|
|
9
|
+
(0, registry_1.Step)("I expect the url to contain {string}", async (page, part) => {
|
|
10
|
+
await (0, test_1.expect)(page).toHaveURL(new RegExp(part));
|
|
11
|
+
console.log(`✅ URL contains "${part}"`);
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* Checks if the URL is an exact match.
|
|
15
|
+
* Pattern: Then I expect the url to be "https://google.com"
|
|
16
|
+
*/
|
|
6
17
|
(0, registry_1.Step)("I expect the url to be {string}", async (page, url) => {
|
|
7
18
|
await (0, test_1.expect)(page).toHaveURL(url);
|
|
19
|
+
console.log(`✅ URL is "${url}"`);
|
|
8
20
|
});
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Checks if the page title contains a string.
|
|
23
|
+
* Pattern: Then I expect the title to contain "Welcome"
|
|
24
|
+
*/
|
|
25
|
+
(0, registry_1.Step)("I expect the title to contain {string}", async (page, part) => {
|
|
26
|
+
await (0, test_1.expect)(page).toHaveTitle(new RegExp(part));
|
|
27
|
+
console.log(`✅ Title contains "${part}"`);
|
|
13
28
|
});
|
|
14
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Checks if the page title is an exact match.
|
|
31
|
+
* Pattern: Then I expect the title to be "Welcome Page"
|
|
32
|
+
*/
|
|
15
33
|
(0, registry_1.Step)("I expect the title to be {string}", async (page, title) => {
|
|
16
34
|
await (0, test_1.expect)(page).toHaveTitle(title);
|
|
17
|
-
});
|
|
18
|
-
// Partial Title Check
|
|
19
|
-
(0, registry_1.Step)("I expect the title to contain {string}", async (page, titlePart) => {
|
|
20
|
-
await (0, test_1.expect)(page).toHaveTitle(new RegExp(titlePart));
|
|
21
|
-
});
|
|
22
|
-
// Screenshot Match (Visual Regression)
|
|
23
|
-
(0, registry_1.Step)("I expect the page screenshot to match {string}", async (page, filename) => {
|
|
24
|
-
await (0, test_1.expect)(page).toHaveScreenshot(filename);
|
|
35
|
+
console.log(`✅ Title is "${title}"`);
|
|
25
36
|
});
|
|
@@ -2,19 +2,123 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const test_1 = require("@playwright/test");
|
|
4
4
|
const registry_1 = require("../../core/registry");
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const state_1 = require("../utils/state");
|
|
6
|
+
// ===============================
|
|
7
|
+
// 1. VISIBILITY & STATE CHECKS
|
|
8
|
+
// ===============================
|
|
9
|
+
/**
|
|
10
|
+
* Checks if the stored element is visible.
|
|
11
|
+
* Pattern: Then I expect element to be visible
|
|
12
|
+
*/
|
|
13
|
+
(0, registry_1.Step)("I expect element to be visible", async (page) => {
|
|
14
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
15
|
+
await (0, test_1.expect)(element).toBeVisible();
|
|
16
|
+
console.log("✅ Element is visible");
|
|
8
17
|
});
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Checks if the stored element is hidden.
|
|
20
|
+
* Pattern: Then I expect element to be hidden
|
|
21
|
+
*/
|
|
22
|
+
(0, registry_1.Step)("I expect element to be hidden", async (page) => {
|
|
23
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
24
|
+
await (0, test_1.expect)(element).toBeHidden();
|
|
25
|
+
console.log("✅ Element is hidden");
|
|
12
26
|
});
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Checks if the stored element is enabled.
|
|
29
|
+
* Pattern: Then I expect element to be enabled
|
|
30
|
+
*/
|
|
31
|
+
(0, registry_1.Step)("I expect element to be enabled", async (page) => {
|
|
32
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
33
|
+
await (0, test_1.expect)(element).toBeEnabled();
|
|
34
|
+
console.log("✅ Element is enabled");
|
|
16
35
|
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Checks if the stored element is disabled.
|
|
38
|
+
* Pattern: Then I expect element to be disabled
|
|
39
|
+
*/
|
|
40
|
+
(0, registry_1.Step)("I expect element to be disabled", async (page) => {
|
|
41
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
42
|
+
await (0, test_1.expect)(element).toBeDisabled();
|
|
43
|
+
console.log("✅ Element is disabled");
|
|
44
|
+
});
|
|
45
|
+
// ===============================
|
|
46
|
+
// 2. TEXT & VALUE CHECKS
|
|
47
|
+
// ===============================
|
|
48
|
+
/**
|
|
49
|
+
* Checks if the element contains exact text.
|
|
50
|
+
* Pattern: Then I expect element to have text "Submit"
|
|
51
|
+
*/
|
|
52
|
+
(0, registry_1.Step)("I expect element to have text {string}", async (page, text) => {
|
|
53
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
54
|
+
await (0, test_1.expect)(element).toHaveText(text);
|
|
55
|
+
console.log(`✅ Element has text "${text}"`);
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Checks if the element contains partial text.
|
|
59
|
+
* Pattern: Then I expect element to contain text "Sub"
|
|
60
|
+
*/
|
|
61
|
+
(0, registry_1.Step)("I expect element to contain text {string}", async (page, text) => {
|
|
62
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
63
|
+
await (0, test_1.expect)(element).toContainText(text);
|
|
64
|
+
console.log(`✅ Element contains text "${text}"`);
|
|
65
|
+
});
|
|
66
|
+
/**
|
|
67
|
+
* Checks if the element has a specific input value.
|
|
68
|
+
* Pattern: Then I expect element to have value "123"
|
|
69
|
+
*/
|
|
70
|
+
(0, registry_1.Step)("I expect element to have value {string}", async (page, value) => {
|
|
71
|
+
// Support aliases (e.g. @myVar)
|
|
72
|
+
if (value.startsWith("@")) {
|
|
73
|
+
const alias = value.slice(1);
|
|
74
|
+
const stored = (0, state_1.getVariable)(page, alias);
|
|
75
|
+
if (!stored)
|
|
76
|
+
throw new Error(`Alias @${alias} not found`);
|
|
77
|
+
value = stored;
|
|
78
|
+
}
|
|
79
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
80
|
+
await (0, test_1.expect)(element).toHaveValue(value);
|
|
81
|
+
console.log(`✅ Element has value "${value}"`);
|
|
82
|
+
});
|
|
83
|
+
// ===============================
|
|
84
|
+
// 3. ATTRIBUTE CHECKS
|
|
85
|
+
// ===============================
|
|
86
|
+
/**
|
|
87
|
+
* Checks if the element has a specific attribute.
|
|
88
|
+
* Pattern: Then I expect element to have attribute "data-test"
|
|
89
|
+
*/
|
|
90
|
+
(0, registry_1.Step)("I expect element to have attribute {string}", async (page, attr) => {
|
|
91
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
92
|
+
await (0, test_1.expect)(element).toHaveAttribute(attr);
|
|
93
|
+
console.log(`✅ Element has attribute "${attr}"`);
|
|
94
|
+
});
|
|
95
|
+
/**
|
|
96
|
+
* Checks if the element has a specific attribute value.
|
|
97
|
+
* Pattern: Then I expect element to have attribute "type" with value "submit"
|
|
98
|
+
*/
|
|
99
|
+
(0, registry_1.Step)("I expect element to have attribute {string} with value {string}", async (page, attr, value) => {
|
|
100
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
101
|
+
await (0, test_1.expect)(element).toHaveAttribute(attr, value);
|
|
102
|
+
console.log(`✅ Element has attribute "${attr}" = "${value}"`);
|
|
103
|
+
});
|
|
104
|
+
// ===============================
|
|
105
|
+
// 4. VISUAL REGRESSION (Screenshots)
|
|
106
|
+
// ===============================
|
|
107
|
+
/**
|
|
108
|
+
* Verifies the ENTIRE page matches a stored screenshot.
|
|
109
|
+
* Pattern: Then I expect the page screenshot to match "home-page.png"
|
|
110
|
+
* Note: Screenshots are stored in 'tests/{feature-name}/' folder by default.
|
|
111
|
+
*/
|
|
112
|
+
(0, registry_1.Step)("I expect the page screenshot to match {string}", async (page, filename) => {
|
|
113
|
+
await (0, test_1.expect)(page).toHaveScreenshot(filename);
|
|
114
|
+
console.log(`📸 Page matches screenshot: ${filename}`);
|
|
115
|
+
});
|
|
116
|
+
/**
|
|
117
|
+
* Verifies the CURRENT ELEMENT matches a stored screenshot.
|
|
118
|
+
* Pattern: Then I expect the element screenshot to match "submit-btn.png"
|
|
119
|
+
*/
|
|
120
|
+
(0, registry_1.Step)("I expect the element screenshot to match {string}", async (page, filename) => {
|
|
121
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
122
|
+
await (0, test_1.expect)(element).toHaveScreenshot(filename);
|
|
123
|
+
console.log(`📸 Element matches screenshot: ${filename}`);
|
|
20
124
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Page, Locator } from "@playwright/test";
|
|
2
|
+
export declare function setActiveElement(page: Page, element: Locator): void;
|
|
3
|
+
export declare function getActiveElement(page: Page): Locator;
|
|
4
|
+
export declare function setActiveElements(page: Page, elements: Locator): void;
|
|
5
|
+
export declare function getActiveElements(page: Page): Locator;
|
|
6
|
+
export declare function setVariable(page: Page, key: string, value: any): void;
|
|
7
|
+
export declare function getVariable(page: Page, key: string): any;
|
|
8
|
+
export declare function parseClickOptions(table: any): {
|
|
9
|
+
force?: boolean;
|
|
10
|
+
button?: "left" | "right" | "middle";
|
|
11
|
+
modifiers?: Array<"Alt" | "Control" | "Meta" | "Shift">;
|
|
12
|
+
position?: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
};
|
|
16
|
+
timeout?: number;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/backend/utils/state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAKjD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,QAE5D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAQpD;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,QAE9D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAMrD;AAID,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,QAK9D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,GAAG,CAGxD;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,GAAG;IAC7C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACrC,SAAS,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAwCA"}
|