playwright-cucumber-ts-steps 1.3.0 → 1.3.2

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 (81) hide show
  1. package/dist/backend/actions/click.d.ts +84 -0
  2. package/dist/backend/actions/click.d.ts.map +1 -1
  3. package/dist/backend/actions/click.js +167 -17
  4. package/dist/backend/actions/form.d.ts +6 -0
  5. package/dist/backend/actions/form.d.ts.map +1 -1
  6. package/dist/backend/actions/form.js +17 -1
  7. package/dist/backend/actions/formTable.js +1 -1
  8. package/dist/backend/actions/frames.js +3 -3
  9. package/dist/backend/actions/index.d.ts +2 -0
  10. package/dist/backend/actions/index.d.ts.map +1 -1
  11. package/dist/backend/actions/index.js +2 -0
  12. package/dist/backend/actions/inputs.js +18 -18
  13. package/dist/backend/actions/interactions.d.ts +14 -5
  14. package/dist/backend/actions/interactions.d.ts.map +1 -1
  15. package/dist/backend/actions/interactions.js +69 -13
  16. package/dist/backend/actions/keyboard.js +6 -6
  17. package/dist/backend/actions/misc.d.ts +8 -2
  18. package/dist/backend/actions/misc.d.ts.map +1 -1
  19. package/dist/backend/actions/misc.js +64 -16
  20. package/dist/backend/actions/mobile.js +7 -7
  21. package/dist/backend/actions/mouse.js +9 -9
  22. package/dist/backend/actions/navigation.js +5 -5
  23. package/dist/backend/actions/visual.d.ts +47 -0
  24. package/dist/backend/actions/visual.d.ts.map +1 -0
  25. package/dist/backend/actions/visual.js +97 -0
  26. package/dist/backend/actions/waits.d.ts +6 -0
  27. package/dist/backend/actions/waits.d.ts.map +1 -1
  28. package/dist/backend/actions/waits.js +18 -5
  29. package/dist/backend/api/assertions.js +3 -3
  30. package/dist/backend/api/mock.js +3 -3
  31. package/dist/backend/api/network.js +6 -6
  32. package/dist/backend/api/requests.js +4 -4
  33. package/dist/backend/assertions/document.d.ts +61 -0
  34. package/dist/backend/assertions/document.d.ts.map +1 -0
  35. package/dist/backend/assertions/document.js +166 -0
  36. package/dist/backend/assertions/elements.d.ts +163 -0
  37. package/dist/backend/assertions/elements.d.ts.map +1 -0
  38. package/dist/backend/assertions/elements.js +441 -0
  39. package/dist/backend/assertions/expectVisible.js +1 -1
  40. package/dist/backend/assertions/forms.d.ts +43 -0
  41. package/dist/backend/assertions/forms.d.ts.map +1 -0
  42. package/dist/backend/assertions/forms.js +126 -0
  43. package/dist/backend/assertions/index.d.ts +10 -0
  44. package/dist/backend/assertions/index.d.ts.map +1 -1
  45. package/dist/backend/assertions/index.js +10 -0
  46. package/dist/backend/assertions/pageState.js +4 -4
  47. package/dist/backend/assertions/storage.d.ts +67 -0
  48. package/dist/backend/assertions/storage.d.ts.map +1 -0
  49. package/dist/backend/assertions/storage.js +220 -0
  50. package/dist/backend/assertions/text.d.ts +103 -12
  51. package/dist/backend/assertions/text.d.ts.map +1 -1
  52. package/dist/backend/assertions/text.js +207 -28
  53. package/dist/backend/assertions/visibility.d.ts +18 -0
  54. package/dist/backend/assertions/visibility.d.ts.map +1 -1
  55. package/dist/backend/assertions/visibility.js +58 -12
  56. package/dist/backend/auth/index.js +2 -2
  57. package/dist/backend/db/steps.d.ts +2 -2
  58. package/dist/backend/db/steps.d.ts.map +1 -1
  59. package/dist/backend/db/steps.js +11 -11
  60. package/dist/backend/elements/alerts.js +3 -3
  61. package/dist/backend/elements/find.js +26 -24
  62. package/dist/backend/elements/forms.js +4 -4
  63. package/dist/backend/elements/frames.js +3 -3
  64. package/dist/backend/utils/fixtures.js +1 -1
  65. package/dist/backend/utils/resolver.d.ts +6 -0
  66. package/dist/backend/utils/resolver.d.ts.map +1 -0
  67. package/dist/backend/utils/resolver.js +19 -0
  68. package/dist/component/index.d.ts +3 -0
  69. package/dist/component/index.d.ts.map +1 -0
  70. package/dist/component/index.js +6 -0
  71. package/dist/component/runner.d.ts +18 -0
  72. package/dist/component/runner.d.ts.map +1 -0
  73. package/dist/component/runner.js +91 -0
  74. package/dist/core/runner.d.ts +1 -0
  75. package/dist/core/runner.d.ts.map +1 -1
  76. package/dist/core/runner.js +179 -124
  77. package/dist/index.d.ts +2 -0
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +3 -1
  80. package/dist/metadata.json +564 -172
  81. package/package.json +4 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/component/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,YAAY,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runComponentTests = void 0;
