@cementic/cementic-test 0.2.15 → 0.2.16

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/CHANGELOG.md ADDED
@@ -0,0 +1,84 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@cementic/cementic-test` are documented here.
4
+
5
+ ## v0.2.16
6
+
7
+ - fixed `ct gen` so unquoted and backticked step values such as ```${CT_VAR_EMAIL}``` now map to runnable `.fill()` and `.selectOption()` calls instead of falling back to `TODO` comments
8
+ - fixed generated auth button clicks so generic phrases like `Click the login button` use resilient role-name regex matching when an exact accessible name is unavailable
9
+ - added regression coverage for unquoted `CT_VAR_*` auth steps to keep generated login specs runnable
10
+ - hardened the publish workflow so release tags must match `package.json`, and publishing aborts if the npm version already exists
11
+ - updated package metadata, capture artifact metadata, and capture user agent strings to `0.2.16`
12
+
13
+ ## v0.2.15
14
+
15
+ - fixed capture semantics so `<a href="...">` elements are classified as links unless they explicitly expose `role="button"`
16
+ - made CTA-like presence selectors resilient by generating `Locator.or(...)` fallbacks across button and link roles when the intent is ambiguous
17
+ - tightened intent label cleanup so trailing filler phrases like `on the page` do not leak into generated locator text
18
+ - updated capture artifact metadata and capture user agent version strings to `0.2.15`
19
+
20
+ ## v0.2.14
21
+
22
+ - hardened the release gate so `npm test` now includes the prompt regression suite and `prepublishOnly` runs the full test gate
23
+ - fixed the prompt test harness so it executes the local `dist/cli.js` build instead of depending on whichever global `ct` happens to be on `PATH`
24
+ - added deterministic capture-analysis fallback for `ct tc url --ai` when no LLM key is configured or remote analysis fails
25
+ - hardened auth, navigation, negative-state, heading, and count scenario fallback behavior so capture-driven generation still produces intent-aligned Playwright output offline
26
+ - added smoke coverage for `ct new`, `ct ci`, `ct flow --no-run`, `ct report`, and both `ct serve` execution paths
27
+ - replaced the old browser bootstrap heuristic in `ct new` with explicit browser install profiles: `auto`, `all`, and `chromium`
28
+ - kept the compatibility downgrade conditional to affected older macOS versions only, while preserving the normal Playwright install path for newer machines
29
+ - changed `ct new --mode` handling to reject unsupported scaffold modes explicitly instead of silently behaving like `greenfield`
30
+
31
+ ## v0.2.13
32
+
33
+ - replaced the old Rule 10 prompt guidance with a Playwright knowledge base aligned to official Playwright docs and web-first assertion patterns
34
+ - aligned locator priority with Playwright best practices: `getByRole -> getByLabel -> getByPlaceholder -> getByText -> locator`
35
+ - made counting intents generate `toHaveCount()` or `.count()` instead of intent-text locators
36
+ - enforced that intent wording is never reused as locator text
37
+ - made negative assertions generate `.not.` or `toBeHidden()` patterns
38
+ - made error assertions prefer `getByRole('alert')` and heading assertions prefer `getByRole('heading')`
39
+ - added on-demand Playwright doc fetching for advanced categories such as uploads, mocking, accessibility, viewports, auth, downloads, dialogs, frames, and popups
40
+ - added the 12-check prompt regression suite in `test/prompt.spec.mjs`
41
+
42
+ ## v0.2.12
43
+
44
+ - made both AI generation paths intent-first so markdown and capture-based scenario generation follow the same scope and presence-only rules
45
+ - tightened capture-based scenario generation to preserve presence-only checks as visibility assertions, honor intent-based selector fallback, and avoid generic `'value'` placeholders
46
+ - added regression coverage for the new markdown prompt contract and capture-path intent enforcement
47
+ - breaking change: none
48
+
49
+ ## v0.2.11
50
+
51
+ - AI no longer writes `'value'` as a step input and now prefers matching `CT_VAR_*` references or `test-{fieldname}` fallbacks
52
+ - fixed `ct gen` so generic fill steps read matching `CT_VAR_*` values from the environment and emit env-backed constants only when they are actually needed
53
+ - fixed generated `toHaveValue(...)` assertions so they reuse the same emitted fill value for selector-backed form fields
54
+ - added a generated spec header comment that lists required `CT_VAR_*` variables with inline usage examples
55
+ - breaking change: none
56
+
57
+ ## v0.2.10
58
+
59
+ - fixed `ct gen` so selector-hinted fields like `#username` and `#password` infer matching `CT_VAR_*` constants instead of leaving `.fill('value')` placeholders behind
60
+ - fixed generated `toHaveValue(...)` assertions so they reuse the same `CT_VAR_*` binding when the related fill step was variablized
61
+ - added regression coverage for selector-driven auth generation to keep fill calls and value assertions aligned
62
+
63
+ ## v0.2.9
64
+
65
+ - added `CT_VAR_*` extraction in `ct gen` for input and select test data so generated specs use env-backed constants instead of hardcoded values
66
+ - added `ct gen --vars '{...}'` to override generated fallback values without editing the emitted spec files by hand
67
+ - added regression coverage for variableized generated specs, selector-hint preservation, and select-option extraction
68
+
69
+ ## v0.2.8
70
+
71
+ - fixed TypeScript generator identifier sanitization so scenario titles containing em dashes and other non-identifier characters no longer break generated POM class names or spec imports
72
+ - added regression coverage for generated TypeScript output from scenario titles like `Login-002 — User can enter credentials — before submission`
73
+
74
+ ## v0.2.7
75
+
76
+ - fixed project-first Playwright resolution for `ct tc url --ai`, including global and `npx` CLI usage
77
+ - added clearer capture failure categories and setup guidance for missing Playwright, missing browsers, and page-load failures
78
+ - centralized AI provider detection so `tc --ai` and capture analysis recognize the same providers and env vars
79
+ - fixed missing DeepSeek guidance in the CLI help path
80
+ - changed legacy macOS browser setup to install Chromium only by default and avoid WebKit unsupported failures
81
+
82
+ ## v0.2.6
83
+
84
+ - added an install-time banner with links to the website and community
package/README.md CHANGED
@@ -206,6 +206,8 @@ Before publishing, run the full local gate:
206
206
  npm test
207
207
  ```
208
208
 
209
+ Do not reuse an npm version. npm will reject republishing an existing version even if the Git tag was moved later.
210
+
209
211
  If you want to inspect the prompt regression suite directly:
210
212
 
211
213
  ```bash
@@ -216,12 +218,14 @@ node --test --test-name-pattern="counting intent" test/prompt.spec.mjs
216
218
 
217
219
  Note: on current Node test runner versions, use `--test-reporter=spec`, not `--reporter=spec`.
218
220
 
219
- ### v0.2.15 acceptance criteria
221
+ ### v0.2.16 acceptance criteria
220
222
 
221
- All of the following must be true before publishing `v0.2.15`:
223
+ All of the following must be true before publishing `v0.2.16`:
222
224
 
223
225
  - `npm test` passes from a clean checkout and includes both the integration suite and `test/prompt.spec.mjs`
224
226
  - `prepublishOnly` runs the same full release gate, not just `build`
227
+ - the release tag matches `package.json`
228
+ - the target npm version is not already published
225
229
  - prompt regression suite verifies:
226
230
  - counting intents generate `toHaveCount()` or `.count()`
227
231
  - intent text is never reused as `getByText(...)`
@@ -246,8 +250,10 @@ All of the following must be true before publishing `v0.2.15`:
246
250
  - review flags and review reasons
247
251
  - `ct gen` guarantees:
248
252
  - no `.fill('value')` placeholders
253
+ - no `TODO` comments for recognized fill/select auth steps using quoted, unquoted, or backticked `CT_VAR_*` values
249
254
  - env-backed `CT_VAR_*` extraction when matching vars are available
250
255
  - field-specific fallback values when env vars are not available
