@vijaypjavvadi/bdd2pw 1.0.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.
- package/CHANGELOG.md +308 -0
- package/LICENSE +21 -0
- package/README.md +254 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +176 -0
- package/dist/cli.js.map +1 -0
- package/dist/discovery/mcpClient.d.ts +53 -0
- package/dist/discovery/mcpClient.js +326 -0
- package/dist/discovery/mcpClient.js.map +1 -0
- package/dist/discovery/snapshotParser.d.ts +13 -0
- package/dist/discovery/snapshotParser.js +51 -0
- package/dist/discovery/snapshotParser.js.map +1 -0
- package/dist/emitters/facade.d.ts +52 -0
- package/dist/emitters/facade.js +125 -0
- package/dist/emitters/facade.js.map +1 -0
- package/dist/http/artifacts.d.ts +14 -0
- package/dist/http/artifacts.js +36 -0
- package/dist/http/artifacts.js.map +1 -0
- package/dist/http/jobs.d.ts +26 -0
- package/dist/http/jobs.js +59 -0
- package/dist/http/jobs.js.map +1 -0
- package/dist/http/routes.d.ts +11 -0
- package/dist/http/routes.js +267 -0
- package/dist/http/routes.js.map +1 -0
- package/dist/http/schemas.d.ts +167 -0
- package/dist/http/schemas.js +57 -0
- package/dist/http/schemas.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +507 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/governanceClient.d.ts +24 -0
- package/dist/llm/governanceClient.js +22 -0
- package/dist/llm/governanceClient.js.map +1 -0
- package/dist/llm/providers/anthropic.d.ts +8 -0
- package/dist/llm/providers/anthropic.js +12 -0
- package/dist/llm/providers/anthropic.js.map +1 -0
- package/dist/llm/providers/gemini.d.ts +5 -0
- package/dist/llm/providers/gemini.js +9 -0
- package/dist/llm/providers/gemini.js.map +1 -0
- package/dist/llm/providers/openai.d.ts +5 -0
- package/dist/llm/providers/openai.js +9 -0
- package/dist/llm/providers/openai.js.map +1 -0
- package/dist/parser/gherkinParser.d.ts +17 -0
- package/dist/parser/gherkinParser.js +143 -0
- package/dist/parser/gherkinParser.js.map +1 -0
- package/dist/repo/projectScaffolder.d.ts +24 -0
- package/dist/repo/projectScaffolder.js +64 -0
- package/dist/repo/projectScaffolder.js.map +1 -0
- package/dist/repo/repoScanner.d.ts +21 -0
- package/dist/repo/repoScanner.js +132 -0
- package/dist/repo/repoScanner.js.map +1 -0
- package/dist/reports/reviewReport.d.ts +19 -0
- package/dist/reports/reviewReport.js +96 -0
- package/dist/reports/reviewReport.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +40 -0
- package/dist/server.js.map +1 -0
- package/dist/transformers/locatorPicker.d.ts +40 -0
- package/dist/transformers/locatorPicker.js +283 -0
- package/dist/transformers/locatorPicker.js.map +1 -0
- package/dist/transformers/pomResolver.d.ts +28 -0
- package/dist/transformers/pomResolver.js +35 -0
- package/dist/transformers/pomResolver.js.map +1 -0
- package/dist/transformers/stepMatcher.d.ts +36 -0
- package/dist/transformers/stepMatcher.js +461 -0
- package/dist/transformers/stepMatcher.js.map +1 -0
- package/dist/types.d.ts +166 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +25 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/naming.d.ts +23 -0
- package/dist/utils/naming.js +58 -0
- package/dist/utils/naming.js.map +1 -0
- package/dist/validate/tscRunner.d.ts +17 -0
- package/dist/validate/tscRunner.js +98 -0
- package/dist/validate/tscRunner.js.map +1 -0
- package/package.json +69 -0
- package/templates/gitignore.tmpl +10 -0
- package/templates/package.json.tmpl +16 -0
- package/templates/playwright.config.ts.tmpl +24 -0
- package/templates/tsconfig.json.tmpl +15 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gherkin parser — wraps `@cucumber/gherkin` and produces a `FeatureIR`.
|
|
3
|
+
* See docs/ARCHITECTURE.md §2.
|
|
4
|
+
*/
|
|
5
|
+
import type { FeatureIR } from "../types";
|
|
6
|
+
export declare class GherkinParseError extends Error {
|
|
7
|
+
readonly file: string;
|
|
8
|
+
readonly line?: number | undefined;
|
|
9
|
+
constructor(message: string, file: string, line?: number | undefined);
|
|
10
|
+
}
|
|
11
|
+
export declare function parseFeature(filePath: string): Promise<FeatureIR>;
|
|
12
|
+
export declare function parseFeatureSource(source: string, sourceFile: string): FeatureIR;
|
|
13
|
+
/**
|
|
14
|
+
* Substitute `<placeholder>` tokens in a step's text with values from an
|
|
15
|
+
* Examples row. Used by the orchestrator when expanding Scenario Outlines.
|
|
16
|
+
*/
|
|
17
|
+
export declare function substituteOutlinePlaceholders(text: string, row: Record<string, string>): string;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gherkin parser — wraps `@cucumber/gherkin` and produces a `FeatureIR`.
|
|
4
|
+
* See docs/ARCHITECTURE.md §2.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.GherkinParseError = void 0;
|
|
41
|
+
exports.parseFeature = parseFeature;
|
|
42
|
+
exports.parseFeatureSource = parseFeatureSource;
|
|
43
|
+
exports.substituteOutlinePlaceholders = substituteOutlinePlaceholders;
|
|
44
|
+
const fs = __importStar(require("fs-extra"));
|
|
45
|
+
const gherkin_1 = require("@cucumber/gherkin");
|
|
46
|
+
const messages_1 = require("@cucumber/messages");
|
|
47
|
+
class GherkinParseError extends Error {
|
|
48
|
+
constructor(message, file, line) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.file = file;
|
|
51
|
+
this.line = line;
|
|
52
|
+
this.name = "GherkinParseError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.GherkinParseError = GherkinParseError;
|
|
56
|
+
async function parseFeature(filePath) {
|
|
57
|
+
if (!(await fs.pathExists(filePath))) {
|
|
58
|
+
throw new GherkinParseError(`Feature file not found: ${filePath}`, filePath);
|
|
59
|
+
}
|
|
60
|
+
const source = await fs.readFile(filePath, "utf8");
|
|
61
|
+
return parseFeatureSource(source, filePath);
|
|
62
|
+
}
|
|
63
|
+
function parseFeatureSource(source, sourceFile) {
|
|
64
|
+
const builder = new gherkin_1.AstBuilder(messages_1.IdGenerator.uuid());
|
|
65
|
+
const matcher = new gherkin_1.GherkinClassicTokenMatcher();
|
|
66
|
+
const parser = new gherkin_1.Parser(builder, matcher);
|
|
67
|
+
let document;
|
|
68
|
+
try {
|
|
69
|
+
document = parser.parse(source);
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
throw new GherkinParseError(`Failed to parse Gherkin: ${err.message}`, sourceFile);
|
|
73
|
+
}
|
|
74
|
+
const feature = document.feature;
|
|
75
|
+
if (!feature) {
|
|
76
|
+
throw new GherkinParseError(`No Feature: declaration found in ${sourceFile}`, sourceFile);
|
|
77
|
+
}
|
|
78
|
+
const tags = (feature.tags ?? []).map((t) => t.name);
|
|
79
|
+
let background;
|
|
80
|
+
const scenarios = [];
|
|
81
|
+
for (const child of feature.children ?? []) {
|
|
82
|
+
if (child.background) {
|
|
83
|
+
background = child.background.steps.map(toStepIR);
|
|
84
|
+
}
|
|
85
|
+
else if (child.scenario) {
|
|
86
|
+
scenarios.push(toScenarioIR(child.scenario));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
name: feature.name ?? "(unnamed feature)",
|
|
91
|
+
description: feature.description?.trim() || undefined,
|
|
92
|
+
background,
|
|
93
|
+
scenarios,
|
|
94
|
+
tags,
|
|
95
|
+
sourceFile,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function toScenarioIR(scenario) {
|
|
99
|
+
const steps = (scenario.steps ?? []).map(toStepIR);
|
|
100
|
+
const tags = (scenario.tags ?? []).map((t) => t.name);
|
|
101
|
+
let examples;
|
|
102
|
+
// Scenario Outline → flatten Examples table into row records
|
|
103
|
+
if (scenario.examples && scenario.examples.length > 0) {
|
|
104
|
+
examples = [];
|
|
105
|
+
for (const block of scenario.examples) {
|
|
106
|
+
const headers = (block.tableHeader?.cells ?? []).map((c) => c.value);
|
|
107
|
+
for (const row of block.tableBody ?? []) {
|
|
108
|
+
const record = {};
|
|
109
|
+
const cells = row.cells ?? [];
|
|
110
|
+
headers.forEach((h, i) => {
|
|
111
|
+
record[h] = cells[i]?.value ?? "";
|
|
112
|
+
});
|
|
113
|
+
examples.push(record);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
name: scenario.name ?? "(unnamed scenario)",
|
|
119
|
+
steps,
|
|
120
|
+
examples,
|
|
121
|
+
tags,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function toStepIR(step) {
|
|
125
|
+
const keyword = String(step.keyword ?? "").trim();
|
|
126
|
+
const text = String(step.text ?? "").trim();
|
|
127
|
+
let argument;
|
|
128
|
+
if (step.docString) {
|
|
129
|
+
argument = String(step.docString.content ?? "");
|
|
130
|
+
}
|
|
131
|
+
else if (step.dataTable) {
|
|
132
|
+
argument = (step.dataTable.rows ?? []).map((r) => (r.cells ?? []).map((c) => String(c.value ?? "")));
|
|
133
|
+
}
|
|
134
|
+
return { keyword, text, argument };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Substitute `<placeholder>` tokens in a step's text with values from an
|
|
138
|
+
* Examples row. Used by the orchestrator when expanding Scenario Outlines.
|
|
139
|
+
*/
|
|
140
|
+
function substituteOutlinePlaceholders(text, row) {
|
|
141
|
+
return text.replace(/<([^>]+)>/g, (_m, key) => row[key] ?? `<${key}>`);
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=gherkinParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gherkinParser.js","sourceRoot":"","sources":["../../src/parser/gherkinParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project scaffolder — when the target repo lacks `playwright.config.ts`,
|
|
3
|
+
* delegates to `@vijaypjavvadi/pw-emit`'s `emitProject()` to copy templates
|
|
4
|
+
* into place so the emitted spec has a runnable home.
|
|
5
|
+
*
|
|
6
|
+
* `emitProject()` is itself idempotent — files that already exist are
|
|
7
|
+
* left untouched. So calling this on a repo that already has a config
|
|
8
|
+
* is a no-op + warnings.
|
|
9
|
+
*/
|
|
10
|
+
import type { ReviewItem } from "../types";
|
|
11
|
+
export interface ScaffoldProjectOptions {
|
|
12
|
+
repoRoot: string;
|
|
13
|
+
templatesDir?: string;
|
|
14
|
+
/** Injected as `baseURL` in playwright.config.ts. */
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
/** Injected as `name` in package.json. */
|
|
17
|
+
projectName?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ScaffoldProjectResult {
|
|
20
|
+
filesWritten: string[];
|
|
21
|
+
alreadyExisted: boolean;
|
|
22
|
+
warnings: ReviewItem[];
|
|
23
|
+
}
|
|
24
|
+
export declare function scaffoldProject(opts: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Project scaffolder — when the target repo lacks `playwright.config.ts`,
|
|
4
|
+
* delegates to `@vijaypjavvadi/pw-emit`'s `emitProject()` to copy templates
|
|
5
|
+
* into place so the emitted spec has a runnable home.
|
|
6
|
+
*
|
|
7
|
+
* `emitProject()` is itself idempotent — files that already exist are
|
|
8
|
+
* left untouched. So calling this on a repo that already has a config
|
|
9
|
+
* is a no-op + warnings.
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.scaffoldProject = scaffoldProject;
|
|
46
|
+
const fs = __importStar(require("fs-extra"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const pw_emit_1 = require("@vijaypjavvadi/pw-emit");
|
|
49
|
+
async function scaffoldProject(opts) {
|
|
50
|
+
const configPath = path.join(opts.repoRoot, "playwright.config.ts");
|
|
51
|
+
const alreadyExisted = await fs.pathExists(configPath);
|
|
52
|
+
const result = await (0, pw_emit_1.emitProject)({
|
|
53
|
+
outDir: opts.repoRoot,
|
|
54
|
+
templatesDir: opts.templatesDir,
|
|
55
|
+
baseUrl: opts.baseUrl,
|
|
56
|
+
projectName: opts.projectName,
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
filesWritten: result.filesWritten,
|
|
60
|
+
alreadyExisted,
|
|
61
|
+
warnings: result.warnings,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=projectScaffolder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectScaffolder.js","sourceRoot":"","sources":["../../src/repo/projectScaffolder.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBH,0CAkBC;AAtCD,6CAA+B;AAC/B,2CAA6B;AAC7B,oDAAqD;AAkB9C,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;KAC9B,CAAC,CAAC;IAEH,OAAO;QACL,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,cAAc;QACd,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo scanner — reads `pages/*.page.ts` via `ts-morph` and builds a map of
|
|
3
|
+
* existing Page Object classes (fields + methods) so the resolver can
|
|
4
|
+
* decide reuse / augment / create.
|
|
5
|
+
*
|
|
6
|
+
* What it extracts per class:
|
|
7
|
+
* - className
|
|
8
|
+
* - field names + their initialiser locator expression (best-effort, so
|
|
9
|
+
* pomResolver can decide presence; not used for re-emission)
|
|
10
|
+
* - method names + parameter signatures + raw body
|
|
11
|
+
*
|
|
12
|
+
* Out of scope: detecting decorators, generic constraints, inherited fields.
|
|
13
|
+
* Augment-mode emission preserves whole methods verbatim (origin: 'existing').
|
|
14
|
+
*/
|
|
15
|
+
import type { PageObjectIR } from "../types";
|
|
16
|
+
export interface RepoScanResult {
|
|
17
|
+
repoRoot: string;
|
|
18
|
+
hasPlaywrightConfig: boolean;
|
|
19
|
+
pageObjects: Map<string, PageObjectIR>;
|
|
20
|
+
}
|
|
21
|
+
export declare function scanRepo(repoRoot: string): Promise<RepoScanResult>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Repo scanner — reads `pages/*.page.ts` via `ts-morph` and builds a map of
|
|
4
|
+
* existing Page Object classes (fields + methods) so the resolver can
|
|
5
|
+
* decide reuse / augment / create.
|
|
6
|
+
*
|
|
7
|
+
* What it extracts per class:
|
|
8
|
+
* - className
|
|
9
|
+
* - field names + their initialiser locator expression (best-effort, so
|
|
10
|
+
* pomResolver can decide presence; not used for re-emission)
|
|
11
|
+
* - method names + parameter signatures + raw body
|
|
12
|
+
*
|
|
13
|
+
* Out of scope: detecting decorators, generic constraints, inherited fields.
|
|
14
|
+
* Augment-mode emission preserves whole methods verbatim (origin: 'existing').
|
|
15
|
+
*/
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.scanRepo = scanRepo;
|
|
51
|
+
const fs = __importStar(require("fs-extra"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
const ts_morph_1 = require("ts-morph");
|
|
54
|
+
async function scanRepo(repoRoot) {
|
|
55
|
+
const pageObjects = new Map();
|
|
56
|
+
const hasPlaywrightConfig = await fs.pathExists(path.join(repoRoot, "playwright.config.ts"));
|
|
57
|
+
const pagesDir = path.join(repoRoot, "pages");
|
|
58
|
+
if (!(await fs.pathExists(pagesDir))) {
|
|
59
|
+
return { repoRoot, hasPlaywrightConfig, pageObjects };
|
|
60
|
+
}
|
|
61
|
+
const project = new ts_morph_1.Project({
|
|
62
|
+
skipAddingFilesFromTsConfig: true,
|
|
63
|
+
compilerOptions: { allowJs: false, noEmit: true, target: 99 /* ESNext */ },
|
|
64
|
+
});
|
|
65
|
+
project.addSourceFilesAtPaths(path.join(pagesDir, "*.page.ts"));
|
|
66
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
67
|
+
const filePath = sourceFile.getFilePath().toString();
|
|
68
|
+
for (const cls of sourceFile.getClasses()) {
|
|
69
|
+
const className = cls.getName();
|
|
70
|
+
if (!className)
|
|
71
|
+
continue;
|
|
72
|
+
const fields = cls
|
|
73
|
+
.getProperties()
|
|
74
|
+
.filter((p) => {
|
|
75
|
+
const t = p.getType().getText();
|
|
76
|
+
// Heuristic: keep `Locator` typed fields; skip `Page`, `string`, etc.
|
|
77
|
+
return t.includes("Locator");
|
|
78
|
+
})
|
|
79
|
+
.map((p) => parseLocatorField(p.getName(), p.getInitializer()?.getText()));
|
|
80
|
+
const methods = cls.getMethods().map((m) => {
|
|
81
|
+
const params = m.getParameters().map((p) => ({
|
|
82
|
+
name: p.getName(),
|
|
83
|
+
type: p.getType().getText() || "unknown",
|
|
84
|
+
}));
|
|
85
|
+
const body = m.getBodyText() ?? "";
|
|
86
|
+
return {
|
|
87
|
+
name: m.getName(),
|
|
88
|
+
params,
|
|
89
|
+
body,
|
|
90
|
+
origin: "existing",
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
pageObjects.set(className, {
|
|
94
|
+
className,
|
|
95
|
+
filePath,
|
|
96
|
+
fields,
|
|
97
|
+
methods,
|
|
98
|
+
exists: true,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { repoRoot, hasPlaywrightConfig, pageObjects };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parse a class field initialiser like
|
|
106
|
+
* `page.getByRole('button', { name: 'Sign in' })`
|
|
107
|
+
* back into a `LocatorChoice`. Best-effort — we mainly need the
|
|
108
|
+
* `fieldName` for the resolver's "is this field present?" check; the
|
|
109
|
+
* `api`/`args` are advisory.
|
|
110
|
+
*/
|
|
111
|
+
function parseLocatorField(fieldName, init) {
|
|
112
|
+
const fallback = {
|
|
113
|
+
api: "locator",
|
|
114
|
+
args: '""',
|
|
115
|
+
fieldName,
|
|
116
|
+
source: { tag: "" },
|
|
117
|
+
confidence: "fallback",
|
|
118
|
+
};
|
|
119
|
+
if (!init)
|
|
120
|
+
return fallback;
|
|
121
|
+
const m = init.match(/(?:this\.)?page\.(getByRole|getByLabel|getByPlaceholder|getByTestId|getByText|locator)\s*\(([\s\S]*)\)\s*;?\s*$/);
|
|
122
|
+
if (!m)
|
|
123
|
+
return fallback;
|
|
124
|
+
return {
|
|
125
|
+
api: m[1],
|
|
126
|
+
args: m[2].trim(),
|
|
127
|
+
fieldName,
|
|
128
|
+
source: { tag: "" },
|
|
129
|
+
confidence: "unique",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=repoScanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repoScanner.js","sourceRoot":"","sources":["../../src/repo/repoScanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate `BDD_REVIEW.md` — every warning, fallback, ambiguity, and
|
|
3
|
+
* `tsc --noEmit` diagnostic for human review. See docs/ARCHITECTURE.md §10.
|
|
4
|
+
*
|
|
5
|
+
* The report is the deliverable, not a clean compile (FR-12). Sections:
|
|
6
|
+
* 1. Summary (counts by severity)
|
|
7
|
+
* 2. Errors (block testing — fix first)
|
|
8
|
+
* 3. Warnings (likely correct but worth a glance)
|
|
9
|
+
* 4. Info (FYI, ambiguous locators, fallback steps, etc.)
|
|
10
|
+
* 5. Manual TODOs — every step that fell through to a TODO comment
|
|
11
|
+
*/
|
|
12
|
+
import type { ReviewItem } from "../types";
|
|
13
|
+
export interface WriteReviewInput {
|
|
14
|
+
repoRoot: string;
|
|
15
|
+
items: ReviewItem[];
|
|
16
|
+
feature: string;
|
|
17
|
+
url: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function writeReviewReport(input: WriteReviewInput): Promise<string>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Generate `BDD_REVIEW.md` — every warning, fallback, ambiguity, and
|
|
4
|
+
* `tsc --noEmit` diagnostic for human review. See docs/ARCHITECTURE.md §10.
|
|
5
|
+
*
|
|
6
|
+
* The report is the deliverable, not a clean compile (FR-12). Sections:
|
|
7
|
+
* 1. Summary (counts by severity)
|
|
8
|
+
* 2. Errors (block testing — fix first)
|
|
9
|
+
* 3. Warnings (likely correct but worth a glance)
|
|
10
|
+
* 4. Info (FYI, ambiguous locators, fallback steps, etc.)
|
|
11
|
+
* 5. Manual TODOs — every step that fell through to a TODO comment
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.writeReviewReport = writeReviewReport;
|
|
48
|
+
const fs = __importStar(require("fs-extra"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
async function writeReviewReport(input) {
|
|
51
|
+
const lines = [];
|
|
52
|
+
const counts = {
|
|
53
|
+
error: input.items.filter((i) => i.severity === "error").length,
|
|
54
|
+
warn: input.items.filter((i) => i.severity === "warn").length,
|
|
55
|
+
info: input.items.filter((i) => i.severity === "info").length,
|
|
56
|
+
};
|
|
57
|
+
lines.push("# bdd2pw — Conversion Review");
|
|
58
|
+
lines.push("");
|
|
59
|
+
lines.push(`> **Source feature:** \`${input.feature}\``);
|
|
60
|
+
lines.push(`> **Target URL:** ${input.url}`);
|
|
61
|
+
lines.push(`> **Generated:** ${new Date().toISOString()}`);
|
|
62
|
+
lines.push("");
|
|
63
|
+
lines.push("## Summary");
|
|
64
|
+
lines.push("");
|
|
65
|
+
lines.push(`${counts.error} error${counts.error === 1 ? "" : "s"} · ${counts.warn} warning${counts.warn === 1 ? "" : "s"} · ${counts.info} info`);
|
|
66
|
+
lines.push("");
|
|
67
|
+
emitSection(lines, "Errors", "error", input.items);
|
|
68
|
+
emitSection(lines, "Warnings", "warn", input.items);
|
|
69
|
+
emitSection(lines, "Info", "info", input.items);
|
|
70
|
+
if (input.items.length === 0) {
|
|
71
|
+
lines.push("Nothing to review — everything mapped cleanly. Run `npx playwright test` to verify.");
|
|
72
|
+
lines.push("");
|
|
73
|
+
}
|
|
74
|
+
const outPath = path.join(input.repoRoot, "BDD_REVIEW.md");
|
|
75
|
+
await fs.ensureDir(input.repoRoot);
|
|
76
|
+
await fs.writeFile(outPath, lines.join("\n"), "utf8");
|
|
77
|
+
return outPath;
|
|
78
|
+
}
|
|
79
|
+
function emitSection(lines, heading, severity, items) {
|
|
80
|
+
const filtered = items.filter((i) => i.severity === severity);
|
|
81
|
+
if (filtered.length === 0)
|
|
82
|
+
return;
|
|
83
|
+
lines.push(`## ${heading}`);
|
|
84
|
+
lines.push("");
|
|
85
|
+
for (const item of filtered) {
|
|
86
|
+
const loc = item.file !== undefined
|
|
87
|
+
? `\`${item.file}${item.line ? `:${item.line}` : ""}\` — `
|
|
88
|
+
: "";
|
|
89
|
+
lines.push(`- ${loc}${item.message}`);
|
|
90
|
+
if (item.suggestion) {
|
|
91
|
+
lines.push(` - Suggestion: ${item.suggestion}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
lines.push("");
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=reviewReport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reviewReport.js","sourceRoot":"","sources":["../../src/reports/reviewReport.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP service entry. Express :4300. See docs/SCOPE.md §8b
|
|
3
|
+
* and docs/ARCHITECTURE.md §3 + §6.
|
|
4
|
+
*/
|
|
5
|
+
import { Application } from "express";
|
|
6
|
+
export interface ServerOptions {
|
|
7
|
+
port?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function createApp(): Application;
|
|
10
|
+
export declare function startServer(opts?: ServerOptions): Promise<void>;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HTTP service entry. Express :4300. See docs/SCOPE.md §8b
|
|
4
|
+
* and docs/ARCHITECTURE.md §3 + §6.
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.createApp = createApp;
|
|
11
|
+
exports.startServer = startServer;
|
|
12
|
+
const express_1 = __importDefault(require("express"));
|
|
13
|
+
const logger_1 = require("./utils/logger");
|
|
14
|
+
const routes_1 = require("./http/routes");
|
|
15
|
+
function createApp() {
|
|
16
|
+
const app = (0, express_1.default)();
|
|
17
|
+
app.use(express_1.default.json({ limit: "1mb" }));
|
|
18
|
+
app.get("/healthz", (_req, res) => res.json({ ok: true }));
|
|
19
|
+
app.get("/version", (_req, res) => res.json({
|
|
20
|
+
name: "@vijaypjavvadi/bdd2pw",
|
|
21
|
+
version: require("../package.json").version,
|
|
22
|
+
commit: process.env.GIT_COMMIT ?? "unknown",
|
|
23
|
+
}));
|
|
24
|
+
(0, routes_1.mountRoutes)(app);
|
|
25
|
+
return app;
|
|
26
|
+
}
|
|
27
|
+
async function startServer(opts = {}) {
|
|
28
|
+
const port = opts.port ?? 4300;
|
|
29
|
+
const app = createApp();
|
|
30
|
+
app.listen(port, () => {
|
|
31
|
+
logger_1.logger.info({ port }, "bdd2pw HTTP service listening");
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (require.main === module) {
|
|
35
|
+
startServer({ port: Number(process.env.PORT ?? 4300) }).catch((err) => {
|
|
36
|
+
logger_1.logger.error({ err }, "server failed to start");
|
|
37
|
+
process.exit(1);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;;;AAUH,8BAgBC;AAED,kCAMC;AAhCD,sDAA+C;AAC/C,2CAAwC;AACxC,0CAA4C;AAM5C,SAAgB,SAAS;IACvB,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAExC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3D,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAChC,GAAG,CAAC,IAAI,CAAC;QACP,IAAI,EAAE,uBAAuB;QAC7B,OAAO,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAC,OAAO;QAC3C,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,SAAS;KAC5C,CAAC,CACH,CAAC;IAEF,IAAA,oBAAW,EAAC,GAAG,CAAC,CAAC;IAEjB,OAAO,GAAG,CAAC;AACb,CAAC;AAEM,KAAK,UAAU,WAAW,CAAC,OAAsB,EAAE;IACxD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC;IAC/B,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACpB,eAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,+BAA+B,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC5B,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACpE,eAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,wBAAwB,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locator picker — given an `ElementIR` and the full snapshot context,
|
|
3
|
+
* choose the most stable Playwright locator API.
|
|
4
|
+
*
|
|
5
|
+
* Priority chain (FR-6):
|
|
6
|
+
* getByRole > getByLabel > getByPlaceholder > getByTestId > getByText
|
|
7
|
+
* > locator(css) > locator(xpath)
|
|
8
|
+
*
|
|
9
|
+
* The output `LocatorChoice` is then handed to `@vijaypjavvadi/pw-emit`
|
|
10
|
+
* which renders it into TS. We never render here.
|
|
11
|
+
*/
|
|
12
|
+
import type { ElementIR, LocatorChoice } from "../types";
|
|
13
|
+
/**
|
|
14
|
+
* Pick the highest-priority unique locator for `element`. Uniqueness is
|
|
15
|
+
* checked against `allElements` — if multiple elements would match the
|
|
16
|
+
* candidate locator, we fall down the chain.
|
|
17
|
+
*/
|
|
18
|
+
export declare function pickLocator(element: ElementIR, allElements: ElementIR[]): LocatorChoice;
|
|
19
|
+
/**
|
|
20
|
+
* Dedupe a list of locator choices.
|
|
21
|
+
*
|
|
22
|
+
* 1. **By locator identity (api + args).** When two `LocatorChoice`s
|
|
23
|
+
* resolve to the same Playwright call (e.g. a `<label>` and the
|
|
24
|
+
* `<input>` it labels both produce `getByLabel('Username')`), keep
|
|
25
|
+
* only the FIRST. Without this, the live scanner emits
|
|
26
|
+
* `username = page.getByLabel('Username')` AND
|
|
27
|
+
* `usernameInput = page.getByLabel('Username')` — two fields, one
|
|
28
|
+
* element, confusing for tests and noisy in the POM.
|
|
29
|
+
* The kept entry uses the LONGER fieldName when names differ —
|
|
30
|
+
* `usernameInput` is more useful than `username` because the
|
|
31
|
+
* step-matcher's preferredSuffix logic looks for `*Input`.
|
|
32
|
+
*
|
|
33
|
+
* 2. **By field name collision.** If two elements legitimately deserve
|
|
34
|
+
* different `LocatorChoice`s but happen to synthesise the same
|
|
35
|
+
* fieldName, suffix the later one with `2`, `3`, … so emitted TS
|
|
36
|
+
* stays valid.
|
|
37
|
+
*
|
|
38
|
+
* Stable-sorted by input order otherwise.
|
|
39
|
+
*/
|
|
40
|
+
export declare function dedupeLocators(choices: LocatorChoice[]): LocatorChoice[];
|