4
+ // src/component/index.ts
5
+ var runner_1 = require("./runner");
6
+ Object.defineProperty(exports, "runComponentTests", { enumerable: true, get: function () { return runner_1.runComponentTests; } });
@@ -0,0 +1,18 @@
1
+ import "../backend/actions/index";
2
+ import "../backend/assertions/index";
3
+ import "../backend/elements/index";
4
+ import "../backend/api/index";
5
+ import "../backend/auth/index";
6
+ import "../backend/utils/state";
7
+ import "../backend/db/index";
8
+ export interface ComponentRunnerOptions {
9
+ tags?: string;
10
+ mountDir?: string;
11
+ componentGlob?: string;
12
+ }
13
+ /**
14
+ * Runs component tests using Playwright's component testing capabilities
15
+ * This allows testing individual UI components in isolation
16
+ */
17
+ export declare function runComponentTests(entryPoint: string, options?: ComponentRunnerOptions): Promise<void>;
18
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/component/runner.ts"],"names":[],"mappings":"AAMA,OAAO,0BAA0B,CAAC;AAClC,OAAO,6BAA6B,CAAC;AACrC,OAAO,2BAA2B,CAAC;AACnC,OAAO,sBAAsB,CAAC;AAC9B,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,qBAAqB,CAAC;AAE7B,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,iBAmC3F"}
@@ -0,0 +1,91 @@
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
+ exports.runComponentTests = runComponentTests;
37
+ // src/component/runner.ts
38
+ const test_1 = require("@playwright/test");
39
+ const glob_1 = require("glob");
40
+ const registry_1 = require("../core/registry");
41
+ // LOAD BACKEND LIBRARIES for component testing
42
+ require("../backend/actions/index");
43
+ require("../backend/assertions/index");
44
+ require("../backend/elements/index");
45
+ require("../backend/api/index");
46
+ require("../backend/auth/index");
47
+ require("../backend/utils/state");
48
+ require("../backend/db/index");
49
+ /**
50
+ * Runs component tests using Playwright's component testing capabilities
51
+ * This allows testing individual UI components in isolation
52
+ */
53
+ async function runComponentTests(entryPoint, options) {
54
+ const files = (0, glob_1.globSync)(options?.componentGlob || entryPoint);
55
+ if (files.length === 0) {
56
+ console.log(`āš ļø No Component test files found for: ${entryPoint}`);
57
+ }
58
+ for (const file of files) {
59
+ // Import the component test file to register any component-specific steps
60
+ await Promise.resolve(`${file}`).then(s => __importStar(require(s)));
61
+ // Run tests using Playwright's component testing setup
62
+ test_1.test.describe(`Component: ${file}`, () => {
63
+ test_1.test.beforeEach(async ({ page: _page }) => {
64
+ // Component testing setup would happen here
65
+ // In actual component testing, we'd mount components directly
66
+ console.log(`šŸ”§ Preparing component test environment for: ${file}`);
67
+ });
68
+ // Iterate through registered steps and create component-specific tests
69
+ for (const step of registry_1.stepRegistry) {
70
+ // Only run steps that are relevant to component testing
71
+ if (isComponentRelevantStep(step)) {
72
+ (0, test_1.test)(`Component step: ${typeof step.pattern === 'string' ? step.pattern : step.pattern.source}`, async ({ page: _page }) => {
73
+ // In component testing context, we might have different parameters
74
+ // For now, we'll just log that this would be a component test
75
+ console.log(`🧪 Running component test for step: ${typeof step.pattern === 'string' ? step.pattern : step.pattern.source}`);
76
+ // Here we would execute the step function with component-specific context
77
+ // await step.fn({ page }, ...args);
78
+ });
79
+ }
80
+ }
81
+ });
82
+ }
83
+ }
84
+ /**
85
+ * Determines if a step is relevant for component testing
86
+ */
87
+ function isComponentRelevantStep(_step) {
88
+ // For now, consider all steps potentially relevant for component testing
89
+ // In the future, we could have more specific logic
90
+ return true;
91
+ }
@@ -3,6 +3,7 @@ import "../backend/assertions/index";
3
3
  import "../backend/elements/index";