256
+ - generic auth button click steps fall back to resilient role-name matching when exact labels are missing
251
257
  - existing POM files are not overwritten
252
258
  - generated specs compile as valid Playwright TypeScript or JavaScript
253
259
  - command smoke coverage passes for:
@@ -258,7 +264,7 @@ All of the following must be true before publishing `v0.2.15`:
258
264
  - unsupported `ct new --mode` values failing explicitly
259
265
  - `ct ci`
260
266
  - `ct flow --no-run`
261
- - README, package version, and documented changelog entries match the release being cut
267
+ - README, package version, runtime metadata strings, and changelog entries match the release being cut
262
268
 
263
269
  ---
264
270
 
@@ -451,6 +457,14 @@ And start automating.
451
457
 
452
458
  ## Changelog
453
459
 
460
+ See [CHANGELOG.md](./CHANGELOG.md) for the full release history.
461
+
462
+ ### v0.2.16
463
+
464
+ - fixed `ct gen` so unquoted and backticked `CT_VAR_*` fill steps now generate runnable `.fill()` calls instead of `TODO` comments
465
+ - made generated auth button clicks more resilient for generic phrases like `Click the login button`
466
+ - added release guards so publish only proceeds when the Git tag matches `package.json` and the npm version has not already been published
467
+
454
468
  ### v0.2.15
455
469
 
456
470
  - fixed capture semantics so `<a href="...">` elements are classified as links unless they explicitly expose `role="button"`
package/dist/capture.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  formatCaptureFailure,
7
7
  inferInteractiveRole,
8
8
  toPageSummary
9
- } from "./chunk-XUWFEWJZ.js";
9
+ } from "./chunk-WUGSOKKY.js";
10
10
  export {
11
11
  CaptureRuntimeError,
12
12
  captureElements,
@@ -92,6 +92,16 @@ function parseQuotedLiteral(value) {
92
92
  }
93
93
  return void 0;
94
94
  }
