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":"click.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/click.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,179 @@
|
|
|
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
|
+
* Clicks on the previously stored element.
|
|
7
|
+
* Pattern: When I click
|
|
8
|
+
*/
|
|
9
|
+
(0, registry_1.Step)("I click", async (page, table) => {
|
|
10
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
11
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
12
|
+
await element.click(options);
|
|
13
|
+
console.log("🖱️ Clicked on stored element");
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Clicks on an element matching the given selector.
|
|
17
|
+
* Pattern: When I click on element {string}
|
|
18
|
+
*/
|
|
19
|
+
(0, registry_1.Step)("I click on element {string}", async (page, selector, table) => {
|
|
20
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
21
|
+
const element = page.locator(selector);
|
|
22
|
+
await element.click(options);
|
|
23
|
+
(0, state_1.setActiveElement)(page, element);
|
|
24
|
+
console.log(`🖱️ Clicked on element "${selector}"`);
|
|
25
|
+
});
|
|
26
|
+
/**
|
|
27
|
+
* Clicks on a button with the given label.
|
|
28
|
+
* Pattern: When I click on button {string}
|
|
29
|
+
*/
|
|
30
|
+
(0, registry_1.Step)("I click on button {string}", async (page, label, table) => {
|
|
31
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
32
|
+
const button = page.getByRole("button", { name: label });
|
|
33
|
+
await button.click(options);
|
|
34
|
+
(0, state_1.setActiveElement)(page, button);
|
|
35
|
+
console.log(`🖱️ Clicked on button "${label}"`);
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Clicks on a link with the given text.
|
|
39
|
+
* Pattern: When I click on link {string}
|
|
40
|
+
*/
|
|
41
|
+
(0, registry_1.Step)("I click on link {string}", async (page, text, table) => {
|
|
42
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
43
|
+
const link = page.getByRole("link", { name: text });
|
|
44
|
+
await link.click(options);
|
|
45
|
+
(0, state_1.setActiveElement)(page, link);
|
|
46
|
+
console.log(`✅ Clicked on link "${text}"`);
|
|
47
|
+
});
|
|
48
|
+
/**
|
|
49
|
+
* Clicks on a label with the given text.
|
|
50
|
+
* Pattern: When I click on label {string}
|
|
51
|
+
*/
|
|
52
|
+
(0, registry_1.Step)("I click on label {string}", async (page, labelText, table) => {
|
|
53
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
54
|
+
const label = page.getByLabel(labelText);
|
|
55
|
+
await label.click(options);
|
|
56
|
+
(0, state_1.setActiveElement)(page, label);
|
|
57
|
+
console.log(`🏷️ Clicked on label "${labelText}"`);
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Clicks on an element containing the given text (fuzzy match).
|
|
61
|
+
* Supports aliasing with @alias.
|
|
62
|
+
* Pattern: When I click on text {string}
|
|
63
|
+
*/
|
|
64
|
+
(0, registry_1.Step)("I click on text {string}", async (page, rawText, table) => {
|
|
65
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
66
|
+
let text = rawText;
|
|
67
|
+
// Handle Alias (e.g., "@username")
|
|
68
|
+
if (rawText.startsWith("@")) {
|
|
69
|
+
const aliasKey = rawText.slice(1);
|
|
70
|
+
const storedValue = (0, state_1.getVariable)(page, aliasKey);
|
|
71
|
+
if (!storedValue) {
|
|
72
|
+
throw new Error(`❌ No value found for alias "@${aliasKey}"`);
|
|
73
|
+
}
|
|
74
|
+
text = storedValue;
|
|
75
|
+
}
|
|
76
|
+
const locator = page.getByText(text, { exact: false }).first();
|
|
77
|
+
await locator.waitFor({ state: "visible", timeout: 5000 });
|
|
78
|
+
await locator.click(options);
|
|
79
|
+
(0, state_1.setActiveElement)(page, locator);
|
|
80
|
+
console.log(`🖱️ Clicked on text "${text}"`);
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* Clicks on an element containing the exact given text.
|
|
84
|
+
* Pattern: When I click on exact text {string}
|
|
85
|
+
*/
|
|
86
|
+
(0, registry_1.Step)("I click on exact text {string}", async (page, exactText, table) => {
|
|
87
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
88
|
+
const locator = page.getByText(exactText, { exact: true });
|
|
89
|
+
await locator.waitFor({ state: "visible", timeout: 5000 });
|
|
90
|
+
await locator.click(options);
|
|
91
|
+
(0, state_1.setActiveElement)(page, locator);
|
|
92
|
+
console.log(`🖱️ Clicked on exact text "${exactText}"`);
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* Clicks all previously stored elements.
|
|
96
|
+
* Pattern: When I click all
|
|
97
|
+
*/
|
|
98
|
+
(0, registry_1.Step)("I click all", async (page, table) => {
|
|
99
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
100
|
+
const elements = (0, state_1.getActiveElements)(page);
|
|
101
|
+
const count = await elements.count();
|
|
102
|
+
if (count === 0)
|
|
103
|
+
throw new Error("⚠️ No elements found to click.");
|
|
104
|
+
for (let i = 0; i < count; i++) {
|
|
105
|
+
const el = elements.nth(i);
|
|
106
|
+
await el.waitFor({ state: "visible", timeout: 5000 });
|
|
107
|
+
await el.click(options);
|
|
108
|
+
console.log(`🖱️ Clicked element #${i + 1}`);
|
|
109
|
+
}
|
|
110
|
+
console.log(`✅ Clicked all ${count} elements.`);
|
|
111
|
+
});
|
|
112
|
+
/**
|
|
113
|
+
* Double-clicks on the previously stored element.
|
|
114
|
+
* Pattern: When I double click
|
|
115
|
+
*/
|
|
116
|
+
(0, registry_1.Step)("I double click", async (page, table) => {
|
|
117
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
118
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
119
|
+
await element.dblclick(options);
|
|
120
|
+
console.log("🖱️ Double-clicked on stored element");
|
|
121
|
+
});
|
|
122
|
+
/**
|
|
123
|
+
* Double-clicks on an element containing the given text.
|
|
124
|
+
* Pattern: When I double click on text {string}
|
|
125
|
+
*/
|
|
126
|
+
(0, registry_1.Step)("I double click on text {string}", async (page, text, table) => {
|
|
127
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
128
|
+
const element = page.getByText(text).first();
|
|
129
|
+
await element.dblclick(options);
|
|
130
|
+
console.log(`🖱️ Double-clicked on text "${text}"`);
|
|
131
|
+
});
|
|
132
|
+
/**
|
|
133
|
+
* Double-clicks at the given page coordinates.
|
|
134
|
+
* Pattern: When I double click position {int} {int}
|
|
135
|
+
*/
|
|
136
|
+
(0, registry_1.Step)("I double click position {int} {int}", async (page, x, y, table) => {
|
|
137
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
138
|
+
await page.mouse.dblclick(x, y, options);
|
|
139
|
+
console.log(`🖱️ Double-clicked at (${x}, ${y})`);
|
|
140
|
+
});
|
|
141
|
+
/**
|
|
142
|
+
* Right-clicks on the previously stored element.
|
|
143
|
+
* Pattern: When I right click
|
|
144
|
+
*/
|
|
145
|
+
(0, registry_1.Step)("I right click", async (page, table) => {
|
|
146
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
147
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
148
|
+
await element.click({ button: "right", ...options });
|
|
149
|
+
console.log("🖱️ Right-clicked on stored element");
|
|
150
|
+
});
|
|
151
|
+
/**
|
|
152
|
+
* Right-clicks on an element containing the given text.
|
|
153
|
+
* Pattern: When I right click on text {string}
|
|
154
|
+
*/
|
|
155
|
+
(0, registry_1.Step)("I right click on text {string}", async (page, text, table) => {
|
|
156
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
157
|
+
const element = page.getByText(text).first();
|
|
158
|
+
await element.click({ button: "right", ...options });
|
|
159
|
+
console.log(`🖱️ Right-clicked on text "${text}"`);
|
|
160
|
+
});
|
|
161
|
+
/**
|
|
162
|
+
* Right-clicks at the given page coordinates.
|
|
163
|
+
* Pattern: When I right click position {int} {int}
|
|
164
|
+
*/
|
|
165
|
+
(0, registry_1.Step)("I right click position {int} {int}", async (page, x, y, table) => {
|
|
166
|
+
const options = (0, state_1.parseClickOptions)(table);
|
|
167
|
+
await page.mouse.click(x, y, { button: "right", ...options });
|
|
168
|
+
console.log(`🖱️ Right-clicked at (${x}, ${y})`);
|
|
169
|
+
});
|
|
170
|
+
/**
|
|
171
|
+
* Clicks on an element matching the given selector (Regex Version).
|
|
172
|
+
* Pattern: I click on selector "..."
|
|
173
|
+
*/
|
|
174
|
+
(0, registry_1.Step)(/^I click on selector "([^"]+)"$/, async (page, selector) => {
|
|
175
|
+
const locator = page.locator(selector);
|
|
176
|
+
await locator.click();
|
|
177
|
+
(0, state_1.setActiveElement)(page, locator);
|
|
178
|
+
console.log(`🖱️ Clicked on selector: ${selector}`);
|
|
179
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/find.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const test_1 = require("@playwright/test");
|
|
4
|
+
const registry_1 = require("../../core/registry");
|
|
5
|
+
const state_1 = require("../utils/state");
|
|
6
|
+
// =============================
|
|
7
|
+
// 1. GENERAL FINDING (Single)
|
|
8
|
+
// =============================
|
|
9
|
+
/**
|
|
10
|
+
* Finds an element by CSS selector.
|
|
11
|
+
* Pattern: When I find element by selector ".my-class"
|
|
12
|
+
*/
|
|
13
|
+
(0, registry_1.Step)("I find element by selector {string}", async (page, selector) => {
|
|
14
|
+
const element = page.locator(selector);
|
|
15
|
+
await (0, test_1.expect)(element).toHaveCount(1);
|
|
16
|
+
(0, state_1.setActiveElement)(page, element);
|
|
17
|
+
console.log(`🔍 Found element by selector: "${selector}"`);
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Finds an element by exact text.
|
|
21
|
+
* Pattern: When I find element by text "Submit"
|
|
22
|
+
*/
|
|
23
|
+
(0, registry_1.Step)("I find element by text {string}", async (page, text) => {
|
|
24
|
+
const element = page.getByText(text, { exact: true });
|
|
25
|
+
await (0, test_1.expect)(element).toHaveCount(1);
|
|
26
|
+
(0, state_1.setActiveElement)(page, element);
|
|
27
|
+
console.log(`🔍 Found element by exact text: "${text}"`);
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Finds an element by title attribute.
|
|
31
|
+
* Pattern: When I find element by title "Tooltip"
|
|
32
|
+
*/
|
|
33
|
+
(0, registry_1.Step)("I find element by title {string}", async (page, title) => {
|
|
34
|
+
const element = page.getByTitle(title);
|
|
35
|
+
await (0, test_1.expect)(element).toHaveCount(1);
|
|
36
|
+
(0, state_1.setActiveElement)(page, element);
|
|
37
|
+
console.log(`🔍 Found element by title: "${title}"`);
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Finds an element by test id (data-testid).
|
|
41
|
+
* Pattern: When I find element by testid "submit-btn"
|
|
42
|
+
*/
|
|
43
|
+
(0, registry_1.Step)("I find element by testid {string}", async (page, testid) => {
|
|
44
|
+
const element = page.getByTestId(testid);
|
|
45
|
+
await (0, test_1.expect)(element).toHaveCount(1);
|
|
46
|
+
(0, state_1.setActiveElement)(page, element);
|
|
47
|
+
console.log(`🔍 Found element by testid: "${testid}"`);
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* Finds an element by ARIA role.
|
|
51
|
+
* Pattern: When I find element by role "button"
|
|
52
|
+
*/
|
|
53
|
+
(0, registry_1.Step)("I find element by role {string}", async (page, role) => {
|
|
54
|
+
const element = page.getByRole(role);
|
|
55
|
+
await (0, test_1.expect)(element).toHaveCount(1);
|
|
56
|
+
(0, state_1.setActiveElement)(page, element);
|
|
57
|
+
console.log(`🔍 Found element by role: "${role}"`);
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Finds an element by placeholder.
|
|
61
|
+
* Pattern: When I find element by placeholder text "Search..."
|
|
62
|
+
*/
|
|
63
|
+
(0, registry_1.Step)("I find element by placeholder text {string}", async (page, text) => {
|
|
64
|
+
const element = page.getByPlaceholder(text);
|
|
65
|
+
await (0, test_1.expect)(element).toHaveCount(1);
|
|
66
|
+
(0, state_1.setActiveElement)(page, element);
|
|
67
|
+
console.log(`🔍 Found element by placeholder: "${text}"`);
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Finds an element by label.
|
|
71
|
+
* Pattern: When I find element by label text "Username"
|
|
72
|
+
*/
|
|
73
|
+
(0, registry_1.Step)("I find element by label text {string}", async (page, label) => {
|
|
74
|
+
const element = page.getByLabel(label);
|
|
75
|
+
await (0, test_1.expect)(element).toHaveCount(1);
|
|
76
|
+
(0, state_1.setActiveElement)(page, element);
|
|
77
|
+
console.log(`🔍 Found element by label: "${label}"`);
|
|
78
|
+
});
|
|
79
|
+
/**
|
|
80
|
+
* Finds an element by alt text (images).
|
|
81
|
+
* Pattern: When I find element by alt text "Logo"
|
|
82
|
+
*/
|
|
83
|
+
(0, registry_1.Step)("I find element by alt text {string}", async (page, alt) => {
|
|
84
|
+
const element = page.getByAltText(alt);
|
|
85
|
+
await (0, test_1.expect)(element).toHaveCount(1);
|
|
86
|
+
(0, state_1.setActiveElement)(page, element);
|
|
87
|
+
console.log(`🔍 Found element by alt text: "${alt}"`);
|
|
88
|
+
});
|
|
89
|
+
// =============================
|
|
90
|
+
// 2. SPECIFIC ELEMENTS (Link/Heading/Name)
|
|
91
|
+
// =============================
|
|
92
|
+
/**
|
|
93
|
+
* Finds a link by text.
|
|
94
|
+
* Pattern: When I find link by text "Home"
|
|
95
|
+
*/
|
|
96
|
+
(0, registry_1.Step)("I find link by text {string}", async (page, text) => {
|
|
97
|
+
const element = page.getByRole("link", { name: text });
|
|
98
|
+
// We use .first() here because links often have duplicates (e.g. footer/header)
|
|
99
|
+
// but strict mode usually prefers uniqueness.
|
|
100
|
+
(0, state_1.setActiveElement)(page, element.first());
|
|
101
|
+
console.log(`🔍 Found link by text: "${text}"`);
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* Finds a heading by text.
|
|
105
|
+
* Pattern: When I find heading by text "Welcome"
|
|
106
|
+
*/
|
|
107
|
+
(0, registry_1.Step)("I find heading by text {string}", async (page, text) => {
|
|
108
|
+
const element = page.getByRole("heading", { name: text });
|
|
109
|
+
(0, state_1.setActiveElement)(page, element.first());
|
|
110
|
+
console.log(`🔍 Found heading by text: "${text}"`);
|
|
111
|
+
});
|
|
112
|
+
/**
|
|
113
|
+
* Finds an element by name attribute.
|
|
114
|
+
* Pattern: When I find element by name "email"
|
|
115
|
+
*/
|
|
116
|
+
(0, registry_1.Step)("I find element by name {string}", async (page, name) => {
|
|
117
|
+
// Broad selector for name attribute
|
|
118
|
+
const element = page.locator(`[name="${name}"]`);
|
|
119
|
+
await (0, test_1.expect)(element).toHaveCount(1);
|
|
120
|
+
(0, state_1.setActiveElement)(page, element);
|
|
121
|
+
console.log(`🔍 Found element by name: "${name}"`);
|
|
122
|
+
});
|
|
123
|
+
// =============================
|
|
124
|
+
// 3. GENERAL FINDING (Multiple)
|
|
125
|
+
// =============================
|
|
126
|
+
/**
|
|
127
|
+
* Finds all elements by selector.
|
|
128
|
+
* Pattern: When I find elements by selector ".items"
|
|
129
|
+
*/
|
|
130
|
+
(0, registry_1.Step)("I find elements by selector {string}", async (page, selector) => {
|
|
131
|
+
const elements = page.locator(selector);
|
|
132
|
+
const count = await elements.count();
|
|
133
|
+
(0, state_1.setActiveElements)(page, elements);
|
|
134
|
+
console.log(`🔍 Found ${count} elements with selector: "${selector}"`);
|
|
135
|
+
});
|
|
136
|
+
/**
|
|
137
|
+
* Finds all headings by text.
|
|
138
|
+
* Pattern: When I find headings by text "Chapter"
|
|
139
|
+
*/
|
|
140
|
+
(0, registry_1.Step)("I find headings by text {string}", async (page, text) => {
|
|
141
|
+
const elements = page.getByRole("heading", { name: text });
|
|
142
|
+
(0, state_1.setActiveElements)(page, elements);
|
|
143
|
+
console.log(`🔍 Found headings matching "${text}"`);
|
|
144
|
+
});
|
|
145
|
+
/**
|
|
146
|
+
* Finds all buttons by text (supports alias).
|
|
147
|
+
* Pattern: When I find buttons by text "Save"
|
|
148
|
+
*/
|
|
149
|
+
(0, registry_1.Step)("I find buttons by text {string}", async (page, text) => {
|
|
150
|
+
let searchText = text;
|
|
151
|
+
// Handle Alias
|
|
152
|
+
if (text.startsWith("@")) {
|
|
153
|
+
const alias = text.slice(1);
|
|
154
|
+
const val = (0, state_1.getVariable)(page, alias);
|
|
155
|
+
if (!val)
|
|
156
|
+
throw new Error(`❌ No value found for alias "@${alias}"`);
|
|
157
|
+
searchText = val;
|
|
158
|
+
}
|
|
159
|
+
const elements = page.getByRole("button", { name: searchText });
|
|
160
|
+
(0, state_1.setActiveElements)(page, elements);
|
|
161
|
+
console.log(`🔍 Found buttons matching "${searchText}"`);
|
|
162
|
+
});
|
|
163
|
+
// =============================
|
|
164
|
+
// 4. GET / REFINE SELECTION
|
|
165
|
+
// =============================
|
|
166
|
+
/**
|
|
167
|
+
* Gets the first element from a stored list.
|
|
168
|
+
* Pattern: When I get first element
|
|
169
|
+
*/
|
|
170
|
+
(0, registry_1.Step)("I get first element", async (page) => {
|
|
171
|
+
const elements = (0, state_1.getActiveElements)(page);
|
|
172
|
+
const element = elements.first();
|
|
173
|
+
(0, state_1.setActiveElement)(page, element);
|
|
174
|
+
console.log("👉 Selected FIRST element");
|
|
175
|
+
});
|
|
176
|
+
/**
|
|
177
|
+
* Gets the last element from a stored list.
|
|
178
|
+
* Pattern: When I get last element
|
|
179
|
+
*/
|
|
180
|
+
(0, registry_1.Step)("I get last element", async (page) => {
|
|
181
|
+
const elements = (0, state_1.getActiveElements)(page);
|
|
182
|
+
const element = elements.last();
|
|
183
|
+
(0, state_1.setActiveElement)(page, element);
|
|
184
|
+
console.log("👉 Selected LAST element");
|
|
185
|
+
});
|
|
186
|
+
/**
|
|
187
|
+
* Gets the nth element (1-based index).
|
|
188
|
+
* Pattern: When I get 2nd element
|
|
189
|
+
*/
|
|
190
|
+
(0, registry_1.Step)(/^I get (\d+)(?:st|nd|rd|th) element$/, async (page, indexStr) => {
|
|
191
|
+
const index = parseInt(indexStr, 10);
|
|
192
|
+
const elements = (0, state_1.getActiveElements)(page);
|
|
193
|
+
const count = await elements.count();
|
|
194
|
+
if (index < 1 || index > count) {
|
|
195
|
+
throw new Error(`❌ Cannot get element #${index} — only ${count} found.`);
|
|
196
|
+
}
|
|
197
|
+
// Playwright is 0-based, Gherkin is 1-based
|
|
198
|
+
const element = elements.nth(index - 1);
|
|
199
|
+
(0, state_1.setActiveElement)(page, element);
|
|
200
|
+
console.log(`👉 Selected element #${index}`);
|
|
201
|
+
});
|
|
202
|
+
/**
|
|
203
|
+
* Gets the currently focused element.
|
|
204
|
+
* Pattern: When I get focused element
|
|
205
|
+
*/
|
|
206
|
+
(0, registry_1.Step)("I get focused element", async (page) => {
|
|
207
|
+
// Use CSS selector for focused element
|
|
208
|
+
const element = page.locator("*:focus");
|
|
209
|
+
(0, state_1.setActiveElement)(page, element);
|
|
210
|
+
console.log("👉 Selected FOCUSED element");
|
|
211
|
+
});
|
|
212
|
+
// =============================
|
|
213
|
+
// 5. INPUTS & TEXTAREAS SPECIFICS
|
|
214
|
+
// =============================
|
|
215
|
+
/**
|
|
216
|
+
* Finds input by ID.
|
|
217
|
+
* Pattern: When I find input by ID "username"
|
|
218
|
+
*/
|
|
219
|
+
(0, registry_1.Step)("I find input by ID {string}", async (page, id) => {
|
|
220
|
+
const element = page.locator(`input#${id}`);
|
|
221
|
+
(0, state_1.setActiveElement)(page, element);
|
|
222
|
+
console.log(`🔍 Found input by ID: "${id}"`);
|
|
223
|
+
});
|
|
224
|
+
/**
|
|
225
|
+
* Finds input by name.
|
|
226
|
+
* Pattern: When I find input by name "email"
|
|
227
|
+
*/
|
|
228
|
+
(0, registry_1.Step)("I find input by name {string}", async (page, name) => {
|
|
229
|
+
const element = page.locator(`input[name="${name}"]`);
|
|
230
|
+
(0, state_1.setActiveElement)(page, element);
|
|
231
|
+
console.log(`🔍 Found input by name: "${name}"`);
|
|
232
|
+
});
|
|
233
|
+
/**
|
|
234
|
+
* Finds input by placeholder.
|
|
235
|
+
* Pattern: When I find input by placeholder text "Enter email"
|
|
236
|
+
*/
|
|
237
|
+
(0, registry_1.Step)("I find input by placeholder text {string}", async (page, placeholder) => {
|
|
238
|
+
const element = page.locator(`input[placeholder="${placeholder}"]`);
|
|
239
|
+
(0, state_1.setActiveElement)(page, element);
|
|
240
|
+
console.log(`🔍 Found input by placeholder: "${placeholder}"`);
|
|
241
|
+
});
|
|
242
|
+
/**
|
|
243
|
+
* Finds input by display value (supports alias).
|
|
244
|
+
* Pattern: When I find input by display value "John Doe"
|
|
245
|
+
*/
|
|
246
|
+
(0, registry_1.Step)("I find input by display value {string}", async (page, value) => {
|
|
247
|
+
let searchValue = value;
|
|
248
|
+
if (value.startsWith("@")) {
|
|
249
|
+
const alias = value.slice(1);
|
|
250
|
+
const val = (0, state_1.getVariable)(page, alias);
|
|
251
|
+
if (!val)
|
|
252
|
+
throw new Error(`❌ No value found for alias "@${alias}"`);
|
|
253
|
+
searchValue = val;
|
|
254
|
+
}
|
|
255
|
+
const element = page.locator(`input[value="${searchValue}"]`);
|
|
256
|
+
await (0, test_1.expect)(element).toBeVisible();
|
|
257
|
+
(0, state_1.setActiveElement)(page, element);
|
|
258
|
+
console.log(`🔍 Found input with value: "${searchValue}"`);
|
|
259
|
+
});
|
|
260
|
+
/**
|
|
261
|
+
* Finds textarea by label.
|
|
262
|
+
* Pattern: When I find textarea by label text "Comments"
|
|
263
|
+
*/
|
|
264
|
+
(0, registry_1.Step)("I find textarea by label text {string}", async (page, label) => {
|
|
265
|
+
const element = page.getByLabel(label).locator("textarea").first();
|
|
266
|
+
// Fallback if strict label matching fails, try locator strategy
|
|
267
|
+
const count = await element.count();
|
|
268
|
+
if (count === 0) {
|
|
269
|
+
// Try generic label finding
|
|
270
|
+
const altElement = page.getByLabel(label);
|
|
271
|
+
(0, state_1.setActiveElement)(page, altElement);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
(0, state_1.setActiveElement)(page, element);
|
|
275
|
+
}
|
|
276
|
+
console.log(`🔍 Found textarea by label: "${label}"`);
|
|
277
|
+
});
|
|
278
|
+
// =============================
|
|
279
|
+
// 6. UTILITIES (Store Text)
|
|
280
|
+
// =============================
|
|
281
|
+
/**
|
|
282
|
+
* Stores the text of the current element into a variable.
|
|
283
|
+
* Pattern: When I store element text as "myVar"
|
|
284
|
+
*/
|
|
285
|
+
(0, registry_1.Step)("I store element text as {string}", async (page, alias) => {
|
|
286
|
+
const element = (0, state_1.getActiveElement)(page);
|
|
287
|
+
const text = await element.textContent();
|
|
288
|
+
const cleanText = text?.trim() || "";
|
|
289
|
+
(0, state_1.setVariable)(page, alias, cleanText);
|
|
290
|
+
console.log(`💾 Stored text "${cleanText}" as variable "@${alias}"`);
|
|
291
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../src/backend/actions/form.ts"],"names":[],"mappings":""}
|
|
@@ -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', '...'] ]
|