4
4
  import "../backend/api/index";
5
5
  import "../backend/auth/index";
6
+ import "../backend/utils/state";
6
7
  import "../backend/db/index";
7
8
  export interface RunnerOptions {
8
9
  tags?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"AAOA,OAAO,0BAA0B,CAAC;AAClC,OAAO,6BAA6B,CAAC;AACrC,OAAO,2BAA2B,CAAC;AACnC,OAAO,sBAAsB,CAAC;AAC9B,OAAO,uBAAuB,CAAC;AAE/B,OAAO,qBAAqB,CAAC;AAG7B,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC3C;AAQD,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,QA0NpE"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/core/runner.ts"],"names":[],"mappings":"AAOA,OAAO,0BAA0B,CAAC;AAClC,OAAO,6BAA6B,CAAC;AACrC,OAAO,2BAA2B,CAAC;AACnC,OAAO,sBAAsB,CAAC;AAC9B,OAAO,uBAAuB,CAAC;AAC/B,OAAO,wBAAwB,CAAC;AAChC,OAAO,qBAAqB,CAAC;AAG7B,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;CAC3C;AA8FD,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,QA8LpE"}
@@ -45,24 +45,82 @@ require("../backend/assertions/index");
45
45
  require("../backend/elements/index");
46
46
  require("../backend/api/index");
47
47
  require("../backend/auth/index");
48
+ require("../backend/utils/state");
48
49
  require("../backend/db/index");
49
- function runTests(featureGlob, options) {
50
- // Filter for the specific step we're looking for
51
- const findLabelSteps = registry_1.stepRegistry.filter(step => step.pattern && step.pattern.toString().includes('label text'));
52
- console.log(`šŸ“‹ FOUND ${findLabelSteps.length} steps containing "label text":`);
53
- findLabelSteps.forEach((step, index) => {
54
- let patternStr;
55
- if (step.expression instanceof RegExp) {
56
- patternStr = `REGEX: ${step.expression.source}`;
50
+ /**
51
+ * Parses a block of Gherkin lines into structured steps.
52
+ */
53
+ function parseSteps(block) {
54
+ const rawLines = block.split("\n");
55
+ const steps = [];
56
+ let currentStep = null;
57
+ let docStringBuffer = [];
58
+ let isDocStringOpen = false;
59
+ for (const line of rawLines) {
60
+ const trimmedLine = line.trim();
61
+ // A. Handle DocStrings (""")
62
+ if (trimmedLine.startsWith('"""')) {
63
+ isDocStringOpen = !isDocStringOpen;
64
+ if (!isDocStringOpen && currentStep) {
65
+ currentStep.docString = docStringBuffer.join("\n");
66
+ docStringBuffer = [];
67
+ }
68
+ continue;
69
+ }
70
+ // B. Inside DocString? Capture raw line (preserve indent)
71
+ if (isDocStringOpen) {
72
+ docStringBuffer.push(line);
73
+ continue;
57
74
  }
58
- else if (typeof step.pattern === 'string') {
59
- patternStr = `CUCUMBER: ${step.pattern}`;
75
+ // C. Skip Empty Lines, Comments, Tags, and section headers
76
+ if (!trimmedLine ||
77
+ trimmedLine.startsWith("#") ||
78
+ (/^@/.test(trimmedLine) && !/^@[^@]/.test(line)) || // Skip lines that start with @ (but not inline tags like "And I expect #element @tag")
79
+ /^(Background|Scenario|Scenario Outline|Feature):/.test(trimmedLine)) {
80
+ continue;
60
81
  }
61
- else {
62
- patternStr = `UNKNOWN: ${step.pattern}`;
82
+ // D. Handle Data Tables (| col | col |)
83
+ if (trimmedLine.startsWith("|")) {
84
+ if (currentStep) {
85
+ if (!currentStep.dataTable)
86
+ currentStep.dataTable = [];
87
+ const cleanRow = trimmedLine
88
+ .split("|")
89
+ .slice(1, -1)
90
+ .map((c) => c.trim());
91
+ currentStep.dataTable.push(cleanRow);
92
+ }
93
+ continue;
63
94
  }
64
- console.log(` ${index + 1}. ${patternStr}`);
65
- });
95
+ // E. It is a New Step
96
+ const cleanText = trimmedLine
97
+ .replace(/^(Given|When|Then|And|But)\s+/i, "")
98
+ .replace(/:$/, "")
99
+ .trim();
100
+ currentStep = {
101
+ text: trimmedLine,
102
+ cleanText: cleanText,
103
+ };
104
+ steps.push(currentStep);
105
+ }
106
+ return steps;
107
+ }
108
+ /**
109
+ * Extracts the Background block text from a feature file content.
110
+ * Returns the raw text between "Background:" and the first Scenario.
111
+ */
112
+ function extractBackgroundBlock(content) {
113
+ const backgroundMatch = content.match(/Background:\s*[^\n]*/);
114
+ if (!backgroundMatch)
115
+ return null;
116
+ const startIndex = backgroundMatch.index + backgroundMatch[0].length;
117
+ const nextSection = content
118
+ .slice(startIndex)
119
+ .search(/(?:(?:@\S+\s*)*(?:Scenario|Scenario Outline):)/);
120
+ const blockEnd = nextSection === -1 ? content.length : startIndex + nextSection;
121
+ return content.slice(startIndex, blockEnd);
122
+ }
123
+ function runTests(featureGlob, options) {
66
124
  // Also show ALL steps if there are few
67
125
  if (registry_1.stepRegistry.length <= 10) {
68
126
  registry_1.stepRegistry.forEach((step, index) => {
@@ -86,21 +144,50 @@ function runTests(featureGlob, options) {
86
144
  }
87
145
  for (const file of files) {
88
146
  const content = fs.readFileSync(file, "utf8");
89
- // 1. CAPTURE FEATURE TAGS
90
- const featureTagMatch = content.match(/((?:@\S+\s*)+)Feature:/);
147
+ // 1. CAPTURE FEATURE TAGS (excluding comments)
148
+ // First, let's create a version of the content that excludes comment lines
149
+ // A comment line is one that starts with # followed by a space or end of line
150
+ const contentLines = content.split('\n');
151
+ const nonCommentLines = [];
152
+ for (const line of contentLines) {
153
+ const trimmedLine = line.trim();
154
+ // Skip lines that are comments (start with # followed by space or end of line)
155
+ if (trimmedLine.startsWith('#') && (trimmedLine.length === 1 || /\s/.test(trimmedLine[1]))) {
156
+ continue;
157
+ }
158
+ nonCommentLines.push(line);
159
+ }
160
+ const nonCommentContent = nonCommentLines.join('\n');
161
+ const featureTagMatch = nonCommentContent.match(/((?:@\S+\s*)+)Feature:/);
91
162
  const rawFeatureTags = featureTagMatch ? featureTagMatch[1] : "";
92
163
  const featureTags = rawFeatureTags.replace(/[\r\n]+/g, " ").trim();
164
+ // Check if the feature has @ignore tag and skip if it does
165
+ if (featureTags.includes('@ignore')) {
166
+ console.log(`ā­ļø Skipping feature due to @ignore tag`);
167
+ continue;
168
+ }
93
169
  const featureMatch = content.match(/Feature:\s*(.+)/);
94
170
  const featureName = featureMatch
95
171
  ? featureMatch[1].trim()
96
172
  : "Unnamed Feature";
97
173
  test_1.test.describe(featureName, () => {
98
- // 2. SCENARIO REGEX
99
- // Matches: Optional Tags -> (Scenario OR Scenario Outline) -> Name
100
- const scenarioRegex = /(?:((?:@\S+\s*)+))?(?:Scenario|Scenario Outline):\s*(.+)/g;
174
+ // 2. PARSE BACKGROUND (if present)
175
+ const backgroundBlock = extractBackgroundBlock(content);
176
+ const backgroundSteps = backgroundBlock
177
+ ? parseSteps(backgroundBlock)
178
+ : [];
179
+ // 3. SCENARIO REGEX
180
+ // For scenario tags, we need to be more careful about comments
181
+ // We'll process each scenario individually to check for comment lines
182
+ const _scenarioPattern = /(?:((?:@\S+\s*)+))?(?:Scenario|Scenario Outline):\s*(.+)/g;
101
183
  let match;
102
184
  let foundCount = 0;
103
- while ((match = scenarioRegex.exec(content)) !== null) {
185
+ // 3. SCENARIO REGEX
186
+ // Use the non-comment content for tag matching to avoid commented tags
187
+ const scenarioRegex = /(?:((?:@\S+\s*)+))?(?:Scenario|Scenario Outline):\s*(.+)/g;
188
+ // Reset regex index for each file
189
+ scenarioRegex.lastIndex = 0;
190
+ while ((match = scenarioRegex.exec(nonCommentContent)) !== null) {
104
191
  foundCount++;
105
192
  const rawScenarioTags = match[1] || "";
106
193
  const scenarioTags = rawScenarioTags.replace(/[\r\n]+/g, " ").trim();
@@ -110,7 +197,13 @@ function runTests(featureGlob, options) {
110
197
  const fullName = combinedTags
111
198
  ? `${scenarioName} ${combinedTags}`
112
199
  : scenarioName;
113
- // 4. ENV FILTERING
200
+ // CHECK FOR IGNORE TAG
201
+ // If the combined tags include @ignore, skip this scenario
202
+ if (combinedTags.includes('@ignore')) {
203
+ console.log(`ā­ļø Skipping scenario "${scenarioName}" due to @ignore tag`);
204
+ continue;
205
+ }
206
+ // ENV FILTERING
114
207
  const activeFilter = options?.tags || envTag;
115
208
  if (activeFilter) {
116
209
  const targetGroups = activeFilter.split(",").map((t) => t.trim());
@@ -121,110 +214,66 @@ function runTests(featureGlob, options) {
121
214
  if (!isMatch)
122
215
  continue;
123
216
  }
124
- // 5. EXTRACT SCENARIO BLOCK
125
- const startIndex = match.index + match[0].length;
126
- const nextMatchIndex = content
127
- .slice(startIndex)
128
- .search(/(?:Scenario|Scenario Outline):/);
129
- const blockEnd = nextMatchIndex === -1 ? content.length : startIndex + nextMatchIndex;
130
- const scenarioBlock = content.slice(startIndex, blockEnd);
131
- (0, test_1.test)(fullName, async ({ page }, testInfo) => {
132
- // ==================================================
133
- // PHASE 1: PARSE GHERKIN (Preserve Formatting)
134
- // ==================================================
135
- const rawLines = scenarioBlock.split("\n");
136
- const steps = [];
137
- let currentStep = null;
138
- let docStringBuffer = [];
139
- let isDocStringOpen = false;
140
- for (let line of rawLines) {
141
- const trimmedLine = line.trim();
142
- // A. Handle DocStrings (""")
143
- if (trimmedLine.startsWith('"""')) {
144
- isDocStringOpen = !isDocStringOpen;
145
- if (!isDocStringOpen && currentStep) {
146
- // Closing DocString: Save buffer to step
147
- currentStep.docString = docStringBuffer.join("\n");
148
- docStringBuffer = [];
149
- }
150
- continue;
151
- }
152
- // B. Inside DocString? Capture raw line (preserve indent)
153
- if (isDocStringOpen) {
154
- docStringBuffer.push(line); // Don't trim!
155
- continue;
156
- }
157
- // C. Skip Empty Lines & Comments
158
- if (!trimmedLine ||
159
- trimmedLine.startsWith("#") ||
160
- trimmedLine.startsWith("@")) {
161
- continue;
162
- }
163
- // D. Handle Data Tables (| col | col |)
164
- if (trimmedLine.startsWith("|")) {
165
- if (currentStep) {
166
- if (!currentStep.dataTable)
167
- currentStep.dataTable = [];
168
- const row = trimmedLine;
169
- const _row = trimmedLine.split("|").map((cell) => cell.trim())
170
- .filter((cell, index, arr) => index > 0 && index < arr.length - 1);
171
- // Simple split often leaves empty strings at start/end
172
- // Better split logic:
173
- const cleanRow = trimmedLine
174
- .split("|")
175
- .slice(1, -1) // Remove first and last empty splits from |...|
176
- .map((c) => c.trim());
177
- currentStep.dataTable.push(cleanRow);
217
+ // EXTRACT SCENARIO BLOCK from original content
218
+ // Find the corresponding scenario in the original content
219
+ const scenarioPattern = new RegExp(`(?:((?:@\\S+\\s*)+))?\\s*(?:Scenario|Scenario Outline):\\s*${escapeRegExp(scenarioName)}`, 'g');
220
+ const originalMatch = content.match(scenarioPattern);
221
+ if (originalMatch) {
222
+ // Find the exact position of this scenario in the original content
223
+ const scenarioStartPattern = `(?:((?:@\\S+\\s*)+))?\\s*(?:Scenario|Scenario Outline):\\s*${escapeRegExp(scenarioName)}`;
224
+ const _scenarioStartRegex = new RegExp(scenarioStartPattern);
225
+ const scenarioStartMatch = content.match(scenarioStartPattern);
226
+ if (scenarioStartMatch && scenarioStartMatch.index !== undefined) {
227
+ const startIndex = scenarioStartMatch.index + scenarioStartMatch[0].length;
228
+ const nextMatchIndex = content
229
+ .slice(startIndex)
230
+ .search(/(?:(?:@\S+\s*)*(?:Scenario|Scenario Outline):)/);
231
+ const blockEnd = nextMatchIndex === -1 ? content.length : startIndex + nextMatchIndex;
232
+ const scenarioBlock = content.slice(startIndex, blockEnd);
233
+ (0, test_1.test)(fullName, async ({ page }, testInfo) => {
234
+ // ==================================================
235
+ // PHASE 1: PARSE GHERKIN (Preserve Formatting)
236
+ // ==================================================
237
+ const scenarioSteps = parseSteps(scenarioBlock);
238
+ const steps = [...backgroundSteps, ...scenarioSteps];
239
+ // ==================================================
240
+ // PHASE 2: EXECUTE STEPS
241
+ // ==================================================
242
+ console.log(`\nšŸ”¹ Scenario: ${scenarioName}`);
243
+ for (const step of steps) {
244
+ const matchResult = findMatchingStep(step.cleanText);
245
+ if (!matchResult) {
246
+ throw new Error(`āŒ Undefined Step: "${step.cleanText}"`);
247
+ }
248
+ try {
249
+ console.log(` executing: ${step.text.trim()}`);
250
+ const args = [...matchResult.args];
251
+ // Append Data Table if present
252
+ if (step.dataTable && step.dataTable.length > 0) {
253
+ args.push(step.dataTable);
254
+ }
255
+ // Append DocString if present
256
+ if (step.docString) {
257
+ args.push(step.docString);
258
+ }
259
+ await matchResult.fn(page, ...args);
260
+ }
261
+ catch (error) {
262
+ console.error(`āŒ Failed at step: "${step.text.trim()}"`);
263
+ const screenshot = await page.screenshot({
264
+ fullPage: true,
265
+ type: "png",
266
+ });
267
+ await testInfo.attach("failure-screenshot", {
268
+ body: screenshot,
269
+ contentType: "image/png",
270
+ });
271
+ throw error;
272
+ }
178
273
  }
179
- continue;
180
- }
181
- // E. It is a New Step
182
- const cleanText = trimmedLine
183
- .replace(/^(Given|When|Then|And|But)\s+/i, "")
184
- .replace(/:$/, "") // Remove trailing colon
185
- .trim();
186
- currentStep = {
187
- text: trimmedLine,
188
- cleanText: cleanText,
189
- };
190
- steps.push(currentStep);
274
+ });
191
275
  }
192
- // ==================================================
193
- // PHASE 2: EXECUTE STEPS
194
- // ==================================================
195
- console.log(`\nšŸ”¹ Scenario: ${scenarioName}`);
196
- for (const step of steps) {
197
- const matchResult = findMatchingStep(step.cleanText);
198
- if (!matchResult) {
199
- throw new Error(`āŒ Undefined Step: "${step.cleanText}"`);
200
- }
201
- try {
202
- console.log(` executing: ${step.text.trim()}`);
203
- const args = [...matchResult.args];
204
- // Append Data Table if present
205
- if (step.dataTable && step.dataTable.length > 0) {
206
- args.push(step.dataTable);
207
- }
208
- // Append DocString if present
209
- if (step.docString) {
210
- args.push(step.docString);
211
- }
212
- await matchResult.fn(page, ...args);
213
- }
214
- catch (error) {
215
- console.error(`āŒ Failed at step: "${step.text.trim()}"`);
216
- const screenshot = await page.screenshot({
217
- fullPage: true,
218
- type: "png",
219
- });
220
- await testInfo.attach("failure-screenshot", {
221
- body: screenshot,
222
- contentType: "image/png",
223
- });
224
- throw error;
225
- }
226
- }
227
- });
276
+ }
228
277
  }
229
278
  if (foundCount === 0) {
230
279
  console.warn(`āš ļø File matched but 0 Scenarios found in: ${file}`);
@@ -232,6 +281,12 @@ function runTests(featureGlob, options) {
232
281
  });
233
282
  }
234
283
  }
284
+ /**
285
+ * Escapes special regex characters in a string
286
+ */
287
+ function escapeRegExp(string) {
288
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
289
+ }
235
290
  /**
236
291
  * Finds the matching step definition from the registry.
237
292
  * Supports: RegExp (with capture groups) and CucumberExpressions.
@@ -249,7 +304,7 @@ function findMatchingStep(text) {
249
304
  };
250
305
  }
251
306
  }
252
- catch (e) {
307
+ catch (_e) {
253
308
  // Continue to next step if Cucumber Expression fails
254
309
  continue;
255
310
  }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export { runTests } from "./core/runner";
2
2
  export { Step } from "./core/registry";
3
3
  export { getReporters } from "./reporting/index";
4
+ export { runComponentTests } from "./component/index";
4
5
  export type { RunnerOptions } from "./core/runner";
5
6
  export type { ReportOptions } from "./reporting/index";
6
7
  export type { StepAction } from "./core/registry";
8
+ export type { ComponentRunnerOptions } from "./component/index";
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,YAAY,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getReporters = exports.Step = exports.runTests = void 0;
3
+ exports.runComponentTests = exports.getReporters = exports.Step = exports.runTests = void 0;
4
4
  var runner_1 = require("./core/runner");
5
5
  Object.defineProperty(exports, "runTests", { enumerable: true, get: function () { return runner_1.runTests; } });
6
6
  var registry_1 = require("./core/registry");
7
7
  Object.defineProperty(exports, "Step", { enumerable: true, get: function () { return registry_1.Step; } });
8
8
  var index_1 = require("./reporting/index");
9
9
  Object.defineProperty(exports, "getReporters", { enumerable: true, get: function () { return index_1.getReporters; } });
10
+ var index_2 = require("./component/index");
11
+ Object.defineProperty(exports, "runComponentTests", { enumerable: true, get: function () { return index_2.runComponentTests; } });