95
+ function unwrapStepValue(value) {
96
+ if (!value) return void 0;
97
+ const trimmed = value.trim();
98
+ if (!trimmed) return void 0;
99
+ const quoted = parseQuotedLiteral(trimmed);
100
+ if (quoted !== void 0) return quoted;
101
+ const inlineCode = trimmed.match(/^`([^`]+)`$/);
102
+ if (inlineCode) return inlineCode[1].trim();
103
+ return trimmed;
104
+ }
95
105
  function selectorTargetToEnvSuffix(value) {
96
106
  const selector = normalizeSelectorTarget(value);
97
107
  if (!selector) return void 0;
@@ -118,7 +128,7 @@ function isGenericFallbackValue(value) {
118
128
  return /^(?:value|text|input|option|selection|selected value|default)$/i.test(value.trim());
119
129
  }
120
130
  function extractCtVarTemplate(value) {
121
- const match = value?.trim().match(/^\$\{(CT_VAR_[A-Z0-9_]+)\}$/);
131
+ const match = unwrapStepValue(value)?.match(/^\$\{(CT_VAR_[A-Z0-9_]+)\}$/);
122
132
  return match?.[1];
123
133
  }
124
134
  function placeholderTokenFromSuffix(value) {
@@ -151,7 +161,16 @@ function parseStepBinding(step) {
151
161
  kind: "fill",
152
162
  rawField: fillMatch[1].trim(),
153
163
  fieldLabel: cleanFieldLabel(fillMatch[1]),
154
- value: fillMatch[3].trim()
164
+ value: unwrapStepValue(fillMatch[3])
165
+ };
166
+ }
167
+ const fillLooseMatch = s.match(/\b(?:fill|input|write)\s+(?:in\s+)?(.+?)\s+with\s+(.+)$/i);
168
+ if (fillLooseMatch) {
169
+ return {
170
+ kind: "fill",
171
+ rawField: fillLooseMatch[1].trim(),
172
+ fieldLabel: cleanFieldLabel(fillLooseMatch[1]),
173
+ value: unwrapStepValue(fillLooseMatch[2])
155
174
  };
156
175
  }
157
176
  const fillWithoutValueMatch = s.match(/\b(?:fill|input|write)\s+(?:in\s+)?(.+?)$/i);
@@ -168,7 +187,16 @@ function parseStepBinding(step) {
168
187
  kind: "fill",
169
188
  rawField: enterMatch[3].trim(),
170
189
  fieldLabel: cleanFieldLabel(enterMatch[3]),
171
- value: enterMatch[2].trim()
190
+ value: unwrapStepValue(enterMatch[2])
191
+ };
192
+ }
193
+ const enterLooseMatch = s.match(/\b(?:enter|type)\s+(.+?)\s+(?:in|into)\s+(.+)$/i);
194
+ if (enterLooseMatch) {
195
+ return {
196
+ kind: "fill",
197
+ rawField: enterLooseMatch[2].trim(),
198
+ fieldLabel: cleanFieldLabel(enterLooseMatch[2]),
199
+ value: unwrapStepValue(enterLooseMatch[1])
172
200
  };
173
201
  }
174
202
  const enterWithoutValueMatch = s.match(/\b(?:enter|type)\s+(?:in|into)\s+(.+)/i);
@@ -185,7 +213,16 @@ function parseStepBinding(step) {
185
213
  kind: "select",
186
214
  rawField: selectMatch[3].trim(),
187
215
  fieldLabel: cleanFieldLabel(selectMatch[3]),
188
- value: selectMatch[2].trim()
216
+ value: unwrapStepValue(selectMatch[2])
217
+ };
218
+ }
219
+ const selectLooseMatch = s.match(/\b(?:select|choose|pick)\s+(.+?)\s+(?:from|in|into|on)\s+(.+)$/i);
220
+ if (selectLooseMatch) {
221
+ return {
222
+ kind: "select",
223
+ rawField: selectLooseMatch[2].trim(),
224
+ fieldLabel: cleanFieldLabel(selectLooseMatch[2]),
225
+ value: unwrapStepValue(selectLooseMatch[1])
189
226
  };
190
227
  }
191
228
  const selectWithoutValueMatch = s.match(/\b(?:select|choose|pick)\s+(.+?)\s+(?:dropdown|combobox|select|menu)\b/i);
@@ -236,7 +273,8 @@ function extractStepVars(steps, stepHints, assertionHints, overrides) {
236
273
  const shouldDeclareVariable = binding.kind === "select" || templateEnvKey !== void 0 || overrideValue !== void 0 || hasRuntimeEnvValue;
237
274
  let stepVar = shouldDeclareVariable ? seen.get(envKey) : void 0;
238
275
  if (shouldDeclareVariable && !stepVar) {
239
- const fallback = overrideValue ?? process.env[envKey] ?? ((!fallbackFromBinding || isGenericFallbackValue(fallbackFromBinding) || templateEnvKey) && assertionFallback ? assertionFallback : fallbackFromBinding) ?? (binding.kind === "fill" ? buildFillPlaceholder(binding.rawField, binding.fieldLabel, selectorTarget) : assertionFallback ?? "option");
276
+ const placeholderFallback = binding.kind === "fill" ? buildFillPlaceholder(binding.rawField, binding.fieldLabel, selectorTarget) : assertionFallback ?? "option";
277
+ const fallback = overrideValue ?? process.env[envKey] ?? (templateEnvKey ? assertionFallback ?? placeholderFallback : (!fallbackFromBinding || isGenericFallbackValue(fallbackFromBinding)) && assertionFallback ? assertionFallback : fallbackFromBinding) ?? placeholderFallback;
240
278
  stepVar = {
241
279
  envKey,
242
280
  constName: envKey,
@@ -306,6 +344,33 @@ function ensureStatement(value) {
306
344
  if (!trimmed) return trimmed;
307
345
  return trimmed.endsWith(";") ? trimmed : `${trimmed};`;
308
346
  }
347
+ function phraseToRegexPattern(value) {
348
+ return value.trim().split(/\s+/).filter(Boolean).map((token) => escapeForRegex(token)).join("\\s+");
349
+ }
350
+ function buttonNameExpressionFromStep(step) {
351
+ const quotedMatch = step.match(/["']([^"']+)["']/);
352
+ if (quotedMatch) return `'${escapeForSingleQuotedString(quotedMatch[1])}'`;
353
+ const label = step.replace(/^(click|press|tap)\s+(?:on\s+)?(?:the\s+)?/i, "").replace(/\b(button|btn)\b/gi, "").replace(/[.?!,:;]+$/g, "").trim();
354
+ if (!label) return `'button'`;
355
+ const lowered = label.toLowerCase();
356
+ const variants = /* @__PURE__ */ new Set();
357
+ if (/\b(?:login|log in|sign in|sign-in)\b/.test(lowered)) {
358
+ variants.add("login");
359
+ variants.add("log in");
360
+ variants.add("sign in");
361
+ } else if (/\b(?:register|sign up|sign-up|signup|create account)\b/.test(lowered)) {
362
+ variants.add("register");
363
+ variants.add("sign up");
364
+ variants.add("create account");
365
+ } else if (/\bcontinue\b/.test(lowered)) {
366
+ variants.add("continue");
367
+ variants.add("next");
368
+ } else {
369
+ variants.add(label);
370
+ }
371
+ const pattern = Array.from(variants).map(phraseToRegexPattern).join("|");
372
+ return `/${pattern}/i`;
373
+ }
309
374
  function parseValueAssertionHint(playwright) {
310
375
  const statement = ensureStatement(String(playwright ?? "").trim());
311
376
  if (!statement) return void 0;
@@ -350,9 +415,7 @@ function stepToPlaywright(step, url, hint, stepVar) {
350
415
  }
351
416
  if (/\bclick\b.*(button|btn|submit|sign in|log in|login|register|continue|next|save|confirm)/i.test(s)) {
352
417
  if (hintedSelector) return `await ${hintedSelector}.click();`;
353
- const nameMatch = s.match(/["']([^"']+)["']/);
354
- const name = nameMatch?.[1] ?? s.replace(/click\s+(the\s+)?/i, "").trim();
355
- return `await page.getByRole('button', { name: '${escapeForSingleQuotedString(name)}' }).click();`;
418
+ return `await page.getByRole('button', { name: ${buttonNameExpressionFromStep(s)} }).click();`;
356
419
  }
357
420
  if (/\bclick\b.*(link|anchor|nav)/i.test(s)) {
358
421
  if (hintedSelector) return `await ${hintedSelector}.click();`;
@@ -666,4 +729,4 @@ export {
666
729
  gen,
667
730
  genCmd
668
731
  };
669
- //# sourceMappingURL=chunk-3S26OWNR.js.map
732
+ //# sourceMappingURL=chunk-PYKYHIO3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/gen.ts"],"sourcesContent":["import { Command } from 'commander';\nimport fg from 'fast-glob';\nimport { readFileSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';\nimport { join, basename, relative, resolve } from 'node:path';\n\ntype NormalizedCase = {\n id?: string;\n title: string;\n tags?: string[];\n steps?: string[];\n step_hints?: Array<{ selector?: string }>;\n expected?: string[];\n assertion_hints?: Array<{ playwright?: string }>;\n needs_review?: boolean;\n review_reasons?: string[];\n source?: string;\n url?: string;\n};\n\ntype VarsOverrideMap = Map<string, string>;\n\ntype StepBinding = {\n kind: 'fill' | 'select';\n rawField?: string;\n fieldLabel?: string;\n value?: string;\n};\n\ntype StepVar = {\n envKey: string;\n constName: string;\n fallback: string;\n selectorHint?: string;\n selectorTarget?: string;\n fieldLabel?: string;\n};\n\ntype StepValueState = {\n declarations: StepVar[];\n byStepIndex: Map<number, StepVar>;\n fillValueBySelector: Map<string, string>;\n};\n\nfunction escapeForSingleQuotedString(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\");\n}\n\nfunction escapeForRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\\\/]/g, '\\\\$&');\n}\n\nfunction normalizeUrlForComparison(value?: string): string | undefined {\n if (!value) return undefined;\n try {\n return new URL(value).toString().replace(/\\/$/, '');\n } catch {\n return value.trim().replace(/\\/$/, '');\n }\n}\n\nfunction urlPatternFromAbsoluteUrl(value?: string): string | undefined {\n if (!value) return undefined;\n try {\n const parsed = new URL(value);\n const normalizedPath = `${parsed.pathname}${parsed.search}`.replace(/\\/$/, '') || '/';\n if (normalizedPath === '/') return '\\\\/';\n return normalizedPath.replace(/^\\/+/, '').split('/').map(escapeForRegex).join('\\\\/');\n } catch {\n return undefined;\n }\n}\n\nfunction pageNameToUrlPattern(value: string): string | undefined {\n const cleaned = value\n .replace(/\\b(the|a|an|user)\\b/gi, ' ')\n .replace(/\\b(page|screen)\\b/gi, ' ')\n .trim();\n const tokens = cleaned.split(/[\\s/-]+/).map(token => token.trim()).filter(Boolean);\n if (tokens.length === 0) return undefined;\n return tokens.map(token => escapeForRegex(token.toLowerCase())).join('[-_\\\\/]?');\n}\n\nfunction fieldNameFromSentence(value: string): string | undefined {\n const match =\n value.match(/\\bfor\\s+([a-zA-Z][a-zA-Z\\s-]{0,30}?)\\s+field\\b/i) ??\n value.match(/\\b([a-zA-Z][a-zA-Z\\s-]{0,30}?)\\s+field\\b/i) ??\n value.match(/\\b([a-zA-Z][a-zA-Z\\s-]{0,30}?)\\s+input\\b/i);\n return match?.[1]?.trim();\n}\n\nfunction visibleTextRegexFromPhrase(value: string): string {\n const cleaned = value\n .replace(/[\"']/g, '')\n .replace(/\\b(the|a|an|user|should|must|is|are|be|visible|shown|showing|displayed|present|appears?|rendered)\\b/gi, ' ')\n .replace(/\\b(message|text|content|heading|label)\\b/gi, ' ')\n .trim();\n const tokens = cleaned.split(/\\s+/).filter(Boolean);\n if (tokens.length === 0) return '.+';\n return tokens.map(token => escapeForRegex(token)).join('\\\\s+');\n}\n\nfunction sanitizeEnvSegment(value: string): string {\n return value\n .toUpperCase()\n .replace(/[^A-Z0-9]+/g, '_')\n .replace(/_+/g, '_')\n .replace(/^_|_$/g, '');\n}\n\nfunction normalizeOverrideKey(value: string): string {\n return sanitizeEnvSegment(value.replace(/^CT_VAR_/i, ''));\n}\n\nfunction cleanFieldLabel(value?: string): string | undefined {\n if (!value) return undefined;\n const cleaned = value\n .replace(/[.?!,:;]+$/g, '')\n .replace(/^(?:the|a|an)\\s+/i, '')\n .replace(/\\s+(?:field|input|box|area|dropdown|combobox|select|menu)\\b/gi, '')\n .trim();\n return cleaned || undefined;\n}\n\nfunction isPositionalFieldReference(value?: string): boolean {\n if (!value) return false;\n const cleaned = value.trim().toLowerCase();\n return /^(?:(?:the|a|an)\\s+)?(?:first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|\\d+(?:st|nd|rd|th)?)(?:\\s+\\w+){0,2}\\s+(?:field|input|box|area|dropdown|combobox|select|menu)$/.test(cleaned);\n}\n\nfunction fieldNameToEnvSuffix(value?: string): string | undefined {\n if (!value || isPositionalFieldReference(value)) return undefined;\n const cleaned = value\n .replace(/[.?!,:;]+$/g, '')\n .replace(/^(?:the|a|an)\\s+/i, '')\n .replace(/\\b(?:field|input|box|area|dropdown|combobox|select|menu)\\b/gi, ' ')\n .trim();\n const suffix = sanitizeEnvSegment(cleaned);\n if (!suffix) return undefined;\n if (/^(?:FIELD|INPUT|BOX|AREA|DROPDOWN|COMBOBOX|SELECT|MENU)$/.test(suffix)) return undefined;\n return suffix;\n}\n\nfunction normalizeSelectorTarget(value?: string): string | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n return trimmed.replace(/^page\\./, '').replace(/\\s+/g, ' ');\n}\n\nfunction parseQuotedLiteral(value?: string): string | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (trimmed.length < 2) return undefined;\n\n if (trimmed.startsWith('\"') && trimmed.endsWith('\"')) {\n try {\n return JSON.parse(trimmed);\n } catch {\n return trimmed.slice(1, -1);\n }\n }\n\n if (trimmed.startsWith(\"'\") && trimmed.endsWith(\"'\")) {\n return trimmed\n .slice(1, -1)\n .replace(/\\\\\\\\/g, '\\\\')\n .replace(/\\\\'/g, \"'\");\n }\n\n return undefined;\n}\n\nfunction unwrapStepValue(value?: string): string | undefined {\n if (!value) return undefined;\n const trimmed = value.trim();\n if (!trimmed) return undefined;\n\n const quoted = parseQuotedLiteral(trimmed);\n if (quoted !== undefined) return quoted;\n\n const inlineCode = trimmed.match(/^`([^`]+)`$/);\n if (inlineCode) return inlineCode[1].trim();\n\n return trimmed;\n}\n\nfunction selectorTargetToEnvSuffix(value?: string): string | undefined {\n const selector = normalizeSelectorTarget(value);\n if (!selector) return undefined;\n\n const quotedArg = (pattern: RegExp, group = 2): string | undefined => {\n const match = selector.match(pattern);\n return match?.[group]?.trim();\n };\n\n const locatorArg = quotedArg(/^(?:locator)\\((['\"])(.*?)\\1\\)/i);\n if (locatorArg) {\n const idMatch = locatorArg.match(/#([A-Za-z][\\w-]*)/);\n if (idMatch) return sanitizeEnvSegment(idMatch[1]);\n\n const attrMatch = locatorArg.match(/\\[(?:name|id|data-testid|aria-label)=['\"]?([^'\"\\]]+)['\"]?\\]/i);\n if (attrMatch) return sanitizeEnvSegment(attrMatch[1]);\n\n const classMatch = locatorArg.match(/\\.([A-Za-z][\\w-]*)/);\n if (classMatch) return sanitizeEnvSegment(classMatch[1]);\n\n return sanitizeEnvSegment(locatorArg.replace(/^[#.]+/, ''));\n }\n\n const labelArg =\n quotedArg(/^(?:getByLabel|getByPlaceholder|getByTestId|getByText)\\((['\"])(.*?)\\1/i) ??\n quotedArg(/^getByRole\\((['\"])(?:textbox|searchbox|combobox|spinbutton)\\1,\\s*\\{\\s*name:\\s*(['\"])(.*?)\\2/i, 3);\n if (labelArg) return fieldNameToEnvSuffix(labelArg) ?? sanitizeEnvSegment(labelArg);\n\n return undefined;\n}\n\nfunction isGenericFallbackValue(value?: string): boolean {\n if (!value) return true;\n return /^(?:value|text|input|option|selection|selected value|default)$/i.test(value.trim());\n}\n\nfunction extractCtVarTemplate(value?: string): string | undefined {\n const match = unwrapStepValue(value)?.match(/^\\$\\{(CT_VAR_[A-Z0-9_]+)\\}$/);\n return match?.[1];\n}\n\nfunction placeholderTokenFromSuffix(value?: string): string {\n const cleaned = value?.trim().replace(/^CT_VAR_/, '');\n if (!cleaned) return 'input';\n return cleaned.toLowerCase().replace(/_+/g, '-');\n}\n\nfunction inferFieldToken(rawField?: string, fieldLabel?: string, selectorTarget?: string): string {\n const suffix =\n fieldNameToEnvSuffix(rawField) ??\n fieldNameToEnvSuffix(fieldLabel) ??\n selectorTargetToEnvSuffix(selectorTarget);\n return placeholderTokenFromSuffix(suffix);\n}\n\nfunction buildFillPlaceholder(\n rawField?: string,\n fieldLabel?: string,\n selectorTarget?: string,\n): string {\n return `test-${inferFieldToken(rawField, fieldLabel, selectorTarget)}`;\n}\n\nfunction resolveLiteralFillValue(binding: StepBinding, selectorTarget?: string): string {\n const rawValue = binding.value?.trim();\n if (rawValue && !isGenericFallbackValue(rawValue) && !extractCtVarTemplate(rawValue)) {\n return rawValue;\n }\n return buildFillPlaceholder(binding.rawField, binding.fieldLabel, selectorTarget);\n}\n\nfunction exampleValueForEnvKey(envKey: string): string {\n return `your-${placeholderTokenFromSuffix(envKey)}`;\n}\n\nfunction parseStepBinding(step: string): StepBinding | undefined {\n const s = step.trim();\n\n const fillMatch = s.match(/\\b(?:fill|input|write)\\s+(?:in\\s+)?(.+?)\\s+with\\s+(['\"])(.*?)\\2/i);\n if (fillMatch) {\n return {\n kind: 'fill',\n rawField: fillMatch[1].trim(),\n fieldLabel: cleanFieldLabel(fillMatch[1]),\n value: unwrapStepValue(fillMatch[3]),\n };\n }\n\n const fillLooseMatch = s.match(/\\b(?:fill|input|write)\\s+(?:in\\s+)?(.+?)\\s+with\\s+(.+)$/i);\n if (fillLooseMatch) {\n return {\n kind: 'fill',\n rawField: fillLooseMatch[1].trim(),\n fieldLabel: cleanFieldLabel(fillLooseMatch[1]),\n value: unwrapStepValue(fillLooseMatch[2]),\n };\n }\n\n const fillWithoutValueMatch = s.match(/\\b(?:fill|input|write)\\s+(?:in\\s+)?(.+?)$/i);\n if (fillWithoutValueMatch) {\n return {\n kind: 'fill',\n rawField: fillWithoutValueMatch[1].trim(),\n fieldLabel: cleanFieldLabel(fillWithoutValueMatch[1]),\n };\n }\n\n const enterMatch = s.match(/\\b(?:enter|type)\\s+(['\"])(.*?)\\1\\s+(?:in|into)\\s+(.+)/i);\n if (enterMatch) {\n return {\n kind: 'fill',\n rawField: enterMatch[3].trim(),\n fieldLabel: cleanFieldLabel(enterMatch[3]),\n value: unwrapStepValue(enterMatch[2]),\n };\n }\n\n const enterLooseMatch = s.match(/\\b(?:enter|type)\\s+(.+?)\\s+(?:in|into)\\s+(.+)$/i);\n if (enterLooseMatch) {\n return {\n kind: 'fill',\n rawField: enterLooseMatch[2].trim(),\n fieldLabel: cleanFieldLabel(enterLooseMatch[2]),\n value: unwrapStepValue(enterLooseMatch[1]),\n };\n }\n\n const enterWithoutValueMatch = s.match(/\\b(?:enter|type)\\s+(?:in|into)\\s+(.+)/i);\n if (enterWithoutValueMatch) {\n return {\n kind: 'fill',\n rawField: enterWithoutValueMatch[1].trim(),\n fieldLabel: cleanFieldLabel(enterWithoutValueMatch[1]),\n };\n }\n\n const selectMatch = s.match(/\\b(?:select|choose|pick)\\s+(['\"])(.*?)\\1\\s+(?:from|in|into|on)\\s+(.+)/i);\n if (selectMatch) {\n return {\n kind: 'select',\n rawField: selectMatch[3].trim(),\n fieldLabel: cleanFieldLabel(selectMatch[3]),\n value: unwrapStepValue(selectMatch[2]),\n };\n }\n\n const selectLooseMatch = s.match(/\\b(?:select|choose|pick)\\s+(.+?)\\s+(?:from|in|into|on)\\s+(.+)$/i);\n if (selectLooseMatch) {\n return {\n kind: 'select',\n rawField: selectLooseMatch[2].trim(),\n fieldLabel: cleanFieldLabel(selectLooseMatch[2]),\n value: unwrapStepValue(selectLooseMatch[1]),\n };\n }\n\n const selectWithoutValueMatch = s.match(/\\b(?:select|choose|pick)\\s+(.+?)\\s+(?:dropdown|combobox|select|menu)\\b/i);\n if (selectWithoutValueMatch) {\n return {\n kind: 'select',\n rawField: selectWithoutValueMatch[1].trim(),\n fieldLabel: cleanFieldLabel(selectWithoutValueMatch[1]),\n };\n }\n\n return undefined;\n}\n\nfunction resolveStepVarOverride(\n overrides: VarsOverrideMap,\n envKey: string,\n keySuffix: string,\n rawField?: string,\n fieldLabel?: string,\n): string | undefined {\n const candidates = [envKey, keySuffix, rawField, fieldLabel];\n for (const candidate of candidates) {\n if (!candidate) continue;\n const normalized = normalizeOverrideKey(candidate);\n if (normalized && overrides.has(normalized)) return overrides.get(normalized);\n }\n return undefined;\n}\n\nfunction extractStepVars(\n steps: string[],\n stepHints: Array<{ selector?: string }> | undefined,\n assertionHints: Array<{ playwright?: string }> | undefined,\n overrides: VarsOverrideMap,\n): StepValueState {\n const declarations: StepVar[] = [];\n const byStepIndex = new Map<number, StepVar>();\n const fillValueBySelector = new Map<string, string>();\n const seen = new Map<string, StepVar>();\n let unnamedFillIndex = 0;\n let unnamedSelectIndex = 0;\n\n const assertionFallbacks = new Map<string, string>();\n for (const hint of assertionHints ?? []) {\n const parsed = parseValueAssertionHint(hint?.playwright);\n if (!parsed?.expected) continue;\n if (parsed.target) assertionFallbacks.set(parsed.target, parsed.expected);\n const envSuffix = selectorTargetToEnvSuffix(parsed.target);\n if (envSuffix) assertionFallbacks.set(`CT_VAR_${envSuffix}`, parsed.expected);\n }\n\n for (const [index, step] of steps.entries()) {\n const binding = parseStepBinding(step);\n if (!binding) continue;\n\n const selectorTarget = normalizeSelectorTarget(stepHints?.[index]?.selector);\n const keySuffix =\n fieldNameToEnvSuffix(binding.rawField) ??\n fieldNameToEnvSuffix(binding.fieldLabel) ??\n selectorTargetToEnvSuffix(selectorTarget) ??\n `${binding.kind === 'select' ? 'SELECT' : 'INPUT'}_${binding.kind === 'select' ? ++unnamedSelectIndex : ++unnamedFillIndex}`;\n const templateEnvKey = extractCtVarTemplate(binding.value);\n const envKey = templateEnvKey ?? `CT_VAR_${keySuffix}`;\n const assertionFallback =\n (selectorTarget ? assertionFallbacks.get(selectorTarget) : undefined) ??\n assertionFallbacks.get(envKey);\n const overrideValue = resolveStepVarOverride(overrides, envKey, keySuffix, binding.rawField, binding.fieldLabel);\n const fallbackFromBinding = binding.value?.trim();\n const hasRuntimeEnvValue = process.env[envKey] !== undefined;\n const shouldDeclareVariable =\n binding.kind === 'select' ||\n templateEnvKey !== undefined ||\n overrideValue !== undefined ||\n hasRuntimeEnvValue;\n\n let stepVar = shouldDeclareVariable ? seen.get(envKey) : undefined;\n if (shouldDeclareVariable && !stepVar) {\n const placeholderFallback =\n binding.kind === 'fill'\n ? buildFillPlaceholder(binding.rawField, binding.fieldLabel, selectorTarget)\n : assertionFallback ?? 'option';\n const fallback =\n overrideValue ??\n process.env[envKey] ??\n (templateEnvKey\n ? assertionFallback ?? placeholderFallback\n : ((!fallbackFromBinding || isGenericFallbackValue(fallbackFromBinding)) && assertionFallback\n ? assertionFallback\n : fallbackFromBinding)) ??\n placeholderFallback;\n\n stepVar = {\n envKey,\n constName: envKey,\n fallback,\n selectorHint: stepHints?.[index]?.selector,\n selectorTarget,\n fieldLabel: binding.fieldLabel,\n };\n seen.set(envKey, stepVar);\n declarations.push(stepVar);\n }\n\n if (stepVar) byStepIndex.set(index, stepVar);\n if (binding.kind === 'fill' && selectorTarget) {\n const expression = stepVar\n ? stepVar.constName\n : `'${escapeForSingleQuotedString(resolveLiteralFillValue(binding, selectorTarget))}'`;\n fillValueBySelector.set(selectorTarget, expression);\n }\n }\n\n return { declarations, byStepIndex, fillValueBySelector };\n}\n\nfunction buildVariableHeaderComment(stepVars: StepVar[]): string {\n if (stepVars.length === 0) return '';\n const exampleEntries = stepVars.map((stepVar) => `${stepVar.envKey}=${exampleValueForEnvKey(stepVar.envKey)}`);\n return [\n '// ── Test variables ────────────────────────────────────────────────────────────',\n '// Set these environment variables before running:',\n '//',\n ...stepVars.map((stepVar) => `// ${stepVar.envKey}=${exampleValueForEnvKey(stepVar.envKey)}`),\n '//',\n '// Or pass them inline:',\n `// ${exampleEntries.join(' ')} npx playwright test`,\n '// ─────────────────────────────────────────────────────────────────────────────',\n ].join('\\n');\n}\n\nfunction buildVariableDeclarations(stepVars: StepVar[]): string {\n if (stepVars.length === 0) return '';\n return [\n ' // Test variables',\n ...stepVars.map(\n stepVar =>\n ` const ${stepVar.constName} = process.env['${stepVar.envKey}'] ?? '${escapeForSingleQuotedString(stepVar.fallback)}';`,\n ),\n '',\n ].join('\\n');\n}\n\nfunction parseVarsOverride(raw?: string): VarsOverrideMap {\n if (!raw) return new Map();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n console.error('❌ --vars must be a valid JSON object');\n process.exit(1);\n }\n\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n console.error('❌ --vars must be a valid JSON object');\n process.exit(1);\n }\n\n const overrides = new Map<string, string>();\n for (const [key, value] of Object.entries(parsed)) {\n const normalizedKey = normalizeOverrideKey(key);\n if (!normalizedKey) continue;\n overrides.set(normalizedKey, String(value));\n }\n return overrides;\n}\n\n// ─── Step → Playwright action ─────────────────────────────────────────────────\n\nfunction ensureStatement(value: string): string {\n const trimmed = value.trim();\n if (!trimmed) return trimmed;\n return trimmed.endsWith(';') ? trimmed : `${trimmed};`;\n}\n\nfunction phraseToRegexPattern(value: string): string {\n return value\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .map(token => escapeForRegex(token))\n .join('\\\\s+');\n}\n\nfunction buttonNameExpressionFromStep(step: string): string {\n const quotedMatch = step.match(/[\"']([^\"']+)[\"']/);\n if (quotedMatch) return `'${escapeForSingleQuotedString(quotedMatch[1])}'`;\n\n const label = step\n .replace(/^(click|press|tap)\\s+(?:on\\s+)?(?:the\\s+)?/i, '')\n .replace(/\\b(button|btn)\\b/gi, '')\n .replace(/[.?!,:;]+$/g, '')\n .trim();\n if (!label) return `'button'`;\n\n const lowered = label.toLowerCase();\n const variants = new Set<string>();\n if (/\\b(?:login|log in|sign in|sign-in)\\b/.test(lowered)) {\n variants.add('login');\n variants.add('log in');\n variants.add('sign in');\n } else if (/\\b(?:register|sign up|sign-up|signup|create account)\\b/.test(lowered)) {\n variants.add('register');\n variants.add('sign up');\n variants.add('create account');\n } else if (/\\bcontinue\\b/.test(lowered)) {\n variants.add('continue');\n variants.add('next');\n } else {\n variants.add(label);\n }\n\n const pattern = Array.from(variants).map(phraseToRegexPattern).join('|');\n return `/${pattern}/i`;\n}\n\nfunction parseValueAssertionHint(playwright?: string): { target?: string; expected?: string } | undefined {\n const statement = ensureStatement(String(playwright ?? '').trim());\n if (!statement) return undefined;\n\n const match = statement.match(/expect\\(([\\s\\S]+)\\)\\.toHaveValue\\(([\\s\\S]+)\\)\\s*;$/);\n if (!match) return undefined;\n\n return {\n target: normalizeSelectorTarget(match[1]),\n expected: parseQuotedLiteral(match[2]),\n };\n}\n\nfunction findFillValueExpressionForTarget(\n target: string | undefined,\n stepState: StepValueState,\n): string | undefined {\n const normalizedTarget = normalizeSelectorTarget(target);\n if (!normalizedTarget) return undefined;\n\n return stepState.fillValueBySelector.get(normalizedTarget);\n}\n\nfunction rewriteValueAssertionToUseVariable(\n playwright: string,\n stepState: StepValueState,\n): string | undefined {\n const parsed = parseValueAssertionHint(playwright);\n if (!parsed?.target) return undefined;\n\n const valueExpression = findFillValueExpressionForTarget(parsed.target, stepState);\n if (!valueExpression) return undefined;\n\n return ensureStatement(`await expect(page.${parsed.target}).toHaveValue(${valueExpression})`);\n}\n\nfunction stepToPlaywright(\n step: string,\n url?: string,\n hint?: { selector?: string },\n stepVar?: StepVar,\n): string {\n const s = step.trim();\n const hintedSelector = hint?.selector ? `page.${hint.selector}` : undefined;\n const selectorTarget = normalizeSelectorTarget(hint?.selector);\n const valueExpression = (value: string): string =>\n stepVar?.constName ?? `'${escapeForSingleQuotedString(value)}'`;\n\n // Navigate\n if (/^(navigate|go to|open|visit|load)/i.test(s)) {\n const urlMatch = s.match(/https?:\\/\\/[^\\s'\"]+/) || s.match(/[\"']([^\"']+)[\"']/);\n const dest = urlMatch?.[1] ?? urlMatch?.[0] ?? url ?? '/';\n if (\n normalizeUrlForComparison(dest) &&\n normalizeUrlForComparison(dest) === normalizeUrlForComparison(url)\n ) {\n return `// navigation handled by pomPage.goto()`;\n }\n return `await page.goto('${escapeForSingleQuotedString(dest)}');`;\n }\n\n // Fill / type / enter into a field\n // Pattern: \"Fill in <field> with '<value>'\" or \"Enter '<value>' in <field>\"\n const binding = parseStepBinding(s);\n if (binding?.kind === 'fill') {\n const fallbackValue = stepVar?.fallback ?? resolveLiteralFillValue(binding, selectorTarget);\n if (hintedSelector) return `await ${hintedSelector}.fill(${valueExpression(fallbackValue)});`;\n const field = binding.fieldLabel ?? 'field';\n return `await page.getByLabel('${escapeForSingleQuotedString(field)}').fill(${valueExpression(fallbackValue)});`;\n }\n\n // Click a button\n if (/\\bclick\\b.*(button|btn|submit|sign in|log in|login|register|continue|next|save|confirm)/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.click();`;\n return `await page.getByRole('button', { name: ${buttonNameExpressionFromStep(s)} }).click();`;\n }\n\n // Click a link\n if (/\\bclick\\b.*(link|anchor|nav)/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.click();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/click\\s+(the\\s+)?/i, '').trim();\n return `await page.getByRole('link', { name: '${escapeForSingleQuotedString(name)}' }).click();`;\n }\n\n // Generic click\n if (/^(click|press|tap)\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.click();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (nameMatch) return `await page.getByText('${escapeForSingleQuotedString(nameMatch[1])}').click();`;\n const target = s.replace(/^(click|press|tap)\\s+(on\\s+)?/i, '').trim();\n return `await page.getByText('${escapeForSingleQuotedString(target)}').click();`;\n }\n\n // Select dropdown\n if (binding?.kind === 'select') {\n const fallbackValue = binding.value ?? stepVar?.fallback ?? 'option';\n if (hintedSelector) return `await ${hintedSelector}.selectOption(${valueExpression(fallbackValue)});`;\n if (binding.fieldLabel) {\n return `await page.getByLabel('${escapeForSingleQuotedString(binding.fieldLabel)}').selectOption(${valueExpression(fallbackValue)});`;\n }\n return `await page.getByRole('combobox').selectOption(${valueExpression(fallbackValue)});`;\n }\n\n // Check / uncheck\n if (/\\b(uncheck|untick|disable)\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.uncheck();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/uncheck|untick|disable/gi, '').trim();\n return `await page.getByLabel('${escapeForSingleQuotedString(name)}').uncheck();`;\n }\n if (/\\b(check|tick|enable)\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.check();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/check|tick|enable/gi, '').trim();\n return `await page.getByLabel('${escapeForSingleQuotedString(name)}').check();`;\n }\n\n // Keyboard key\n if (/press.*(enter|tab|escape|esc|space|backspace)/i.test(s)) {\n const keyMatch = s.match(/enter|tab|escape|esc|space|backspace/i);\n const key = (keyMatch?.[0] ?? 'Enter');\n return `await page.keyboard.press('${key.charAt(0).toUpperCase() + key.slice(1).toLowerCase()}');`;\n }\n\n // Reload\n if (/\\b(reload|refresh)\\b/i.test(s)) {\n return `await page.reload();`;\n }\n\n // Hover\n if (/\\bhover\\b/i.test(s)) {\n if (hintedSelector) return `await ${hintedSelector}.hover();`;\n const nameMatch = s.match(/[\"']([^\"']+)[\"']/);\n const name = nameMatch?.[1] ?? s.replace(/hover\\s+(over\\s+)?/i, '').trim();\n return `await page.getByText('${escapeForSingleQuotedString(name)}').hover();`;\n }\n\n // Scroll\n if (/\\bscroll\\b/i.test(s)) {\n return `await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));`;\n }\n\n // Fallback — preserve the step as a comment so the file is still valid\n return `// TODO: map to Playwright action → \"${s}\"`;\n}\n\n// ─── Expected result → assertion ──────────────────────────────────────────────\n\nfunction expectedToAssertion(\n expected: string,\n norm?: NormalizedCase,\n hint?: { playwright?: string },\n stepState?: StepValueState,\n): string {\n if (hint?.playwright) {\n const rewritten = stepState ? rewriteValueAssertionToUseVariable(hint.playwright, stepState) : undefined;\n return rewritten ?? ensureStatement(hint.playwright);\n }\n const s = expected.trim();\n const fieldName = fieldNameFromSentence(s);\n const currentUrlPattern = urlPatternFromAbsoluteUrl(norm?.url);\n\n // Cleared / empty field\n if (/\\b(clear|cleared|empty|blank)\\b/i.test(s) && /\\b(field|input|value)\\b/i.test(s) && fieldName) {\n return `await expect(page.getByLabel('${escapeForSingleQuotedString(fieldName)}')).toHaveValue('');`;\n }\n\n // Stayed on the same page / did not submit\n if (/\\bform\\b.*\\b(?:does not submit|doesn't submit|not submit|not submitted)\\b/i.test(s)) {\n const fallbackPattern = currentUrlPattern || 'login';\n return `await expect(page).toHaveURL(/${fallbackPattern}/); // form did not navigate`;\n }\n if (/\\b(remains?|stays?|still)\\s+on\\b/i.test(s)) {\n const pageMatch = s.match(/\\b(?:remains?|stays?|still)\\s+on\\s+(?:the\\s+)?(.+?)(?:\\s+page|\\s+screen)?$/i);\n const pagePattern = pageMatch?.[1] ? pageNameToUrlPattern(pageMatch[1]) : undefined;\n return `await expect(page).toHaveURL(/${pagePattern || currentUrlPattern || 'login'}/);`;\n }\n\n // URL / redirect check\n if (/\\b(url|redirect(?:s|ed)?|navigate(?:s|d)?|route|path)\\b/i.test(s)) {\n const pathMatch =\n s.match(/[\"'](\\/[^\"']+)[\"']/) ??\n s.match(/to\\s+(\\/[\\w/-]+)/i) ??\n s.match(/https?:\\/\\/[^\\s'\"]+/i);\n const matchedPath = pathMatch?.[1] ?? pathMatch?.[0];\n if (matchedPath) {\n const fromUrl = matchedPath.startsWith('http') ? urlPatternFromAbsoluteUrl(matchedPath) : undefined;\n const directPath = matchedPath.startsWith('/') ? matchedPath.replace(/^\\/+/, '').split('/').map(escapeForRegex).join('\\\\/') : undefined;\n const finalPattern = fromUrl ?? directPath ?? currentUrlPattern;\n if (finalPattern !== undefined) return `await expect(page).toHaveURL(/${finalPattern}/);`;\n }\n return `await expect(page).toHaveURL(/dashboard|success|home/);`;\n }\n\n // Page title\n if (/\\bpage title\\b|\\btitle should\\b|\\bdocument title\\b/i.test(s)) {\n const titleMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (titleMatch) return `await expect(page).toHaveTitle('${escapeForSingleQuotedString(titleMatch[1])}');`;\n return `await expect(page).toHaveTitle(/.+/);`;\n }\n\n // Error / validation message\n if (/\\b(error|invalid|fail|incorrect|required|validation)\\b/i.test(s)) {\n const msgMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (msgMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(msgMatch[1])}')).toBeVisible();`;\n if (fieldName) return `await expect(page.getByText(/${escapeForRegex(fieldName)}/i)).toBeVisible();`;\n return `await expect(page.getByRole('alert')).toBeVisible();`;\n }\n\n // Success / confirmation\n if (/\\b(success|confirm|complete|thank|welcome|sent|saved)\\b/i.test(s)) {\n const msgMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (msgMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(msgMatch[1])}')).toBeVisible();`;\n if (/\\bwelcome\\b/i.test(s)) return `await expect(page.getByText(/welcome/i)).toBeVisible();`;\n return `await expect(page.getByRole('status')).toBeVisible();`;\n }\n\n // Not visible / hidden\n if (/\\b(not visible|hidden|disappear|removed|gone)\\b/i.test(s)) {\n const elementMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (elementMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(elementMatch[1])}')).not.toBeVisible();`;\n return `await expect(page.locator('.modal, [role=\"dialog\"]').first()).not.toBeVisible();`;\n }\n\n // Visible / present\n if (/\\b(visible|appear|display|show|render|present)\\b/i.test(s)) {\n const elementMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (elementMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(elementMatch[1])}')).toBeVisible();`;\n const subjectMatch =\n s.match(/^(?:the\\s+)?(.+?)\\s+(?:is|are|should|must|becomes?|appears?|renders?|shows?|displays?)\\b/i) ??\n s.match(/^(?:the\\s+)?(.+?)\\s+(?:visible|present)\\b/i);\n const subject = subjectMatch?.[1]?.trim();\n if (subject) {\n if (/\\bnavigation\\b.*\\bmenu\\b|\\bmenu\\b.*\\bnavigation\\b|\\bnavigation\\b/i.test(subject)) {\n return `await expect(page.getByRole('navigation')).toBeVisible();`;\n }\n return `await expect(page.getByText(/${visibleTextRegexFromPhrase(subject)}/i)).toBeVisible();`;\n }\n return `await expect(page.locator('[data-testid]').first()).toBeVisible();`;\n }\n\n // Count of items\n if (/\\b(count|number of|list of|\\d+\\s+item)\\b/i.test(s)) {\n const countMatch = s.match(/(\\d+)/);\n if (countMatch) return `await expect(page.locator('li, tr, [role=\"listitem\"]')).toHaveCount(${countMatch[1]});`;\n return `await expect(page.locator('li, tr').first()).toBeVisible();`;\n }\n\n // Enabled / disabled\n if (/\\b(enabled|clickable|active)\\b/i.test(s)) {\n return `await expect(page.getByRole('button').first()).toBeEnabled();`;\n }\n if (/\\b(disabled|inactive)\\b/i.test(s)) {\n return `await expect(page.getByRole('button').first()).toBeDisabled();`;\n }\n\n // Input value\n if (/\\b(clear|cleared|empty|blank)\\b/i.test(s) && /\\b(field|input|value)\\b/i.test(s)) {\n return `await expect(page.getByRole('textbox').first()).toHaveValue('');`;\n }\n if (/\\b(field|input|value|filled)\\b/i.test(s)) {\n const valMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (valMatch) {\n const envSuffix = fieldNameToEnvSuffix(fieldName);\n const stepVar = envSuffix ? stepState?.declarations.find(candidate => candidate.envKey === `CT_VAR_${envSuffix}`) : undefined;\n if (stepVar) return `await expect(page.getByRole('textbox').first()).toHaveValue(${stepVar.constName});`;\n return `await expect(page.getByRole('textbox').first()).toHaveValue('${escapeForSingleQuotedString(valMatch[1])}');`;\n }\n return `await expect(page.getByRole('textbox').first()).not.toBeEmpty();`;\n }\n\n // Text / heading / content (broad fallback before final fallback)\n if (/\\b(text|content|label|message|heading)\\b/i.test(s)) {\n const textMatch = s.match(/[\"']([^\"']+)[\"']/);\n if (textMatch) return `await expect(page.getByText('${escapeForSingleQuotedString(textMatch[1])}')).toBeVisible();`;\n const words = s.replace(/\\b(the|should|must|contain|display|show|have|text|content)\\b/gi, '').trim();\n return `await expect(page.getByText(/${visibleTextRegexFromPhrase(words)}/i)).toBeVisible();`;\n }\n\n // Final fallback\n const cleaned = s.replace(/[\"']/g, '').trim();\n return `await expect(page.getByText(/${visibleTextRegexFromPhrase(cleaned)}/i)).toBeVisible(); // TODO: refine assertion`;\n}\n\n// ─── POM class name / filename ────────────────────────────────────────────────\n\nfunction toIdentifier(value: string, fallback = 'Landing'): string {\n const tokens = value\n .normalize('NFKD')\n .replace(/[\\u0300-\\u036f]/g, '')\n .split(/[^a-zA-Z0-9_]+/)\n .filter(Boolean);\n\n const identifier = tokens\n .map((token) => {\n const normalized = /^[A-Z0-9_]+$/.test(token) ? token.toLowerCase() : token;\n return normalized.charAt(0).toUpperCase() + normalized.slice(1);\n })\n .join('');\n\n if (!identifier) return fallback;\n return /^[0-9]/.test(identifier) ? `Case${identifier}` : identifier;\n}\n\nfunction derivePomClassName(norm: NormalizedCase): string {\n const raw = norm.id ?? norm.title ?? 'Landing';\n const withoutNum = raw.replace(/-\\d+$/, '');\n return `${toIdentifier(withoutNum)}Page`;\n}\n\n// ─── POM class source ─────────────────────────────────────────────────────────\n\nfunction buildPomClass(className: string, norm: NormalizedCase, lang: 'ts' | 'js'): string {\n const pageUrl = norm.url ?? '/';\n const isTs = lang === 'ts';\n\n return isTs\n ? `import { Page } from '@playwright/test';\n\nexport class ${className} {\n readonly page: Page;\n\n constructor(page: Page) {\n this.page = page;\n }\n\n async goto(): Promise<void> {\n await this.page.goto('${escapeForSingleQuotedString(pageUrl)}');\n }\n\n async waitForLoad(): Promise<void> {\n await this.page.waitForLoadState('domcontentloaded');\n }\n}\n`\n : `// @ts-check\nexport class ${className} {\n /** @param {import('@playwright/test').Page} page */\n constructor(page) {\n /** @type {import('@playwright/test').Page} */\n this.page = page;\n }\n\n async goto() {\n await this.page.goto('${escapeForSingleQuotedString(pageUrl)}');\n }\n\n async waitForLoad() {\n await this.page.waitForLoadState('domcontentloaded');\n }\n}\n`;\n}\n\n// ─── Spec file source ─────────────────────────────────────────────────────────\n\nfunction buildTestTitle(norm: NormalizedCase): string {\n const idPart = norm.id ?? '';\n\n // norm.title may already include the ID prefix (e.g. \"AUTH-001 — User can log in\")\n // Strip it to avoid \"AUTH-001 — AUTH-001 — User can log in\" in the test name.\n let cleanTitle = norm.title || 'Untitled';\n if (idPart && cleanTitle.startsWith(idPart)) {\n cleanTitle = cleanTitle.slice(idPart.length).replace(/^\\s*[—\\-–]+\\s*/, '').trim();\n }\n\n const tagSuffix = (norm.tags ?? []).map(t => `@${t}`).join(' ');\n return [idPart, cleanTitle, tagSuffix].filter(Boolean).join(' — ').trim();\n}\n\nfunction buildSpecFile(\n norm: NormalizedCase,\n pomClassName: string,\n pomImportPath: string,\n overrides: VarsOverrideMap,\n): string {\n const title = buildTestTitle(norm);\n const steps = norm.steps ?? [];\n const expected = norm.expected ?? [];\n const stepVars = extractStepVars(steps, norm.step_hints, norm.assertion_hints, overrides);\n\n let importPath = pomImportPath.replace(/\\\\/g, '/');\n if (!importPath.startsWith('.')) importPath = `./${importPath}`;\n // Strip .ts extension — TypeScript resolves without it\n importPath = importPath.replace(/\\.ts$/, '');\n\n const stepLines = steps.length\n ? steps\n .map((s, index) => ` ${stepToPlaywright(s, norm.url, norm.step_hints?.[index], stepVars.byStepIndex.get(index))}`)\n .join('\\n')\n : ' // TODO: add steps';\n\n const assertionLines = expected.length\n ? expected.map((e, index) => ` ${expectedToAssertion(e, norm, norm.assertion_hints?.[index], stepVars)}`).join('\\n')\n : ' // TODO: add assertions';\n\n const reviewNote = norm.needs_review\n ? `\\n // ⚠️ Flagged for review — steps or assertions may need manual refinement\\n`\n : '';\n const variableHeader = buildVariableHeaderComment(stepVars.declarations);\n const variableHeaderBlock = variableHeader ? `${variableHeader}\\n` : '';\n const variableDeclarations = buildVariableDeclarations(stepVars.declarations);\n\n return `import { test, expect } from '@playwright/test';\nimport { ${pomClassName} } from '${importPath}';\n\n${variableHeaderBlock}test('${title}', async ({ page }) => {${reviewNote}\n const pomPage = new ${pomClassName}(page);\n\n${variableDeclarations} // ── Setup ─────────────────────────────────────────────────────────────────\n await pomPage.goto();\n await pomPage.waitForLoad();\n\n // ── Steps ─────────────────────────────────────────────────────────────────\n${stepLines}\n\n // ── Assertions ────────────────────────────────────────────────────────────\n${assertionLines}\n});\n`;\n}\n\n// ─── Main ─────────────────────────────────────────────────────────────────────\n\nexport async function gen(opts: { lang: string; out: string; vars?: string }) {\n if (opts.lang !== 'ts' && opts.lang !== 'js') {\n console.error('❌ --lang must be ts or js');\n process.exit(1);\n }\n const lang = opts.lang as 'ts' | 'js';\n const specExt = lang === 'js' ? 'spec.js' : 'spec.ts';\n const pomExt = lang === 'js' ? '.js' : '.ts';\n const varsOverride = parseVarsOverride(opts.vars);\n\n const normalized = await fg([\n '.cementic/normalized/*.json',\n '!.cementic/normalized/_index.json',\n ]);\n\n if (normalized.length === 0) {\n console.warn('⚠️ No normalized cases found in .cementic/normalized/');\n console.warn(' Run: ct normalize ./cases');\n process.exit(1);\n }\n\n const projectRoot = process.cwd();\n const testsOutDir = resolve(projectRoot, opts.out);\n const pagesOutDir = resolve(projectRoot, 'pages');\n\n mkdirSync(testsOutDir, { recursive: true });\n mkdirSync(pagesOutDir, { recursive: true });\n\n let specCount = 0;\n let pomCount = 0;\n\n for (const f of normalized) {\n const norm = JSON.parse(readFileSync(f, 'utf8')) as NormalizedCase;\n\n const pomClassName = derivePomClassName(norm);\n const pomFileName = pomClassName + pomExt;\n const pomFilePath = join(pagesOutDir, pomFileName);\n\n // Only write a POM file if one doesn't exist yet — never overwrite user edits\n if (!existsSync(pomFilePath)) {\n writeFileSync(pomFilePath, buildPomClass(pomClassName, norm, lang));\n pomCount++;\n }\n\n // Relative import from the test file location to the POM file\n const relToPages = relative(testsOutDir, pagesOutDir);\n const pomImportPath = join(relToPages, pomFileName);\n\n const stem = basename(f).replace(/\\.json$/, '').replace(/[^\\w-]+/g, '-');\n const specPath = join(testsOutDir, `${stem}.${specExt}`);\n writeFileSync(specPath, buildSpecFile(norm, pomClassName, pomImportPath, varsOverride));\n specCount++;\n }\n\n console.log(`✅ Generated ${specCount} spec file(s) → ${opts.out}/`);\n if (pomCount > 0) {\n console.log(`✅ Generated ${pomCount} POM class file(s) → pages/`);\n }\n if (pomCount === 0 && specCount > 0) {\n console.log(`ℹ️ POM classes already exist — skipped regeneration`);\n }\n}\n\nexport function genCmd() {\n const cmd = new Command('gen')\n .description('Generate Playwright POM spec + page-object files from normalized cases')\n .addHelpText('after', `\nExamples:\n $ ct gen --lang ts\n $ ct gen --lang js --out tests/e2e\n $ ct gen --lang ts --vars '{\"email\":\"admin@example.com\"}'\n`)\n .option('--lang <lang>', 'Target language (ts|js)', 'ts')\n .option('--out <dir>', 'Output directory for spec files', 'tests/generated')\n .option('--vars <json>', 'JSON object of fallback values for generated CT_VAR_* constants')\n .action(async opts => {\n await gen({ lang: opts.lang, out: opts.out, vars: opts.vars });\n });\n return cmd;\n}\n"],"mappings":"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","names":[]}
@@ -96,7 +96,7 @@ async function captureElements(url, options = {}) {
96
96
  headless = true,
97
97
  timeoutMs = DEFAULT_TIMEOUT_MS,
98
98
  verbose = false,
99
- userAgent = "Mozilla/5.0 (compatible; CementicTest/0.2.15 capture)"
99
+ userAgent = "Mozilla/5.0 (compatible; CementicTest/0.2.16 capture)"
100
100
  } = options;
101
101
  const chromium = await loadChromium();
102
102
  const mode = headless ? "headless" : "headed";
@@ -619,4 +619,4 @@ export {
619
619
  extractDomDataFromEnvironment,
620
620
  formatCaptureFailure
621
621
  };
622
- //# sourceMappingURL=chunk-XUWFEWJZ.js.map
622
+ //# sourceMappingURL=chunk-WUGSOKKY.js.map