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.
Files changed (53) hide show
  1. package/README.md +10 -15
  2. package/dist/backend/actions/click.d.ts +2 -0
  3. package/dist/backend/actions/click.d.ts.map +1 -0
  4. package/dist/backend/actions/click.js +179 -0
  5. package/dist/backend/actions/find.d.ts +2 -0
  6. package/dist/backend/actions/find.d.ts.map +1 -0
  7. package/dist/backend/actions/find.js +291 -0
  8. package/dist/backend/actions/form.d.ts +2 -0
  9. package/dist/backend/actions/form.d.ts.map +1 -0
  10. package/dist/backend/actions/form.js +185 -0
  11. package/dist/backend/actions/formTable.js +1 -1
  12. package/dist/backend/actions/frames.d.ts +2 -0
  13. package/dist/backend/actions/frames.d.ts.map +1 -0
  14. package/dist/backend/actions/frames.js +60 -0
  15. package/dist/backend/actions/index.d.ts +10 -0
  16. package/dist/backend/actions/index.d.ts.map +1 -1
  17. package/dist/backend/actions/index.js +10 -0
  18. package/dist/backend/actions/inputs.d.ts +2 -0
  19. package/dist/backend/actions/inputs.d.ts.map +1 -0
  20. package/dist/backend/actions/inputs.js +177 -0
  21. package/dist/backend/actions/keyboard.d.ts +2 -0
  22. package/dist/backend/actions/keyboard.d.ts.map +1 -0
  23. package/dist/backend/actions/keyboard.js +62 -0
  24. package/dist/backend/actions/misc.d.ts +2 -0
  25. package/dist/backend/actions/misc.d.ts.map +1 -0
  26. package/dist/backend/actions/misc.js +144 -0
  27. package/dist/backend/actions/mobile.d.ts +2 -0
  28. package/dist/backend/actions/mobile.d.ts.map +1 -0
  29. package/dist/backend/actions/mobile.js +87 -0
  30. package/dist/backend/actions/mouse.d.ts +2 -0
  31. package/dist/backend/actions/mouse.d.ts.map +1 -0
  32. package/dist/backend/actions/mouse.js +105 -0
  33. package/dist/backend/actions/navigation.js +31 -7
  34. package/dist/backend/actions/waits.d.ts +2 -0
  35. package/dist/backend/actions/waits.d.ts.map +1 -0
  36. package/dist/backend/actions/waits.js +51 -0
  37. package/dist/backend/api/index.d.ts +1 -0
  38. package/dist/backend/api/index.d.ts.map +1 -1
  39. package/dist/backend/api/index.js +1 -0
  40. package/dist/backend/api/network.d.ts +2 -0
  41. package/dist/backend/api/network.d.ts.map +1 -0
  42. package/dist/backend/api/network.js +145 -0
  43. package/dist/backend/assertions/pageState.js +25 -14
  44. package/dist/backend/assertions/visibility.js +116 -12
  45. package/dist/backend/utils/state.d.ts +18 -0
  46. package/dist/backend/utils/state.d.ts.map +1 -0
  47. package/dist/backend/utils/state.js +84 -0
  48. package/dist/core/registry.d.ts +14 -14
  49. package/dist/core/registry.d.ts.map +1 -1
  50. package/dist/core/registry.js +13 -4
  51. package/dist/core/runner.d.ts.map +1 -1
  52. package/dist/core/runner.js +91 -37
  53. package/package.json +1 -1
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const fs = __importStar(require("fs"));
37
+ const path = __importStar(require("path"));
38
+ const test_1 = require("@playwright/test");
39
+ const registry_1 = require("../../core/registry");
40
+ const state_1 = require("../utils/state");
41
+ /**
42
+ * Helper to convert raw runner table into Objects (ActionRow[])
43
+ * Handles whitespace in headers (e.g., "| Target |" -> "Target")
44
+ */
45
+ function parseDataTable(table) {
46
+ // Debug Log: See exactly what the runner is passing
47
+ // console.log("DEBUG: Raw Table Input:", JSON.stringify(table, null, 2));
48
+ // 1. If it's a Cucumber object (Legacy)
49
+ if (table && typeof table.hashes === "function") {
50
+ return table.hashes();
51
+ }
52
+ // 2. If it's a raw Array of Arrays (Custom Runner)
53
+ if (Array.isArray(table) && Array.isArray(table[0])) {
54
+ // Trim headers to ensure " Target " becomes "Target"
55
+ const headers = table[0].map((h) => h.trim());
56
+ const rows = table.slice(1);
57
+ return rows.map((row) => {
58
+ const obj = {};
59
+ headers.forEach((header, index) => {
60
+ // Map header to row value (and trim the value too for safety)
61
+ obj[header] = row[index] ? row[index].trim() : "";
62
+ });
63
+ return obj;
64
+ });
65
+ }
66
+ return [];
67
+ }
68
+ function resolveValue(page, rawValue) {
69
+ if (!rawValue)
70
+ return "";
71
+ const trimmed = rawValue.trim();
72
+ // Handle Alias (e.g. @adminPassword)
73
+ if (trimmed.startsWith("@")) {
74
+ const alias = trimmed.slice(1);
75
+ const stored = (0, state_1.getVariable)(page, alias);
76
+ if (stored === undefined) {
77
+ console.warn(`⚠️ Warning: Alias @${alias} not found. Using literal value.`);
78
+ return trimmed;
79
+ }
80
+ return typeof stored === "object" ? JSON.stringify(stored) : String(stored);
81
+ }
82
+ return trimmed;
83
+ }
84
+ (0, registry_1.Step)("I fill the following {string} form data", async (page, formName, table) => {
85
+ console.log(`📝 Processing Form: "${formName}"`);
86
+ // Parse the table
87
+ const rows = parseDataTable(table);
88
+ if (rows.length === 0) {
89
+ console.warn("⚠️ Form data table appears empty or invalid.");
90
+ return;
91
+ }
92
+ for (const row of rows) {
93
+ // Guard clause: If Target is missing/undefined, skip or throw useful error
94
+ if (!row.Target) {
95
+ console.error("❌ Invalid Row Detected (Missing Target):", JSON.stringify(row));
96
+ continue;
97
+ }
98
+ const target = row.Target; // Already trimmed in parser
99
+ const rawValue = row.Value || "";
100
+ const resolvedValue = resolveValue(page, rawValue);
101
+ // ============================================
102
+ // 1. SPECIAL ACTIONS
103
+ // ============================================
104
+ if (target.startsWith("request:")) {
105
+ const parts = target.replace("request:", "").split(":");
106
+ const method = parts[0].toUpperCase();
107
+ const url = parts.slice(1).join(":");
108
+ const payloadFile = rawValue;
109
+ const payloadDir = row.PayloadDir || "payload";
110
+ const filePath = path.resolve(process.cwd(), payloadDir, payloadFile);
111
+ if (!fs.existsSync(filePath)) {
112
+ throw new Error(`❌ Payload file not found: ${filePath}`);
113
+ }
114
+ const payload = JSON.parse(fs.readFileSync(filePath, "utf-8"));
115
+ console.log(`📞 API ${method} -> ${url}`);
116
+ const response = await page.request.fetch(url, {
117
+ method: method,
118
+ data: payload,
119
+ });
120
+ const responseBody = await response.json();
121
+ (0, state_1.setVariable)(page, "lastApiResponse", responseBody);
122
+ (0, state_1.setVariable)(page, "lastStatusCode", response.status());
123
+ console.log(`✅ Status: ${response.status()}`);
124
+ if (row.SaveAs) {
125
+ (0, state_1.setVariable)(page, row.SaveAs, responseBody);
126
+ }
127
+ continue;
128
+ }
129
+ if (target.startsWith("set:localStorage:")) {
130
+ const key = target.split(":")[2];
131
+ await page.evaluate(({ k, v }) => localStorage.setItem(k, v), {
132
+ k: key,
133
+ v: resolvedValue,
134
+ });
135
+ console.log(`📦 localStorage: Set "${key}"`);
136
+ continue;
137
+ }
138
+ if (target === "wait") {
139
+ const time = parseInt(rawValue.replace("wait:", ""), 10);
140
+ if (!isNaN(time)) {
141
+ console.log(`⏳ Waiting ${time}ms`);
142
+ await page.waitForTimeout(time);
143
+ }
144
+ continue;
145
+ }
146
+ // ============================================
147
+ // 2. UI ELEMENT ACTIONS
148
+ // ============================================
149
+ const locator = page.locator(target);
150
+ // ✅ Assertions
151
+ if (rawValue.startsWith("assert:")) {
152
+ const parts = rawValue.split(":");
153
+ const type = parts[1];
154
+ const expected = parts.slice(2).join(":");
155
+ if (type === "visible") {
156
+ await (0, test_1.expect)(locator).toBeVisible();
157
+ console.log(`🔎 Asserted visible: ${target}`);
158
+ }
159
+ else if (type === "text") {
160
+ await (0, test_1.expect)(locator).toHaveText(expected || "");
161
+ console.log(`🔎 Asserted text: ${target}`);
162
+ }
163
+ continue;
164
+ }
165
+ // ✅ Interactions
166
+ if (rawValue === "click") {
167
+ await locator.click();
168
+ console.log(`👆 Clicked: ${target}`);
169
+ continue;
170
+ }
171
+ if (rawValue === "check") {
172
+ await locator.check();
173
+ console.log(`☑️ Checked: ${target}`);
174
+ continue;
175
+ }
176
+ if (rawValue === "select") {
177
+ await locator.selectOption({ index: 0 });
178
+ console.log(`🔽 Selected index 0: ${target}`);
179
+ continue;
180
+ }
181
+ // ✅ Default: Fill
182
+ await locator.fill(resolvedValue);
183
+ console.log(`✍️ Filled ${target} with "${resolvedValue}"`);
184
+ }
185
+ });
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const registry_1 = require("../../core/registry");
4
4
  const test_1 = require("@playwright/test");
