@vijaypjavvadi/bdd2pw 3.0.0 → 3.2.0

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.
@@ -0,0 +1,55 @@
1
+ /**
2
+ * v3.2.0 — TestForge handoff Issue 8. Accept structured JSON scenarios
3
+ * in addition to .feature files.
4
+ *
5
+ * Input shape (TestForge's TCG output):
6
+ *
7
+ * {
8
+ * "name": "Login with valid credentials",
9
+ * "kind": "ui",
10
+ * "heuristic": "happy path",
11
+ * "preconditions": ["user is registered"],
12
+ * "actions": ["navigate to login page", "fill username", ...],
13
+ * "expected": ["dashboard is visible"],
14
+ * "data": { "username": "student", "password": "Password123" }
15
+ * }
16
+ *
17
+ * Either a single object or an array of objects is accepted at the
18
+ * top level. The converter synthesises Gherkin steps:
19
+ *
20
+ * - preconditions → `Given <text>`
21
+ * - actions → `When <text>` (first), `And <text>` (rest)
22
+ * - expected → `Then <text>` (first), `And <text>` (rest)
23
+ *
24
+ * `data` is appended to each action step that contains a templating
25
+ * placeholder like `<username>`; otherwise it's surfaced as a Gherkin
26
+ * doc-string attached to the first action step.
27
+ *
28
+ * The synthesised FeatureIR is identical in shape to what
29
+ * gherkinParser produces, so downstream code (stepMatcher, emitTestFile)
30
+ * treats both paths uniformly.
31
+ */
32
+ import type { FeatureIR } from "../types";
33
+ export interface JsonScenario {
34
+ name: string;
35
+ kind?: "ui" | "api" | "mixed" | string;
36
+ heuristic?: string;
37
+ preconditions?: string[];
38
+ actions?: string[];
39
+ expected?: string[];
40
+ data?: Record<string, string | number | boolean>;
41
+ /** Optional explicit tags. If omitted, derived from `kind`. */
42
+ tags?: string[];
43
+ }
44
+ /** Detect whether a file path is a JSON-scenario input. */
45
+ export declare function isJsonScenarioFile(file: string): boolean;
46
+ /**
47
+ * Read a JSON scenarios file and convert it to a FeatureIR. The
48
+ * feature name is derived from the file basename (matching gherkin
49
+ * convention: `login.feature` → "login"). Each top-level scenario in
50
+ * the JSON becomes a ScenarioIR.
51
+ *
52
+ * Returns a fully-formed FeatureIR — sourceFile points at the JSON
53
+ * file so the rest of the pipeline can reference the input correctly.
54
+ */
55
+ export declare function parseJsonScenarios(filePath: string): Promise<FeatureIR>;
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ /**
3
+ * v3.2.0 — TestForge handoff Issue 8. Accept structured JSON scenarios
4
+ * in addition to .feature files.
5
+ *
6
+ * Input shape (TestForge's TCG output):
7
+ *
8
+ * {
9
+ * "name": "Login with valid credentials",
10
+ * "kind": "ui",
11
+ * "heuristic": "happy path",
12
+ * "preconditions": ["user is registered"],
13
+ * "actions": ["navigate to login page", "fill username", ...],
14
+ * "expected": ["dashboard is visible"],
15
+ * "data": { "username": "student", "password": "Password123" }
16
+ * }
17
+ *
18
+ * Either a single object or an array of objects is accepted at the
19
+ * top level. The converter synthesises Gherkin steps:
20
+ *
21
+ * - preconditions → `Given <text>`
22
+ * - actions → `When <text>` (first), `And <text>` (rest)
23
+ * - expected → `Then <text>` (first), `And <text>` (rest)
24
+ *
25
+ * `data` is appended to each action step that contains a templating
26
+ * placeholder like `<username>`; otherwise it's surfaced as a Gherkin
27
+ * doc-string attached to the first action step.
28
+ *
29
+ * The synthesised FeatureIR is identical in shape to what
30
+ * gherkinParser produces, so downstream code (stepMatcher, emitTestFile)
31
+ * treats both paths uniformly.
32
+ */
33
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
34
+ if (k2 === undefined) k2 = k;
35
+ var desc = Object.getOwnPropertyDescriptor(m, k);
36
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
37
+ desc = { enumerable: true, get: function() { return m[k]; } };
38
+ }
39
+ Object.defineProperty(o, k2, desc);
40
+ }) : (function(o, m, k, k2) {
41
+ if (k2 === undefined) k2 = k;
42
+ o[k2] = m[k];
43
+ }));
44
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
45
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
46
+ }) : function(o, v) {
47
+ o["default"] = v;
48
+ });
49
+ var __importStar = (this && this.__importStar) || (function () {
50
+ var ownKeys = function(o) {
51
+ ownKeys = Object.getOwnPropertyNames || function (o) {
52
+ var ar = [];
53
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
54
+ return ar;
55
+ };
56
+ return ownKeys(o);
57
+ };
58
+ return function (mod) {
59
+ if (mod && mod.__esModule) return mod;
60
+ var result = {};
61
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
62
+ __setModuleDefault(result, mod);
63
+ return result;
64
+ };
65
+ })();
66
+ Object.defineProperty(exports, "__esModule", { value: true });
67
+ exports.isJsonScenarioFile = isJsonScenarioFile;
68
+ exports.parseJsonScenarios = parseJsonScenarios;
69
+ const fs = __importStar(require("fs-extra"));
70
+ const path = __importStar(require("path"));
71
+ /** Detect whether a file path is a JSON-scenario input. */
72
+ function isJsonScenarioFile(file) {
73
+ return /\.json$/i.test(file);
74
+ }
75
+ /**
76
+ * Read a JSON scenarios file and convert it to a FeatureIR. The
77
+ * feature name is derived from the file basename (matching gherkin
78
+ * convention: `login.feature` → "login"). Each top-level scenario in
79
+ * the JSON becomes a ScenarioIR.
80
+ *
81
+ * Returns a fully-formed FeatureIR — sourceFile points at the JSON
82
+ * file so the rest of the pipeline can reference the input correctly.
83
+ */
84
+ async function parseJsonScenarios(filePath) {
85
+ const raw = await fs.readFile(filePath, "utf8");
86
+ let parsed;
87
+ try {
88
+ parsed = JSON.parse(raw);
89
+ }
90
+ catch (err) {
91
+ throw new Error(`JSON scenarios file ${filePath} is not valid JSON: ${err.message}`);
92
+ }
93
+ const list = Array.isArray(parsed)
94
+ ? parsed
95
+ : [parsed];
96
+ const scenarios = list.map(jsonScenarioToScenarioIR);
97
+ const featureName = path.basename(filePath, path.extname(filePath));
98
+ return {
99
+ name: featureName,
100
+ description: undefined,
101
+ background: undefined,
102
+ scenarios,
103
+ tags: [],
104
+ sourceFile: filePath,
105
+ };
106
+ }
107
+ /**
108
+ * Convert one JSON scenario object to a ScenarioIR. The action/expected
109
+ * arrays are flattened into a Gherkin step list following standard BDD
110
+ * convention: first step in each group gets the strong keyword
111
+ * (Given/When/Then), subsequent steps get `And`.
112
+ */
113
+ function jsonScenarioToScenarioIR(s) {
114
+ if (!s || typeof s !== "object" || typeof s.name !== "string") {
115
+ throw new Error(`Each JSON scenario must have a string "name" field. Received: ${JSON.stringify(s)}`);
116
+ }
117
+ const steps = [];
118
+ for (let i = 0; i < (s.preconditions?.length ?? 0); i++) {
119
+ steps.push({
120
+ keyword: i === 0 ? "Given" : "And",
121
+ text: s.preconditions[i],
122
+ });
123
+ }
124
+ for (let i = 0; i < (s.actions?.length ?? 0); i++) {
125
+ const text = applyDataTemplating(s.actions[i], s.data);
126
+ steps.push({
127
+ keyword: i === 0 ? "When" : "And",
128
+ text,
129
+ });
130
+ }
131
+ for (let i = 0; i < (s.expected?.length ?? 0); i++) {
132
+ steps.push({
133
+ keyword: i === 0 ? "Then" : "And",
134
+ text: applyDataTemplating(s.expected[i], s.data),
135
+ });
136
+ }
137
+ // Tag derivation: explicit `tags` wins; otherwise derive from `kind`
138
+ // so `kind: "api"` produces `@api` automatically. Same for ui/mixed.
139
+ const tags = s.tags ?? deriveTagsFromKind(s.kind);
140
+ return {
141
+ name: s.name,
142
+ steps,
143
+ tags,
144
+ examples: undefined,
145
+ };
146
+ }
147
+ function deriveTagsFromKind(kind) {
148
+ if (!kind)
149
+ return [];
150
+ if (kind === "mixed")
151
+ return ["@ui", "@api"];
152
+ return [`@${kind}`];
153
+ }
154
+ /**
155
+ * Replace `<placeholder>` tokens in a step text with values from the
156
+ * scenario's `data` object. Leaves the step alone when no placeholders
157
+ * are present. Same convention as Cucumber's Scenario Outline.
158
+ */
159
+ function applyDataTemplating(text, data) {
160
+ if (!data)
161
+ return text;
162
+ return text.replace(/<([a-zA-Z_][\w]*)>/g, (full, key) => {
163
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
164
+ return String(data[key]);
165
+ }
166
+ return full; // leave unreplaced placeholders intact
167
+ });
168
+ }
169
+ //# sourceMappingURL=jsonScenarioParser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonScenarioParser.js","sourceRoot":"","sources":["../../src/parser/jsonScenarioParser.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,12 @@ export interface ScaffoldProjectOptions {
26
26
  projectName?: string;
