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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"misc.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/misc.ts"],"names":[],"mappings":""}
|
|
@@ -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":""}
|