5
5
  // CHANGE: Removed the ':' at the end of the string below
6
- (0, registry_1.Step)("I fill the following {string} form data", async (page, formName, tableData) => {
6
+ (0, registry_1.Step)("I fill the following {string} test form data", async (page, formName, tableData) => {
7
7
  console.log(`📝 Processing Form: ${formName}`);
8
8
  // The runner passes the table data as the last argument
9
9
  // tableData = [ ['#username', 'tomsmith'], ['#password', '...'] ]
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=frames.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/frames.ts"],"names":[],"mappings":""}
@@ -0,0 +1,60 @@
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
+ // IFRAME HANDLING
7
+ // =============================
8
+ /**
9
+ * Switches context to a specific iframe by its selector.
10
+ * Subsequent "I find element" steps will search INSIDE this frame.
11
+ * Pattern: When I switch to frame "#payment-frame"
12
+ */
13
+ (0, registry_1.Step)("I switch to frame {string}", async (page, selector) => {
14
+ const frameElement = page.locator(selector);
15
+ const frame = frameElement.contentFrame();
16
+ // We can't actually "switch" the global 'page' object easily in this architecture,
17
+ // BUT we can store the frame as the "Active Scope" if we extended our state management.
18
+ //
19
+ // SIMPLER APPROACH: We just store the frame locator as the "Active Element"
20
+ // and update our `find` steps to look inside it if it's a frame.
21
+ // However, for strict BDD, it's often easier to just interact with the frame directly here:
22
+ if (!frame)
23
+ throw new Error(`❌ Iframe "${selector}" not found or has no content.`);
24
+ // Set the frame as the active context for future actions?
25
+ // This requires updating `src/backend/utils/state.ts` to support `scope`.
26
+ //
27
+ // For now, let's just Log it. Frame handling usually requires a dedicated `find inside frame` step.
28
+ console.log(`⚠️ Switching Frames requires a Scope manager. For now, use 'I find element ... in frame ...'`);
29
+ });
30
+ /**
31
+ * A specialized finder that looks INSIDE a frame.
32
+ * Pattern: When I find element "#card-number" in frame "#stripe-iframe"
33
+ */
34
+ (0, registry_1.Step)("I find element {string} in frame {string}", async (page, elementSelector, frameSelector) => {
35
+ const frame = page.frameLocator(frameSelector);
36
+ const element = frame.locator(elementSelector).first();
37
+ await element.waitFor();
38
+ (0, state_1.setActiveElement)(page, element);
39
+ console.log(`🔍 Found element "${elementSelector}" inside frame "${frameSelector}"`);
40
+ });
41
+ // =============================
42
+ // TAB / WINDOW HANDLING
43
+ // =============================
44
+ /**
45
+ * Waits for a new tab (popup) to open and brings it to front.
46
+ * Use this after clicking a link with target="_blank".
47
+ * Pattern: When I switch to new tab
48
+ */
49
+ (0, registry_1.Step)("I switch to new tab", async (page) => {
50
+ // This is tricky: Playwright 'page' object passed to steps is usually fixed.
51
+ // To support multi-tab, we'd need to update the Runner to allow swapping the 'page' reference.
52
+ //
53
+ // For a library like this, checking the popup existence is usually enough:
54
+ console.log("⚠️ Multi-tab support requires Runner updates. Verifying popup event only.");
55
+ const popup = await page.waitForEvent("popup");
56
+ await popup.waitForLoadState();
57
+ console.log(`📑 New tab opened: ${await popup.title()}`);
58
+ // Note: We cannot easily "swap" the 'page' variable for subsequent steps
59
+ // without a more complex Global State object.
60
+ });
@@ -1,4 +1,14 @@
1
1
  import "./navigation";
2
2
  import "./interactions";
3
3
  import "./formTable";
4
+ import "./click";
5
+ import "./find";
6
+ import "./inputs";
7
+ import "./form";
8
+ import "./mouse";
9
+ import "./misc";
10
+ import "./mobile";
11
+ import "./waits";
12
+ import "./frames";
13
+ import "./keyboard";
4
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,OAAO,gBAAgB,CAAC;AACxB,OAAO,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/index.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,CAAC;AACtB,OAAO,gBAAgB,CAAC;AACxB,OAAO,aAAa,CAAC;AACrB,OAAO,SAAS,CAAC;AACjB,OAAO,QAAQ,CAAC;AAChB,OAAO,UAAU,CAAC;AAClB,OAAO,QAAQ,CAAC;AAChB,OAAO,SAAS,CAAC;AACjB,OAAO,QAAQ,CAAC;AAChB,OAAO,UAAU,CAAC;AAClB,OAAO,SAAS,CAAC;AACjB,OAAO,UAAU,CAAC;AAClB,OAAO,YAAY,CAAC"}
@@ -3,3 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  require("./navigation");
4
4
  require("./interactions");
5
5
  require("./formTable");
6
+ require("./click");
7
+ require("./find");
8
+ require("./inputs");
9
+ require("./form");
10
+ require("./mouse");
11
+ require("./misc");
12
+ require("./mobile");
13
+ require("./waits");
14
+ require("./frames");
15
+ require("./keyboard");
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=inputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inputs.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/inputs.ts"],"names":[],"mappings":""}
@@ -0,0 +1,177 @@
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. TYPING & FILLING
7
+ // =============================
8
+ /**
9
+ * Types text into the currently stored element.
10
+ * Supports aliases (e.g. "@password")
11
+ * Pattern: When I type "hello"
12
+ */
13
+ (0, registry_1.Step)("I type {string}", async (page, textOrAlias, table) => {
14
+ const element = (0, state_1.getActiveElement)(page);
15
+ const options = (0, state_1.parseClickOptions)(table); // Reusing generic options parser
16
+ let text = textOrAlias;
17
+ // Handle Alias
18
+ if (textOrAlias.startsWith("@")) {
19
+ const alias = textOrAlias.slice(1);
20
+ const val = (0, state_1.getVariable)(page, alias);
21
+ if (!val)
22
+ throw new Error(`❌ Alias @${alias} not found.`);
23
+ text = val;
24
+ }
25
+ await element.fill(text, options);
26
+ console.log(`⌨️ Filled element with: "${text}"`);
27
+ });
28
+ /**
29
+ * Types the value stored in a variable (Explicit version).
30
+ * Pattern: When I type stored "userEmail"
31
+ */
32
+ (0, registry_1.Step)("I type stored {string}", async (page, alias, table) => {
33
+ const element = (0, state_1.getActiveElement)(page);
34
+ const options = (0, state_1.parseClickOptions)(table);
35
+ const val = (0, state_1.getVariable)(page, alias);
36
+ if (!val)
37
+ throw new Error(`❌ Alias "${alias}" not found.`);
38
+ await element.fill(val, options);
39
+ console.log(`⌨️ Typed stored value from "${alias}"`);
40
+ });
41
+ /**
42
+ * Slowly types text (mimics real keystrokes).
43
+ * Pattern: When I slowly type "hello"
44
+ */
45
+ (0, registry_1.Step)("I slowly type {string}", async (page, text) => {
46
+ const element = (0, state_1.getActiveElement)(page);
47
+ await element.pressSequentially(text, { delay: 100 });
48
+ console.log(`⌨️ Slowly typed: "${text}"`);
49
+ });
50
+ /**
51
+ * Sets the value directly (Alias for Type, but semantically "Paste").
52
+ * Pattern: When I set value "hello"
53
+ */
54
+ (0, registry_1.Step)("I set value {string}", async (page, value, table) => {
55
+ const element = (0, state_1.getActiveElement)(page);
56
+ const options = (0, state_1.parseClickOptions)(table);
57
+ await element.fill(value, options);
58
+ console.log(`📝 Set value to: "${value}"`);
59
+ });
60
+ /**
61
+ * Clears the input field.
62
+ * Pattern: When I clear input
63
+ */
64
+ (0, registry_1.Step)("I clear", async (page) => {
65
+ const element = (0, state_1.getActiveElement)(page);
66
+ await element.fill("");
67
+ console.log("🧹 Cleared input");
68
+ });
69
+ /**
70
+ * Press a keyboard key.
71
+ * Pattern: When I press "Enter"
72
+ */
73
+ (0, registry_1.Step)("I press {string}", async (page, key) => {
74
+ const element = (0, state_1.getActiveElement)(page);
75
+ await element.press(key);
76
+ console.log(`🎹 Pressed key: "${key}"`);
77
+ });
78
+ // =============================
79
+ // 2. CHECKBOXES & RADIOS
80
+ // =============================
81
+ /**
82
+ * Checks the stored checkbox or radio button.
83
+ * Pattern: When I check
84
+ */
85
+ (0, registry_1.Step)("I check", async (page, table) => {
86
+ const element = (0, state_1.getActiveElement)(page);
87
+ const options = (0, state_1.parseClickOptions)(table);
88
+ await element.check(options);
89
+ console.log("✅ Checked element");
90
+ });
91
+ /**
92
+ * Unchecks the stored checkbox.
93
+ * Pattern: When I uncheck
94
+ */
95
+ (0, registry_1.Step)("I uncheck", async (page, table) => {
96
+ const element = (0, state_1.getActiveElement)(page);
97
+ const options = (0, state_1.parseClickOptions)(table);
98
+ await element.uncheck(options);
99
+ console.log("⬜ Unchecked element");
100
+ });
101
+ /**
102
+ * Legacy Alias: When I check input
103
+ */
104
+ (0, registry_1.Step)("I check input", async (page, table) => {
105
+ const element = (0, state_1.getActiveElement)(page);
106
+ const options = (0, state_1.parseClickOptions)(table);
107
+ await element.check(options);
108
+ console.log("✅ Checked input");
109
+ });
110
+ /**
111
+ * Legacy Alias: When I uncheck input
112
+ */
113
+ (0, registry_1.Step)("I uncheck input", async (page, table) => {
114
+ const element = (0, state_1.getActiveElement)(page);
115
+ const options = (0, state_1.parseClickOptions)(table);
116
+ await element.uncheck(options);
117
+ console.log("⬜ Unchecked input");
118
+ });
119
+ // =============================
120
+ // 3. DROPDOWNS & SELECTS
121
+ // =============================
122
+ /**
123
+ * Selects an option by visible text/label.
124
+ * Pattern: When I select option "United States"
125
+ */
126
+ (0, registry_1.Step)("I select option {string}", async (page, option, table) => {
127
+ const element = (0, state_1.getActiveElement)(page);
128
+ const options = (0, state_1.parseClickOptions)(table);
129
+ // Playwright selects by value or label automatically
130
+ await element.selectOption({ label: option }, options);
131
+ console.log(`🔽 Selected option: "${option}"`);
132
+ });
133
+ // =============================
134
+ // 4. FORMS & FILES
135
+ // =============================
136
+ /**
137
+ * Submits the form containing the current element.
138
+ * If no element is stored, tries to find the first form on page.
139
+ * Pattern: When I submit
140
+ */
141
+ (0, registry_1.Step)("I submit", async (page) => {
142
+ let formLocator;
143
+ try {
144
+ const element = (0, state_1.getActiveElement)(page);
145
+ // Try to find the parent form of the stored element
146
+ formLocator = element.locator("xpath=ancestor-or-self::form");
147
+ }
148
+ catch (e) {
149
+ // If no element stored, find first form on page
150
+ formLocator = page.locator("form").first();
151
+ }
152
+ const count = await formLocator.count();
153
+ if (count === 0) {
154
+ throw new Error("❌ No form found to submit.");
155
+ }
156
+ // Native HTML submit (bypasses some validation, extremely reliable)
157
+ await formLocator.evaluate((f) => f.submit());
158
+ console.log("📨 Submitted form");
159
+ });
160
+ /**
161
+ * Uploads a file to a file input.
162
+ * Pattern: When I upload file "docs/resume.pdf"
163
+ */
164
+ (0, registry_1.Step)("I select file {string}", async (page, filePath) => {
165
+ const element = (0, state_1.getActiveElement)(page);
166
+ await element.setInputFiles(filePath);
167
+ console.log(`📂 Selected file: "${filePath}"`);
168
+ });
169
+ /**
170
+ * Alias for upload.
171
+ * Pattern: When I upload file "..."
172
+ */
173
+ (0, registry_1.Step)("I upload file {string}", async (page, filePath) => {
174
+ const element = (0, state_1.getActiveElement)(page);
175
+ await element.setInputFiles(filePath);
176
+ console.log(`📂 Uploaded file: "${filePath}"`);
177
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=keyboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboard.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/keyboard.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
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
+ // KEYBOARD INTERACTIONS
7
+ // ==================================================
8
+ /**
9
+ * Presses a specific key globally (on the page).
10
+ * Useful for global shortcuts like closing modals (Escape) or scrolling (PageDown).
11
+ * Pattern: When I press key "Enter"
12
+ */
13
+ (0, registry_1.Step)("I press key {string}", async (page, key) => {
14
+ await page.keyboard.press(key);
15
+ console.log(`⌨️ Pressed key: "${key}"`);
16
+ });
17
+ /**
18
+ * Presses a key on the currently stored/active element.
19
+ * Useful for input fields or buttons.
20
+ * Pattern: When I press key "Enter" on element
21
+ */
22
+ (0, registry_1.Step)("I press key {string} on element", async (page, key) => {
23
+ const element = (0, state_1.getActiveElement)(page);
24
+ await element.press(key);
25
+ console.log(`⌨️ Pressed key "${key}" on stored element`);
26
+ });
27
+ /**
28
+ * Types text directly using the keyboard (global typing).
29
+ * Unlike 'I type', this sends keystrokes one by one to the page,
30
+ * regardless of which element is focused.
31
+ * Pattern: When I press keys "Hello World"
32
+ */
33
+ (0, registry_1.Step)("I press keys {string}", async (page, text) => {
34
+ await page.keyboard.type(text);
35
+ console.log(`⌨️ Typed keys: "${text}"`);
36
+ });
37
+ /**
38
+ * Performs a keyboard shortcut (e.g., Ctrl+C, Meta+S).
39
+ * Pattern: When I press shortcut "Control+C"
40
+ */
41
+ (0, registry_1.Step)("I press shortcut {string}", async (page, shortcut) => {
42
+ // Playwright's keyboard.press supports combinations like "Control+KeyC"
43
+ await page.keyboard.press(shortcut);
44
+ console.log(`⌨️ Performed shortcut: "${shortcut}"`);
45
+ });
46
+ /**
47
+ * Holds down a specific key (e.g., Shift) for subsequent actions.
48
+ * Make sure to release it later!
49
+ * Pattern: When I hold down key "Shift"
50
+ */
51
+ (0, registry_1.Step)("I hold down key {string}", async (page, key) => {
52
+ await page.keyboard.down(key);
53
+ console.log(`⬇️ Holding down key: "${key}"`);
54
+ });
55
+ /**
56
+ * Releases a specific key.
57
+ * Pattern: When I release key "Shift"
58
+ */
59
+ (0, registry_1.Step)("I release key {string}", async (page, key) => {
60
+ await page.keyboard.up(key);
61
+ console.log(`⬆️ Released key: "${key}"`);
62
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=misc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"misc.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/misc.ts"],"names":[],"mappings":""}