27
27
  /** When true, also write lib/heal.ts and patch tsconfig paths. */
28
28
  selfHealing?: boolean;
29
+ /**
30
+ * v3.2.0 — pin emitted devDependency versions when set to `exact`.
31
+ * Default `caret` matches existing behavior. See TestForge handoff
32
+ * Issue 9 and pw-emit v1.3.0.
33
+ */
34
+ dependencyStrategy?: "caret" | "exact";
29
35
  }
30
36
  export interface ScaffoldProjectResult {
31
37
  filesWritten: string[];
@@ -63,6 +63,7 @@ async function scaffoldProject(opts) {
63
63
  templatesDir: opts.templatesDir,
64
64
  baseUrl: opts.baseUrl,
65
65
  projectName: opts.projectName,
66
+ dependencyStrategy: opts.dependencyStrategy,
66
67
  });
67
68
  const filesWritten = [...result.filesWritten];
68
69
  const warnings = [...result.warnings];
@@ -1 +1 @@
1
- {"version":3,"file":"projectScaffolder.js","sourceRoot":"","sources":["../../src/repo/projectScaffolder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"projectScaffolder.js","sourceRoot":"","sources":["../../src/repo/projectScaffolder.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BH,0CA0BC;AAtDD,6CAA+B;AAC/B,2CAA6B;AAC7B,oDAAqD;AA0B9C,KAAK,UAAU,eAAe,CACnC,IAA4B;IAE5B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,sBAAsB,CAAC,CAAC;IACpE,MAAM,cAAc,GAAG,MAAM,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;IAEvD,MAAM,MAAM,GAAG,MAAM,IAAA,qBAAW,EAAC;QAC/B,MAAM,EAAE,IAAI,CAAC,QAAQ;QACrB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,kBAAkB,EAAE,IAAI,CAAC,kBAAkB;KAC5C,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,CAAC,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEtC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,MAAM,uBAAuB,CAAC,IAAI,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC;IACvE,CAAC;IAED,OAAO;QACL,YAAY;QACZ,cAAc;QACd,QAAQ;KACT,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,uBAAuB,CACpC,QAAgB,EAChB,YAAsB,EACtB,QAAsB;IAEtB,0CAA0C;IAC1C,uEAAuE;IACvE,wEAAwE;IACxE,6EAA6E;IAC7E,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC;QAChE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC;KACvE,CAAC;IACF,IAAI,YAAgC,CAAC;IACrC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3B,YAAY,GAAG,CAAC,CAAC;YACjB,MAAM;QACR,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;QACzD,MAAM,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;SAAM,CAAC;QACN,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,MAAM;YAChB,OAAO,EACL,gDAAgD;gBAChD,6EAA6E;YAC/E,UAAU,EACR,sFAAsF;SACzF,CAAC,CAAC;IACL,CAAC;IAED,4EAA4E;IAC5E,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IAC1D,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;YACpD,sEAAsE;YACtE,sEAAsE;YACtE,mEAAmE;YACnE,mCAAmC;YACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACjC,QAAQ,CAAC,eAAe,GAAG,QAAQ,CAAC,eAAe,IAAI,EAAE,CAAC;YAC1D,QAAQ,CAAC,eAAe,CAAC,OAAO,GAAG,QAAQ,CAAC,eAAe,CAAC,OAAO,IAAI,GAAG,CAAC;YAC3E,QAAQ,CAAC,eAAe,CAAC,KAAK,GAAG,QAAQ,CAAC,eAAe,CAAC,KAAK,IAAI,EAAE,CAAC;YACtE,MAAM,eAAe,GAAG,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YACrF,IACE,CAAC,eAAe;gBAChB,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC;gBAC/B,CAAC,eAAe,CAAC,QAAQ,CAAC,YAAY,CAAC,EACvC,CAAC;gBACD,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,4BAA4B,CAAC,GAAG;oBAC7D,YAAY;iBACb,CAAC;gBACF,4CAA4C;gBAC5C,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBACpC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;wBAChE,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;oBACvC,CAAC;gBACH,CAAC;gBACD,MAAM,EAAE,CAAC,SAAS,CAChB,YAAY,EACZ,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EACxC,MAAM,CACP,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM;gBAChB,OAAO,EAAE,6EAA8E,GAAa,CAAC,OAAO,GAAG;gBAC/G,UAAU,EACR,qGAAqG;aACxG,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;IACpD,MAAM,EAAE,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACjC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC;QACpC,MAAM,EAAE,CAAC,SAAS,CAChB,OAAO,EACP,uEAAuE;YACrE,sEAAsE;YACtE,mEAAmE;YACnE,yEAAyE,EAC3E,MAAM,CACP,CAAC;QACF,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * v3.2.0 — TestForge handoff Issue 10. Emit a `<spec-stem>.spec.meta.json`
3
+ * file alongside each generated `.spec.ts` describing every step's
4
+ * semantic intent. Lets downstream tooling (visual regression, defect
5
+ * analysis, self-healing pipelines) consume bdd2pw's understanding
6
+ * without re-parsing the TypeScript output.
7
+ *
8
+ * Sidecar shape (stable across patch versions):
9
+ *
10
+ * {
11
+ * "version": "<bdd2pw version>",
12
+ * "source": "<.feature path relative to repo>",
13
+ * "scenarios": [
14
+ * {
15
+ * "name": "Login with valid credentials",
16
+ * "tags": ["@positive"],
17
+ * "steps": [
18
+ * { "id": "0001", "keyword": "Given", "text": "...",
19
+ * "intent": "navigation", "locator": "page",
20
+ * "assertion": "goto" },
21
+ * ...
22
+ * ]
23
+ * }
24
+ * ]
25
+ * }
26
+ *
27
+ * `intent` is one of: navigation | interaction | assertion | api |
28
+ * compound | todo. Inferred from the binding shape (pomCall vs
29
+ * assertion vs customBody vs warning). When the binding carries
30
+ * `apiContext`, intent is `api` regardless of which sub-field is set.
31
+ */
32
+ import type { StepBinding } from "../types";
33
+ export interface MetaSidecarStep {
34
+ id: string;
35
+ keyword: string;
36
+ text: string;
37
+ intent: "navigation" | "interaction" | "assertion" | "api" | "compound" | "todo";
38
+ /** Resolved locator string when the binding has one. */
39
+ locator?: string;
40
+ /** Matcher name or method name when the binding has one. */
41
+ assertion?: string;
42
+ }
43
+ export interface MetaSidecarScenario {
44
+ name: string;
45
+ tags?: string[];
46
+ steps: MetaSidecarStep[];
47
+ }
48
+ export interface MetaSidecar {
49
+ version: string;
50
+ source: string;
51
+ scenarios: MetaSidecarScenario[];
52
+ }
53
+ /**
54
+ * Convert a list of bindings (one scenario's worth) into the meta
55
+ * sidecar's `steps` array. IDs are zero-padded 4 digits, sequential
56
+ * in source order — same convention as the optional step boundary
57
+ * markers (v3.1.0 Issue 5), so post-processors can correlate the
58
+ * sidecar to the in-source markers when both features are enabled.
59
+ */
60
+ export declare function bindingsToMetaSteps(bindings: StepBinding[]): MetaSidecarStep[];
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /**
3
+ * v3.2.0 — TestForge handoff Issue 10. Emit a `<spec-stem>.spec.meta.json`
4
+ * file alongside each generated `.spec.ts` describing every step's
5
+ * semantic intent. Lets downstream tooling (visual regression, defect
6
+ * analysis, self-healing pipelines) consume bdd2pw's understanding
7
+ * without re-parsing the TypeScript output.
8
+ *
9
+ * Sidecar shape (stable across patch versions):
10
+ *
11
+ * {
12
+ * "version": "<bdd2pw version>",
13
+ * "source": "<.feature path relative to repo>",
14
+ * "scenarios": [
15
+ * {
16
+ * "name": "Login with valid credentials",
17
+ * "tags": ["@positive"],
18
+ * "steps": [
19
+ * { "id": "0001", "keyword": "Given", "text": "...",
20
+ * "intent": "navigation", "locator": "page",
21
+ * "assertion": "goto" },
22
+ * ...
23
+ * ]
24
+ * }
25
+ * ]
26
+ * }
27
+ *
28
+ * `intent` is one of: navigation | interaction | assertion | api |
29
+ * compound | todo. Inferred from the binding shape (pomCall vs
30
+ * assertion vs customBody vs warning). When the binding carries
31
+ * `apiContext`, intent is `api` regardless of which sub-field is set.
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.bindingsToMetaSteps = bindingsToMetaSteps;
35
+ /**
36
+ * Classify a step binding's intent. Order of checks matters:
37
+ * 1. apiContext flag → "api" (overrides every other shape since API
38
+ * bindings often carry customBody + assertion together).
39
+ * 2. warning-only → "todo".
40
+ * 3. pomCall with method "goto" → "navigation".
41
+ * 4. pomCall otherwise → "interaction".
42
+ * 5. assertion → "assertion".
43
+ * 6. customBody → "compound".
44
+ * 7. fallback → "todo".
45
+ */
46
+ function classifyIntent(b) {
47
+ if (b.apiContext)
48
+ return "api";
49
+ if (!b.pomCall && !b.assertion && !b.customBody && b.warning)
50
+ return "todo";
51
+ if (b.pomCall) {
52
+ return b.pomCall.method === "goto" ? "navigation" : "interaction";
53
+ }
54
+ if (b.assertion)
55
+ return "assertion";
56
+ if (b.customBody)
57
+ return "compound";
58
+ return "todo";
59
+ }
60
+ /**
61
+ * Convert a list of bindings (one scenario's worth) into the meta
62
+ * sidecar's `steps` array. IDs are zero-padded 4 digits, sequential
63
+ * in source order — same convention as the optional step boundary
64
+ * markers (v3.1.0 Issue 5), so post-processors can correlate the
65
+ * sidecar to the in-source markers when both features are enabled.
66
+ */
67
+ function bindingsToMetaSteps(bindings) {
68
+ return bindings.map((b, idx) => {
69
+ const step = {
70
+ id: String(idx + 1).padStart(4, "0"),
71
+ keyword: b.step.keyword,
72
+ text: b.step.text,
73
+ intent: classifyIntent(b),
74
+ };
75
+ if (b.pomCall) {
76
+ step.locator = `${b.pomCall.page}.${b.pomCall.method}`;
77
+ step.assertion = b.pomCall.method;
78
+ }
79
+ else if (b.assertion) {
80
+ step.locator = b.assertion.locator;
81
+ step.assertion = b.assertion.matcher;
82
+ }
83
+ return step;
84
+ });
85
+ }
86
+ //# sourceMappingURL=metaSidecar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metaSidecar.js","sourceRoot":"","sources":["../../src/reports/metaSidecar.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;AAwDH,kDAmBC;AAhDD;;;;;;;;;;GAUG;AACH,SAAS,cAAc,CAAC,CAAc;IACpC,IAAI,CAAC,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC;IAC5E,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QACd,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC;IACpE,CAAC;IACD,IAAI,CAAC,CAAC,SAAS;QAAE,OAAO,WAAW,CAAC;IACpC,IAAI,CAAC,CAAC,UAAU;QAAE,OAAO,UAAU,CAAC;IACpC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,mBAAmB,CACjC,QAAuB;IAEvB,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAoB;YAC5B,EAAE,EAAE,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC;YACpC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO;YACvB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI;YACjB,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;SAC1B,CAAC;QACF,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACvD,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QACpC,CAAC;aAAM,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;YACvB,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC;YACnC,IAAI,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC;QACvC,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * v3.2.0 — TestForge handoff Issue 7. Preserve user-edited blocks
3
+ * across regeneration.
4
+ *
5
+ * Two artefacts make this work:
6
+ *
7
+ * 1. **A generated-header marker.** Every spec emitted with `merge`
8
+ * enabled gets a leading
9
+ * `// bdd2pw:generated v=<version> source=<feature>` comment, so a
10
+ * future run can recognise its own output.
11
+ *
12
+ * 2. **User-block markers.** Anywhere in the spec, the user can wrap
13
+ * hand-edited code in:
14
+ *
15
+ * // bdd2pw:user-block id="custom-locator"
16
+ * await this.special = page.locator("[data-thing]");
17
+ * // bdd2pw:end-user-block
18
+ *
19
+ * During regeneration, blocks with the same `id` are spliced back
20
+ * into the new output. Blocks whose id is absent from the new
21
+ * output are appended at end of file under a clearly-labelled
22
+ * "stale user blocks" section so nothing is lost silently.
23
+ *
24
+ * The merge is conservative — it never touches the LINES INSIDE
25
+ * user blocks; everything else is fresh output.
26
+ *
27
+ * Why id-keyed splicing instead of line-based diff? Line numbers
28
+ * shift every time the input feature changes. Id-keyed markers
29
+ * survive arbitrary reordering and renaming.
30
+ */
31
+ /** Detect whether a previously-emitted spec was produced with merge on. */
32
+ export declare function isMergeAnnotated(existing: string): boolean;
33
+ /** Extract every user-block in `existing`, keyed by id. */
34
+ export declare function extractUserBlocks(existing: string): Map<string, string>;
35
+ /**
36
+ * Splice preserved user blocks into a freshly-rendered spec.
37
+ *
38
+ * - `next` is the new bdd2pw output (no user blocks yet).
39
+ * - `preserved` is the id → block-text map extracted from the
40
+ * PREVIOUS spec via `extractUserBlocks`.
41
+ *
42
+ * For each preserved block: if `next` contains a placeholder line
43
+ * `// bdd2pw:user-block id="<id>"` ... `// bdd2pw:end-user-block`
44
+ * (which bdd2pw never emits by itself — see note below), replace it.
45
+ * Otherwise append the block under a "stale user blocks" footer so
46
+ * the user can salvage / delete it on their own.
47
+ *
48
+ * Note: bdd2pw doesn't insert empty user-block placeholders by
49
+ * default — users opt in by editing a generated spec and adding the
50
+ * markers themselves. So in practice the splice path here is
51
+ * usually the "append at end" branch, which is intentional — once
52
+ * a user edits a spec under merge, the markers they added live
53
+ * forever inside the spec until they remove them.
54
+ */
55
+ export declare function mergeUserBlocks(next: string, preserved: Map<string, string>): string;
56
+ /**
57
+ * Prepend the generated header to a fresh spec so future merges can
58
+ * detect that the file is bdd2pw-managed.
59
+ */
60
+ export declare function prependGeneratedHeader(contents: string, version: string, source: string): string;
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ /**
3
+ * v3.2.0 — TestForge handoff Issue 7. Preserve user-edited blocks
4
+ * across regeneration.
5
+ *
6
+ * Two artefacts make this work:
7
+ *
8
+ * 1. **A generated-header marker.** Every spec emitted with `merge`
9
+ * enabled gets a leading
10
+ * `// bdd2pw:generated v=<version> source=<feature>` comment, so a
11
+ * future run can recognise its own output.
12
+ *
13
+ * 2. **User-block markers.** Anywhere in the spec, the user can wrap
14
+ * hand-edited code in:
15
+ *
16
+ * // bdd2pw:user-block id="custom-locator"
17
+ * await this.special = page.locator("[data-thing]");
18
+ * // bdd2pw:end-user-block
19
+ *
20
+ * During regeneration, blocks with the same `id` are spliced back
21
+ * into the new output. Blocks whose id is absent from the new
22
+ * output are appended at end of file under a clearly-labelled
23
+ * "stale user blocks" section so nothing is lost silently.
24
+ *
25
+ * The merge is conservative — it never touches the LINES INSIDE
26
+ * user blocks; everything else is fresh output.
27
+ *
28
+ * Why id-keyed splicing instead of line-based diff? Line numbers
29
+ * shift every time the input feature changes. Id-keyed markers
30
+ * survive arbitrary reordering and renaming.
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.isMergeAnnotated = isMergeAnnotated;
34
+ exports.extractUserBlocks = extractUserBlocks;
35
+ exports.mergeUserBlocks = mergeUserBlocks;
36
+ exports.prependGeneratedHeader = prependGeneratedHeader;
37
+ /** Match a user-block open marker plus capture its id and body. */
38
+ const USER_BLOCK_RE = /\/\/\s*bdd2pw:user-block\s+id\s*=\s*"([^"]+)"[ \t]*\r?\n([\s\S]*?)\/\/\s*bdd2pw:end-user-block/g;
39
+ /** Header comment we emit at the top of merge-enabled specs. */
40
+ function generatedHeader(version, source) {
41
+ return `// bdd2pw:generated v=${version} source=${source}`;
42
+ }
43
+ /** Detect whether a previously-emitted spec was produced with merge on. */
44
+ function isMergeAnnotated(existing) {
45
+ return /^\/\/\s*bdd2pw:generated\s/.test(existing.trimStart());
46
+ }
47
+ /** Extract every user-block in `existing`, keyed by id. */
48
+ function extractUserBlocks(existing) {
49
+ const out = new Map();
50
+ const re = new RegExp(USER_BLOCK_RE.source, "g");
51
+ let m;
52
+ while ((m = re.exec(existing)) !== null) {
53
+ const id = m[1];
54
+ const body = m[2];
55
+ // Stash the WHOLE block including markers so the splice keeps
56
+ // them in the output. (If we only captured `body`, a subsequent
57
+ // regen would lose the markers and the next regen wouldn't see
58
+ // the block.)
59
+ const full = m[0];
60
+ out.set(id, full);
61
+ }
62
+ return out;
63
+ }
64
+ /**
65
+ * Splice preserved user blocks into a freshly-rendered spec.
66
+ *
67
+ * - `next` is the new bdd2pw output (no user blocks yet).
68
+ * - `preserved` is the id → block-text map extracted from the
69
+ * PREVIOUS spec via `extractUserBlocks`.
70
+ *
71
+ * For each preserved block: if `next` contains a placeholder line
72
+ * `// bdd2pw:user-block id="<id>"` ... `// bdd2pw:end-user-block`
73
+ * (which bdd2pw never emits by itself — see note below), replace it.
74
+ * Otherwise append the block under a "stale user blocks" footer so
75
+ * the user can salvage / delete it on their own.
76
+ *
77
+ * Note: bdd2pw doesn't insert empty user-block placeholders by
78
+ * default — users opt in by editing a generated spec and adding the
79
+ * markers themselves. So in practice the splice path here is
80
+ * usually the "append at end" branch, which is intentional — once
81
+ * a user edits a spec under merge, the markers they added live
82
+ * forever inside the spec until they remove them.
83
+ */
84
+ function mergeUserBlocks(next, preserved) {
85
+ if (preserved.size === 0)
86
+ return next;
87
+ // For each preserved id, replace any matching open/close marker in
88
+ // `next`, otherwise queue for append.
89
+ let merged = next;
90
+ const stale = [];
91
+ for (const [id, block] of preserved.entries()) {
92
+ const placeholder = new RegExp(`\\/\\/\\s*bdd2pw:user-block\\s+id\\s*=\\s*"${escapeForRegex(id)}"[ \\t]*\\r?\\n[\\s\\S]*?\\/\\/\\s*bdd2pw:end-user-block`, "g");
93
+ if (placeholder.test(merged)) {
94
+ merged = merged.replace(placeholder, block);
95
+ }
96
+ else {
97
+ stale.push({ id, block });
98
+ }
99
+ }
100
+ if (stale.length === 0)
101
+ return merged;
102
+ const footer = [
103
+ "",
104
+ "// ── bdd2pw:stale-user-blocks ──────────────────────────────",
105
+ "// The following user-block sections existed in the previous",
106
+ "// generated spec but no matching id was found in the freshly-",
107
+ "// generated output. They're preserved here so you don't lose",
108
+ "// them. Move them inline (or delete them) and the next merge",
109
+ "// will pick up the change.",
110
+ ...stale.map((s) => s.block),
111
+ "// ── bdd2pw:end-stale-user-blocks ──────────────────────────",
112
+ "",
113
+ ].join("\n");
114
+ return merged + footer;
115
+ }
116
+ /**
117
+ * Prepend the generated header to a fresh spec so future merges can
118
+ * detect that the file is bdd2pw-managed.
119
+ */
120
+ function prependGeneratedHeader(contents, version, source) {
121
+ const header = generatedHeader(version, source);
122
+ // Idempotent: if the contents already start with our header, don't
123
+ // duplicate it.
124
+ if (isMergeAnnotated(contents))
125
+ return contents;
126
+ return `${header}\n${contents}`;
127
+ }
128
+ function escapeForRegex(s) {
129
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
130
+ }
131
+ //# sourceMappingURL=specMerge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"specMerge.js","sourceRoot":"","sources":["../../src/reports/specMerge.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,7 @@ const naming_1 = require("../utils/naming");
22
22
  const stepNormalizer_1 = require("./stepNormalizer");
