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,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setActiveElement = setActiveElement;
4
+ exports.getActiveElement = getActiveElement;
5
+ exports.setActiveElements = setActiveElements;
6
+ exports.getActiveElements = getActiveElements;
7
+ exports.setVariable = setVariable;
8
+ exports.getVariable = getVariable;
9
+ exports.parseClickOptions = parseClickOptions;
10
+ // 1. STATE MANAGEMENT
11
+ // We attach data to the Playwright Page object so it persists between steps.
12
+ function setActiveElement(page, element) {
13
+ page.__bdd_element = element;
14
+ }
15
+ function getActiveElement(page) {
16
+ const el = page.__bdd_element;
17
+ if (!el) {
18
+ throw new Error("❌ No stored element found. Did you forget a 'When I find...' step?");
19
+ }
20
+ return el;
21
+ }
22
+ function setActiveElements(page, elements) {
23
+ page.__bdd_elements = elements;
24
+ }
25
+ function getActiveElements(page) {
26
+ const els = page.__bdd_elements;
27
+ if (!els) {
28
+ throw new Error("❌ No stored elements list found.");
29
+ }
30
+ return els;
31
+ }
32
+ // 2. DATA / ALIAS MANAGEMENT (for @variable support)
33
+ function setVariable(page, key, value) {
34
+ if (!page.__bdd_data) {
35
+ page.__bdd_data = {};
36
+ }
37
+ page.__bdd_data[key] = value;
38
+ }
39
+ function getVariable(page, key) {
40
+ const data = page.__bdd_data;
41
+ return data ? data[key] : undefined;
42
+ }
43
+ // 3. OPTION PARSERS
44
+ // Converts Gherkin DataTables into Playwright ClickOptions
45
+ function parseClickOptions(table) {
46
+ if (!table)
47
+ return {};
48
+ let hash = {};
49
+ // Handle Cucumber DataTable object (legacy) or raw Array
50
+ if (typeof table.rowsHash === "function") {
51
+ hash = table.rowsHash();
52
+ }
53
+ else if (Array.isArray(table)) {
54
+ // Convert Array of Arrays [['force', 'true']] to Object { force: 'true' }
55
+ table.forEach((row) => {
56
+ if (Array.isArray(row) && row.length >= 2) {
57
+ hash[row[0].toString()] = row[1].toString();
58
+ }
59
+ });
60
+ }
61
+ else {
62
+ return {};
63
+ }
64
+ const options = {};
65
+ // Parse specific boolean/number values
66
+ if (hash["force"] === "true")
67
+ options.force = true;
68
+ if (hash["button"])
69
+ options.button = hash["button"];
70
+ if (hash["timeout"])
71
+ options.timeout = parseInt(hash["timeout"], 10);
72
+ // Handle modifiers (comma separated)
73
+ if (hash["modifiers"]) {
74
+ options.modifiers = hash["modifiers"].split(",").map((m) => m.trim());
75
+ }
76
+ // Handle position (x,y)
77
+ if (hash["x"] && hash["y"]) {
78
+ options.position = {
79
+ x: parseInt(hash["x"], 10),
80
+ y: parseInt(hash["y"], 10),
81
+ };
82
+ }
83
+ return options;
84
+ }
@@ -1,23 +1,23 @@
1
1
  import { CucumberExpression } from "@cucumber/cucumber-expressions";
2
2
  import { Page } from "@playwright/test";
3
3
  /**
4
- * Registers a new Step Definition.
5
- * * @param pattern - The Gherkin pattern (e.g., 'I click {string}')
6
- * @param fn - The async function to execute. It receives the Playwright Page object as the first argument.
7
- * * @example
8
- * ```ts
9
- * Step('I scroll to bottom', async (page) => {
10
- * await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
11
- * });
12
- * ```
4
+ * Define the type of function for our steps.
5
+ * Every step automatically gets 'page' as the first argument.
13
6
  */
14
7
  export type StepAction = (page: Page, ...args: any[]) => Promise<void>;
