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.
Files changed (52) hide show
  1. package/dist/backend/actions/click.d.ts +2 -0
  2. package/dist/backend/actions/click.d.ts.map +1 -0
  3. package/dist/backend/actions/click.js +179 -0
  4. package/dist/backend/actions/find.d.ts +2 -0
  5. package/dist/backend/actions/find.d.ts.map +1 -0
  6. package/dist/backend/actions/find.js +291 -0
  7. package/dist/backend/actions/form.d.ts +2 -0
  8. package/dist/backend/actions/form.d.ts.map +1 -0
  9. package/dist/backend/actions/form.js +185 -0
  10. package/dist/backend/actions/formTable.js +1 -1
  11. package/dist/backend/actions/frames.d.ts +2 -0
  12. package/dist/backend/actions/frames.d.ts.map +1 -0
  13. package/dist/backend/actions/frames.js +60 -0
  14. package/dist/backend/actions/index.d.ts +10 -0
  15. package/dist/backend/actions/index.d.ts.map +1 -1
  16. package/dist/backend/actions/index.js +10 -0
  17. package/dist/backend/actions/inputs.d.ts +2 -0
  18. package/dist/backend/actions/inputs.d.ts.map +1 -0
  19. package/dist/backend/actions/inputs.js +177 -0
  20. package/dist/backend/actions/keyboard.d.ts +2 -0
  21. package/dist/backend/actions/keyboard.d.ts.map +1 -0
  22. package/dist/backend/actions/keyboard.js +62 -0
  23. package/dist/backend/actions/misc.d.ts +2 -0
  24. package/dist/backend/actions/misc.d.ts.map +1 -0
  25. package/dist/backend/actions/misc.js +144 -0
  26. package/dist/backend/actions/mobile.d.ts +2 -0
  27. package/dist/backend/actions/mobile.d.ts.map +1 -0
  28. package/dist/backend/actions/mobile.js +87 -0
  29. package/dist/backend/actions/mouse.d.ts +2 -0
  30. package/dist/backend/actions/mouse.d.ts.map +1 -0
  31. package/dist/backend/actions/mouse.js +105 -0
  32. package/dist/backend/actions/navigation.js +31 -7
  33. package/dist/backend/actions/waits.d.ts +2 -0
  34. package/dist/backend/actions/waits.d.ts.map +1 -0
  35. package/dist/backend/actions/waits.js +51 -0
  36. package/dist/backend/api/index.d.ts +1 -0
  37. package/dist/backend/api/index.d.ts.map +1 -1
  38. package/dist/backend/api/index.js +1 -0
  39. package/dist/backend/api/network.d.ts +2 -0
  40. package/dist/backend/api/network.d.ts.map +1 -0
  41. package/dist/backend/api/network.js +145 -0
  42. package/dist/backend/assertions/pageState.js +25 -14
  43. package/dist/backend/assertions/visibility.js +116 -12
  44. package/dist/backend/utils/state.d.ts +18 -0
  45. package/dist/backend/utils/state.d.ts.map +1 -0
  46. package/dist/backend/utils/state.js +84 -0
  47. package/dist/core/registry.d.ts +14 -14
  48. package/dist/core/registry.d.ts.map +1 -1
  49. package/dist/core/registry.js +13 -4
  50. package/dist/core/runner.d.ts.map +1 -1
  51. package/dist/core/runner.js +91 -37
  52. 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
- // Basic visit
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
- // Visit and wait for network (useful for slow apps)
9
- (0, registry_1.Step)("I visit {string} and wait for network idle", async (page, url) => {
10
- await page.goto(url, { waitUntil: "networkidle" });
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
- // Go back
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=waits.d.ts.map
@@ -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,4 +1,5 @@
1
1
  import "./requests";
2
2
  import "./assertions";
3
3
  import "./mock";
4
+ import "./network";
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -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"}
@@ -3,3 +3,4 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  require("./requests");
4
4
  require("./assertions");
5
5
  require("./mock");
6
+ require("./network");
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=network.d.ts.map
@@ -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
- // URL Check
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
- // Partial URL Check
10
- (0, registry_1.Step)("I expect the url to contain {string}", async (page, part) => {
11
- // We use a RegExp to allow partial matching safely
12
- await (0, test_1.expect)(page).toHaveURL(new RegExp(part));
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
- // Title Check
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
- // Check if element exists and is visible
6
- (0, registry_1.Step)("I expect {string} to be visible", async (page, selector) => {
7
- await (0, test_1.expect)(page.locator(selector)).toBeVisible();
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
- // Check if element is hidden (or doesn't exist)
10
- (0, registry_1.Step)("I expect {string} to be hidden", async (page, selector) => {
11
- await (0, test_1.expect)(page.locator(selector)).toBeHidden();
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
- // Check if element is enabled (clickable)
14
- (0, registry_1.Step)("I expect {string} to be enabled", async (page, selector) => {
15
- await (0, test_1.expect)(page.locator(selector)).toBeEnabled();
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
- // Check if element is disabled
18
- (0, registry_1.Step)("I expect {string} to be disabled", async (page, selector) => {
19
- await (0, test_1.expect)(page.locator(selector)).toBeDisabled();
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"}