23
23
  const stepNormalizer_2 = require("./stepNormalizer");
24
24
  const apiRules_1 = require("./apiRules");
25
+ const visibilityRules_1 = require("./visibilityRules");
25
26
  /**
26
27
  * Match every step in every scenario to a POM call / assertion / TODO.
27
28
  * Background steps are also threaded through if the orchestrator passes
@@ -85,6 +86,14 @@ const RULES = [
85
86
  // at the top guarantees deterministic interception before any UI rule
86
87
  // accidentally chews on an API-ish phrase. See src/transformers/apiRules.ts.
87
88
  ...apiRules_1.API_RULES,
89
+ // v3.1.0 — visibility rules go SECOND. TestForge handoff Issue 1:
90
+ // steps like "the user's name or profile indicator is visible in the
91
+ // UI" were falling through to the URL-slug rules and producing
92
+ // always-failing `toHaveURL(/.../)` assertions. These rules intercept
93
+ // anything matching "<noun> is visible/displayed/shown/appears" and
94
+ // either resolve a POM locator or land a clean TODO — never a URL
95
+ // regex. See src/transformers/visibilityRules.ts.
96
+ ...visibilityRules_1.VISIBILITY_RULES,
88
97
  // 1. Navigation: "I am on the login page" / "I navigate to /foo"
89
98
  {
90
99
  pattern: new RegExp(`^(?:${SUBJ} )?(?:am on|navigate to|navigates? to|open|opens?|visit|visits?|go to|goes? to|am at|is at) (?:the )?(?:.+?\\s+)?["']?([^"']+?)["']?(?: page)?$`, "i"),