15
- interface StepDefinition {
16
- expression: CucumberExpression;
8
+ /**
9
+ * 1. StepDefinition Interface
10
+ * Updated to allow both CucumberExpression (legacy) AND RegExp (standard)
11
+ */
12
+ export interface StepDefinition {
13
+ expression: CucumberExpression | RegExp;
17
14
  fn: StepAction;
18
- pattern: string;
15
+ pattern: string | RegExp;
19
16
  }
20
17
  export declare const stepRegistry: StepDefinition[];
21
- export declare function Step(pattern: string, fn: StepAction): void;
22
- export {};
18
+ /**
19
+ * 3. The Function to Register Steps
20
+ * Supports passing a string (converted to CucumberExpression) OR a direct RegExp.
21
+ */
22
+ export declare function Step(pattern: string | RegExp, fn: StepAction): void;
23
23
  //# sourceMappingURL=registry.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAEnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC;;;;;;;;;;GAUG;AAGH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvE,UAAU,cAAc;IACtB,UAAU,EAAE,kBAAkB,CAAC;IAC/B,EAAE,EAAE,UAAU,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAID,eAAO,MAAM,YAAY,EAAE,cAAc,EAAO,CAAC;AAMjD,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,QAQnD"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/core/registry.ts"],"names":[],"mappings":"AACA,OAAO,EACL,kBAAkB,EAEnB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvE;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,kBAAkB,GAAG,MAAM,CAAC;IACxC,EAAE,EAAE,UAAU,CAAC;IACf,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAGD,eAAO,MAAM,YAAY,EAAE,cAAc,EAAO,CAAC;AAIjD;;;GAGG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,EAAE,UAAU,QAgB5D"}
@@ -5,13 +5,22 @@ exports.Step = Step;
5
5
  // src/core/registry.ts
6
6
  const cucumber_expressions_1 = require("@cucumber/cucumber-expressions");
7
7
  // 2. The Global Registry
8
- // This array stores every step you create in the backend folder
9
8
  exports.stepRegistry = [];
10
9
  const parameterTypeRegistry = new cucumber_expressions_1.ParameterTypeRegistry();
11
- // 3. The Function to Register Steps
12
- // You will use this inside src/backend/actions/...
10
+ /**
11
+ * 3. The Function to Register Steps
12
+ * Supports passing a string (converted to CucumberExpression) OR a direct RegExp.
13
+ */
13
14
  function Step(pattern, fn) {
14
- const expression = new cucumber_expressions_1.CucumberExpression(pattern, parameterTypeRegistry);
15
+ let expression;
16
+ if (pattern instanceof RegExp) {
17
+ // ✅ If it's a Regex, use it directly (Faster, Standard)
18
+ expression = pattern;
19
+ }
20
+ else {
21
+ // ⚠️ If it's a String, convert to Cucumber Expression (Legacy)
22
+ expression = new cucumber_expressions_1.CucumberExpression(pattern, parameterTypeRegistry);
23
+ }
15
24
  exports.stepRegistry.push({
16
25
  expression,
17
26
  fn,
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"AAMA,OAAO,0BAA0B,CAAC;AAClC,OAAO,6BAA6B,CAAC;AACrC,OAAO,2BAA2B,CAAC;AACnC,OAAO,sBAAsB,CAAC;AAC9B,OAAO,uBAAuB,CAAC;AAE/B,OAAO,qBAAqB,CAAC;AAE7B,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC3C;AAED,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,QAyIpE"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"AAMA,OAAO,0BAA0B,CAAC;AAClC,OAAO,6BAA6B,CAAC;AACrC,OAAO,2BAA2B,CAAC;AACnC,OAAO,sBAAsB,CAAC;AAC9B,OAAO,uBAAuB,CAAC;AAE/B,OAAO,qBAAqB,CAAC;AAE7B,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC3C;AASD,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,QA6LpE"}
@@ -58,7 +58,6 @@ function runTests(featureGlob, options) {
58
58
  for (const file of files) {
59
59
  const content = fs.readFileSync(file, "utf8");
60
60
  // 1. CAPTURE FEATURE TAGS
61
- // Loose Regex: Matches any lines starting with @ above "Feature:"
62
61
  const featureTagMatch = content.match(/((?:@\S+\s*)+)Feature:/);
63
62
  const rawFeatureTags = featureTagMatch ? featureTagMatch[1] : "";
64
63
  const featureTags = rawFeatureTags.replace(/[\r\n]+/g, " ").trim();
@@ -67,9 +66,8 @@ function runTests(featureGlob, options) {
67
66
  ? featureMatch[1].trim()
68
67
  : "Unnamed Feature";
69
68
  test_1.test.describe(featureName, () => {
70
- // 2. LOOSE SCENARIO REGEX
69
+ // 2. SCENARIO REGEX
71
70
  // Matches: Optional Tags -> (Scenario OR Scenario Outline) -> Name
72
- // Change: @\S+ allows dots/dashes. "Scenario|Scenario Outline" supports outlines.
73
71
  const scenarioRegex = /(?:((?:@\S+\s*)+))?(?:Scenario|Scenario Outline):\s*(.+)/g;
74
72
  let match;
75
73
  let foundCount = 0;
@@ -83,9 +81,7 @@ function runTests(featureGlob, options) {
83
81
  const fullName = combinedTags
84
82
  ? `${scenarioName} ${combinedTags}`
85
83
  : scenarioName;
86
- // DEBUG LOG: See what we found
87
- // console.log(` 👉 Found: "${scenarioName}" [Tags: ${combinedTags}]`);
88
- // 4. ENV FILTERING (Optional)
84
+ // 4. ENV FILTERING
89
85
  const activeFilter = options?.tags || envTag;
90
86
  if (activeFilter) {
91
87
  const targetGroups = activeFilter.split(",").map((t) => t.trim());
@@ -96,6 +92,7 @@ function runTests(featureGlob, options) {
96
92
  if (!isMatch)
97
93
  continue;
98
94
  }
95
+ // 5. EXTRACT SCENARIO BLOCK
99
96
  const startIndex = match.index + match[0].length;
100
97
  const nextMatchIndex = content
101
98
  .slice(startIndex)
@@ -103,43 +100,91 @@ function runTests(featureGlob, options) {
103
100
  const blockEnd = nextMatchIndex === -1 ? content.length : startIndex + nextMatchIndex;
104
101
  const scenarioBlock = content.slice(startIndex, blockEnd);
105
102
  (0, test_1.test)(fullName, async ({ page }, testInfo) => {
106
- const lines = scenarioBlock
107
- .trim()
108
- .split("\n")
109
- .map((l) => l.trim())
110
- .filter((l) => l);
111
- for (let i = 0; i < lines.length; i++) {
112
- const stepText = lines[i];
113
- if (stepText.startsWith("#") ||
114
- stepText.startsWith("@") ||
115
- stepText === "")
103
+ // ==================================================
104
+ // PHASE 1: PARSE GHERKIN (Preserve Formatting)
105
+ // ==================================================
106
+ const rawLines = scenarioBlock.split("\n");
107
+ const steps = [];
108
+ let currentStep = null;
109
+ let docStringBuffer = [];
110
+ let isDocStringOpen = false;
111
+ for (let line of rawLines) {
112
+ const trimmedLine = line.trim();
113
+ // A. Handle DocStrings (""")
114
+ if (trimmedLine.startsWith('"""')) {
115
+ isDocStringOpen = !isDocStringOpen;
116
+ if (!isDocStringOpen && currentStep) {
117
+ // Closing DocString: Save buffer to step
118
+ currentStep.docString = docStringBuffer.join("\n");
119
+ docStringBuffer = [];
120
+ }
116
121
  continue;
117
- // Handle Data Tables
118
- const tableData = [];
119
- while (i + 1 < lines.length && lines[i + 1].startsWith("|")) {
120
- i++;
121
- const row = lines[i]
122
- .split("|")
123
- .map((cell) => cell.trim())
124
- .filter((cell) => cell !== "");
125
- tableData.push(row);
126
122
  }
127
- const cleanStep = stepText
123
+ // B. Inside DocString? Capture raw line (preserve indent)
124
+ if (isDocStringOpen) {
125
+ docStringBuffer.push(line); // Don't trim!
126
+ continue;
127
+ }
128
+ // C. Skip Empty Lines & Comments
129
+ if (!trimmedLine ||
130
+ trimmedLine.startsWith("#") ||
131
+ trimmedLine.startsWith("@")) {
132
+ continue;
133
+ }
134
+ // D. Handle Data Tables (| col | col |)
135
+ if (trimmedLine.startsWith("|")) {
136
+ if (currentStep) {
137
+ if (!currentStep.dataTable)
138
+ currentStep.dataTable = [];
139
+ const row = trimmedLine
140
+ .split("|")
141
+ .map((cell) => cell.trim())
142
+ .filter((cell, index, arr) => index > 0 && index < arr.length - 1);
143
+ // Simple split often leaves empty strings at start/end
144
+ // Better split logic:
145
+ const cleanRow = trimmedLine
146
+ .split("|")
147
+ .slice(1, -1) // Remove first and last empty splits from |...|
148
+ .map((c) => c.trim());
149
+ currentStep.dataTable.push(cleanRow);
150
+ }
151
+ continue;
152
+ }
153
+ // E. It is a New Step
154
+ const cleanText = trimmedLine
128
155
  .replace(/^(Given|When|Then|And|But)\s+/i, "")
129
- .replace(/:$/, "")
156
+ .replace(/:$/, "") // Remove trailing colon
130
157
  .trim();
131
- const matchResult = findMatchingStep(cleanStep);
158
+ currentStep = {
159
+ text: trimmedLine,
160
+ cleanText: cleanText,
161
+ };
162
+ steps.push(currentStep);
163
+ }
164
+ // ==================================================
165
+ // PHASE 2: EXECUTE STEPS
166
+ // ==================================================
167
+ console.log(`\n🔹 Scenario: ${scenarioName}`);
168
+ for (const step of steps) {
169
+ const matchResult = findMatchingStep(step.cleanText);
132
170
  if (!matchResult) {
133
- throw new Error(`❌ Undefined Step: "${cleanStep}"`);
171
+ throw new Error(`❌ Undefined Step: "${step.cleanText}"`);
134
172
  }
135
173
  try {
174
+ console.log(` executing: ${step.text.trim()}`);
136
175
  const args = [...matchResult.args];
137
- if (tableData.length > 0)
138
- args.push(tableData);
176
+ // Append Data Table if present
177
+ if (step.dataTable && step.dataTable.length > 0) {
178
+ args.push(step.dataTable);
179
+ }
180
+ // Append DocString if present
181
+ if (step.docString) {
182
+ args.push(step.docString);
183
+ }
139
184
  await matchResult.fn(page, ...args);
140
185
  }
141
186
  catch (error) {
142
- console.error(`❌ Failed at step: "${stepText}"`);
187
+ console.error(`❌ Failed at step: "${step.text.trim()}"`);
143
188
  const screenshot = await page.screenshot({
144
189
  fullPage: true,
145
190
  type: "png",
@@ -153,32 +198,41 @@ function runTests(featureGlob, options) {
153
198
  }
154
199
  });
155
200
  }
156
- // SAFETY CHECK
157
201
  if (foundCount === 0) {
158
202
  console.warn(`⚠️ File matched but 0 Scenarios found in: ${file}`);
159
- console.warn(` Check if you are using 'Scenario Outline' or strange spacing.`);
160
203
  }
161
204
  });
162
205
  }
163
206
  }
207
+ /**
208
+ * Finds the matching step definition from the registry.
209
+ * Supports: RegExp (with capture groups) and CucumberExpressions.
210
+ */
164
211
  function findMatchingStep(text) {
165
212
  for (const step of registry_1.stepRegistry) {
213
+ // 1. RegExp Match
166
214
  if (step.expression instanceof RegExp) {
167
215
  const match = step.expression.exec(text);
168
- if (match)
216
+ if (match) {
217
+ // match[0] is full string, slice(1) are capture groups
169
218
  return { fn: step.fn, args: match.slice(1) };
219
+ }
170
220
  }
221
+ // 2. Cucumber Expression Match (if strictly typed)
171
222
  else if (typeof step.expression.match === "function") {
172
223
  const match = step.expression.match(text);
173
- if (match)
224
+ if (match) {
174
225
  return {
175
226
  fn: step.fn,
176
227
  args: match.map((arg) => arg.getValue(null)),
177
228
  };
229
+ }
178
230
  }
231
+ // 3. String Match (Legacy/Simple)
179
232
  else if (typeof step.expression === "string") {
180
- if (step.expression === text)
233
+ if (step.expression === text) {
181
234
  return { fn: step.fn, args: [] };
235
+ }
182
236
  }
183
237
  }
184
238
  return null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "playwright-cucumber-ts-steps",
3
3
  "description": "A collection of reusable Playwright step definitions for Cucumber in TypeScript, designed to streamline end-to-end testing across web, API, and mobile applications.",
4
- "version": "1.1.7",
4
+ "version": "1.1.8",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",