@yasserkhanorg/e2e-agents 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/LICENSE +168 -0
  2. package/README.md +620 -0
  3. package/dist/agent/analysis.d.ts +62 -0
  4. package/dist/agent/analysis.d.ts.map +1 -0
  5. package/dist/agent/analysis.js +292 -0
  6. package/dist/agent/blast_radius.d.ts +4 -0
  7. package/dist/agent/blast_radius.d.ts.map +1 -0
  8. package/dist/agent/blast_radius.js +37 -0
  9. package/dist/agent/cache_utils.d.ts +38 -0
  10. package/dist/agent/cache_utils.d.ts.map +1 -0
  11. package/dist/agent/cache_utils.js +67 -0
  12. package/dist/agent/config.d.ts +148 -0
  13. package/dist/agent/config.d.ts.map +1 -0
  14. package/dist/agent/config.js +640 -0
  15. package/dist/agent/dependency_graph.d.ts +14 -0
  16. package/dist/agent/dependency_graph.d.ts.map +1 -0
  17. package/dist/agent/dependency_graph.js +227 -0
  18. package/dist/agent/feedback.d.ts +55 -0
  19. package/dist/agent/feedback.d.ts.map +1 -0
  20. package/dist/agent/feedback.js +257 -0
  21. package/dist/agent/flags.d.ts +23 -0
  22. package/dist/agent/flags.d.ts.map +1 -0
  23. package/dist/agent/flags.js +171 -0
  24. package/dist/agent/flow_catalog.d.ts +25 -0
  25. package/dist/agent/flow_catalog.d.ts.map +1 -0
  26. package/dist/agent/flow_catalog.js +106 -0
  27. package/dist/agent/flow_mapping.d.ts +10 -0
  28. package/dist/agent/flow_mapping.d.ts.map +1 -0
  29. package/dist/agent/flow_mapping.js +84 -0
  30. package/dist/agent/framework.d.ts +13 -0
  31. package/dist/agent/framework.d.ts.map +1 -0
  32. package/dist/agent/framework.js +149 -0
  33. package/dist/agent/gap_suggestions.d.ts +14 -0
  34. package/dist/agent/gap_suggestions.d.ts.map +1 -0
  35. package/dist/agent/gap_suggestions.js +101 -0
  36. package/dist/agent/generator.d.ts +10 -0
  37. package/dist/agent/generator.d.ts.map +1 -0
  38. package/dist/agent/generator.js +115 -0
  39. package/dist/agent/git.d.ts +11 -0
  40. package/dist/agent/git.d.ts.map +1 -0
  41. package/dist/agent/git.js +90 -0
  42. package/dist/agent/handoff.d.ts +22 -0
  43. package/dist/agent/handoff.d.ts.map +1 -0
  44. package/dist/agent/handoff.js +180 -0
  45. package/dist/agent/impact-analyzer.d.ts +114 -0
  46. package/dist/agent/impact-analyzer.d.ts.map +1 -0
  47. package/dist/agent/impact-analyzer.js +557 -0
  48. package/dist/agent/index.d.ts +21 -0
  49. package/dist/agent/index.d.ts.map +1 -0
  50. package/dist/agent/index.js +38 -0
  51. package/dist/agent/model-router.d.ts +57 -0
  52. package/dist/agent/model-router.d.ts.map +1 -0
  53. package/dist/agent/model-router.js +154 -0
  54. package/dist/agent/operational_insights.d.ts +41 -0
  55. package/dist/agent/operational_insights.d.ts.map +1 -0
  56. package/dist/agent/operational_insights.js +126 -0
  57. package/dist/agent/pipeline.d.ts +23 -0
  58. package/dist/agent/pipeline.d.ts.map +1 -0
  59. package/dist/agent/pipeline.js +609 -0
  60. package/dist/agent/plan.d.ts +91 -0
  61. package/dist/agent/plan.d.ts.map +1 -0
  62. package/dist/agent/plan.js +331 -0
  63. package/dist/agent/playwright_report.d.ts +8 -0
  64. package/dist/agent/playwright_report.d.ts.map +1 -0
  65. package/dist/agent/playwright_report.js +126 -0
  66. package/dist/agent/report-generator.d.ts +24 -0
  67. package/dist/agent/report-generator.d.ts.map +1 -0
  68. package/dist/agent/report-generator.js +250 -0
  69. package/dist/agent/report.d.ts +81 -0
  70. package/dist/agent/report.d.ts.map +1 -0
  71. package/dist/agent/report.js +147 -0
  72. package/dist/agent/runner.d.ts +7 -0
  73. package/dist/agent/runner.d.ts.map +1 -0
  74. package/dist/agent/runner.js +576 -0
  75. package/dist/agent/selectors.d.ts +10 -0
  76. package/dist/agent/selectors.d.ts.map +1 -0
  77. package/dist/agent/selectors.js +75 -0
  78. package/dist/agent/spec-bridge.d.ts +101 -0
  79. package/dist/agent/spec-bridge.d.ts.map +1 -0
  80. package/dist/agent/spec-bridge.js +273 -0
  81. package/dist/agent/spec-builder.d.ts +102 -0
  82. package/dist/agent/spec-builder.d.ts.map +1 -0
  83. package/dist/agent/spec-builder.js +273 -0
  84. package/dist/agent/subsystem_risk.d.ts +23 -0
  85. package/dist/agent/subsystem_risk.d.ts.map +1 -0
  86. package/dist/agent/subsystem_risk.js +207 -0
  87. package/dist/agent/telemetry.d.ts +84 -0
  88. package/dist/agent/telemetry.d.ts.map +1 -0
  89. package/dist/agent/telemetry.js +220 -0
  90. package/dist/agent/test_path.d.ts +2 -0
  91. package/dist/agent/test_path.d.ts.map +1 -0
  92. package/dist/agent/test_path.js +23 -0
  93. package/dist/agent/tests.d.ts +18 -0
  94. package/dist/agent/tests.d.ts.map +1 -0
  95. package/dist/agent/tests.js +106 -0
  96. package/dist/agent/traceability.d.ts +22 -0
  97. package/dist/agent/traceability.d.ts.map +1 -0
  98. package/dist/agent/traceability.js +183 -0
  99. package/dist/agent/traceability_capture.d.ts +18 -0
  100. package/dist/agent/traceability_capture.d.ts.map +1 -0
  101. package/dist/agent/traceability_capture.js +313 -0
  102. package/dist/agent/traceability_ingest.d.ts +21 -0
  103. package/dist/agent/traceability_ingest.d.ts.map +1 -0
  104. package/dist/agent/traceability_ingest.js +237 -0
  105. package/dist/agent/utils.d.ts +13 -0
  106. package/dist/agent/utils.d.ts.map +1 -0
  107. package/dist/agent/utils.js +152 -0
  108. package/dist/agent/validators/selector-validator.d.ts +74 -0
  109. package/dist/agent/validators/selector-validator.d.ts.map +1 -0
  110. package/dist/agent/validators/selector-validator.js +165 -0
  111. package/dist/anthropic_provider.d.ts +65 -0
  112. package/dist/anthropic_provider.d.ts.map +1 -0
  113. package/dist/anthropic_provider.js +332 -0
  114. package/dist/api.d.ts +48 -0
  115. package/dist/api.d.ts.map +1 -0
  116. package/dist/api.js +113 -0
  117. package/dist/base_provider.d.ts +53 -0
  118. package/dist/base_provider.d.ts.map +1 -0
  119. package/dist/base_provider.js +81 -0
  120. package/dist/cli.d.ts +3 -0
  121. package/dist/cli.d.ts.map +1 -0
  122. package/dist/cli.js +843 -0
  123. package/dist/custom_provider.d.ts +20 -0
  124. package/dist/custom_provider.d.ts.map +1 -0
  125. package/dist/custom_provider.js +276 -0
  126. package/dist/e2e-test-gen/index.d.ts +51 -0
  127. package/dist/e2e-test-gen/index.d.ts.map +1 -0
  128. package/dist/e2e-test-gen/index.js +57 -0
  129. package/dist/e2e-test-gen/spec_parser.d.ts +142 -0
  130. package/dist/e2e-test-gen/spec_parser.d.ts.map +1 -0
  131. package/dist/e2e-test-gen/spec_parser.js +786 -0
  132. package/dist/e2e-test-gen/types.d.ts +185 -0
  133. package/dist/e2e-test-gen/types.d.ts.map +1 -0
  134. package/dist/e2e-test-gen/types.js +4 -0
  135. package/dist/esm/agent/analysis.js +287 -0
  136. package/dist/esm/agent/blast_radius.js +34 -0
  137. package/dist/esm/agent/cache_utils.js +63 -0
  138. package/dist/esm/agent/config.js +637 -0
  139. package/dist/esm/agent/dependency_graph.js +224 -0
  140. package/dist/esm/agent/feedback.js +253 -0
  141. package/dist/esm/agent/flags.js +160 -0
  142. package/dist/esm/agent/flow_catalog.js +103 -0
  143. package/dist/esm/agent/flow_mapping.js +81 -0
  144. package/dist/esm/agent/framework.js +145 -0
  145. package/dist/esm/agent/gap_suggestions.js +98 -0
  146. package/dist/esm/agent/generator.js +112 -0
  147. package/dist/esm/agent/git.js +87 -0
  148. package/dist/esm/agent/handoff.js +177 -0
  149. package/dist/esm/agent/impact-analyzer.js +548 -0
  150. package/dist/esm/agent/index.js +22 -0
  151. package/dist/esm/agent/model-router.js +150 -0
  152. package/dist/esm/agent/operational_insights.js +123 -0
  153. package/dist/esm/agent/pipeline.js +605 -0
  154. package/dist/esm/agent/plan.js +324 -0
  155. package/dist/esm/agent/playwright_report.js +123 -0
  156. package/dist/esm/agent/report-generator.js +247 -0
  157. package/dist/esm/agent/report.js +144 -0
  158. package/dist/esm/agent/runner.js +572 -0
  159. package/dist/esm/agent/selectors.js +71 -0
  160. package/dist/esm/agent/spec-bridge.js +267 -0
  161. package/dist/esm/agent/spec-builder.js +267 -0
  162. package/dist/esm/agent/subsystem_risk.js +204 -0
  163. package/dist/esm/agent/telemetry.js +216 -0
  164. package/dist/esm/agent/test_path.js +20 -0
  165. package/dist/esm/agent/tests.js +101 -0
  166. package/dist/esm/agent/traceability.js +180 -0
  167. package/dist/esm/agent/traceability_capture.js +310 -0
  168. package/dist/esm/agent/traceability_ingest.js +234 -0
  169. package/dist/esm/agent/utils.js +138 -0
  170. package/dist/esm/agent/validators/selector-validator.js +160 -0
  171. package/dist/esm/anthropic_provider.js +324 -0
  172. package/dist/esm/api.js +105 -0
  173. package/dist/esm/base_provider.js +77 -0
  174. package/dist/esm/cli.js +841 -0
  175. package/dist/esm/custom_provider.js +272 -0
  176. package/dist/esm/e2e-test-gen/index.js +50 -0
  177. package/dist/esm/e2e-test-gen/spec_parser.js +782 -0
  178. package/dist/esm/e2e-test-gen/types.js +3 -0
  179. package/dist/esm/index.js +16 -0
  180. package/dist/esm/logger.js +89 -0
  181. package/dist/esm/mcp-server.js +465 -0
  182. package/dist/esm/ollama_provider.js +300 -0
  183. package/dist/esm/openai_provider.js +242 -0
  184. package/dist/esm/package.json +3 -0
  185. package/dist/esm/plan-and-test-constants.js +126 -0
  186. package/dist/esm/provider_factory.js +336 -0
  187. package/dist/esm/provider_interface.js +23 -0
  188. package/dist/esm/provider_utils.js +96 -0
  189. package/dist/index.d.ts +31 -0
  190. package/dist/index.d.ts.map +1 -0
  191. package/dist/index.js +41 -0
  192. package/dist/logger.d.ts +23 -0
  193. package/dist/logger.d.ts.map +1 -0
  194. package/dist/logger.js +93 -0
  195. package/dist/mcp-server.d.ts +35 -0
  196. package/dist/mcp-server.d.ts.map +1 -0
  197. package/dist/mcp-server.js +469 -0
  198. package/dist/ollama_provider.d.ts +65 -0
  199. package/dist/ollama_provider.d.ts.map +1 -0
  200. package/dist/ollama_provider.js +308 -0
  201. package/dist/openai_provider.d.ts +23 -0
  202. package/dist/openai_provider.d.ts.map +1 -0
  203. package/dist/openai_provider.js +250 -0
  204. package/dist/plan-and-test-constants.d.ts +110 -0
  205. package/dist/plan-and-test-constants.d.ts.map +1 -0
  206. package/dist/plan-and-test-constants.js +132 -0
  207. package/dist/provider_factory.d.ts +99 -0
  208. package/dist/provider_factory.d.ts.map +1 -0
  209. package/dist/provider_factory.js +341 -0
  210. package/dist/provider_interface.d.ts +358 -0
  211. package/dist/provider_interface.d.ts.map +1 -0
  212. package/dist/provider_interface.js +28 -0
  213. package/dist/provider_utils.d.ts +39 -0
  214. package/dist/provider_utils.d.ts.map +1 -0
  215. package/dist/provider_utils.js +103 -0
  216. package/package.json +101 -0
  217. package/schemas/gap.schema.json +18 -0
  218. package/schemas/impact.schema.json +418 -0
  219. package/schemas/plan.schema.json +285 -0
  220. package/schemas/subsystem-risk-map.schema.json +62 -0
  221. package/schemas/traceability-input.schema.json +122 -0
@@ -0,0 +1,267 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ /**
4
+ * Spec Bridge - Bridge between PDF specs and Playwright Agent workflow
5
+ *
6
+ * This module converts specification documents (PDF, Markdown, JSON) into
7
+ * Playwright-compatible markdown files that can be consumed by Playwright's
8
+ * native agents (Planner, Generator, Healer).
9
+ *
10
+ * Flow: PDF → SpecParser → Markdown specs → Playwright Planner
11
+ *
12
+ * The Playwright agents (available in Playwright 1.56+) provide production-ready:
13
+ * - Test planning and exploration
14
+ * - Test code generation
15
+ * - Automatic test healing
16
+ *
17
+ * Usage:
18
+ * ```typescript
19
+ * const bridge = new SpecBridge({type: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY});
20
+ * const specPaths = await bridge.convertToPlaywrightSpecs('spec.pdf', 'specs/');
21
+ * // Now use Playwright agents: @planner, @generator, @healer
22
+ * ```
23
+ */
24
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
25
+ import { join, basename } from 'path';
26
+ import { LLMProviderFactory } from '../index.js';
27
+ import { SpecificationParser } from '../e2e-test-gen/spec_parser';
28
+ /**
29
+ * Bridge between PDF/JSON specs and Playwright Agent workflow
30
+ *
31
+ * Converts specification documents into Playwright-compatible markdown
32
+ * that can be consumed by Playwright's native agents.
33
+ */
34
+ export class SpecBridge {
35
+ constructor(config) {
36
+ // Handle both single provider and hybrid config
37
+ let llmProvider;
38
+ if ('primary' in config.llmConfig) {
39
+ // It's a HybridConfig
40
+ llmProvider = LLMProviderFactory.createHybrid(config.llmConfig);
41
+ }
42
+ else {
43
+ // It's a ProviderConfig
44
+ llmProvider = LLMProviderFactory.create(config.llmConfig);
45
+ }
46
+ this.parser = new SpecificationParser(llmProvider);
47
+ this.outputDir = config.outputDir || 'specs';
48
+ this.overwrite = config.overwrite !== false;
49
+ }
50
+ /**
51
+ * Convert a specification file to Playwright-compatible markdown specs
52
+ *
53
+ * @param inputPath - Path to the input specification (PDF, MD, or JSON)
54
+ * @param outputDir - Optional override for output directory
55
+ * @returns Conversion result with paths and metadata
56
+ */
57
+ async convertToPlaywrightSpecs(inputPath, outputDir) {
58
+ /* eslint-disable no-console */
59
+ const targetDir = outputDir || this.outputDir;
60
+ const warnings = [];
61
+ console.log(`Converting specification: ${inputPath}`);
62
+ // Parse the specification
63
+ const specs = await this.parser.parse(inputPath, 'file');
64
+ if (specs.length === 0) {
65
+ warnings.push('No features extracted from specification');
66
+ return {
67
+ specPaths: [],
68
+ features: [],
69
+ totalScenarios: 0,
70
+ warnings,
71
+ };
72
+ }
73
+ // Create output directory
74
+ mkdirSync(targetDir, { recursive: true });
75
+ const specPaths = [];
76
+ let totalScenarios = 0;
77
+ for (const spec of specs) {
78
+ // Validate the spec
79
+ const validation = this.parser.validateSpec(spec);
80
+ if (!validation.valid) {
81
+ warnings.push(`Feature "${spec.name}": ${validation.errors.join(', ')}`);
82
+ }
83
+ // Convert to Playwright markdown format
84
+ const markdown = this.toPlaywrightMarkdown(spec);
85
+ // Generate output filename
86
+ const filename = `${spec.id}.md`;
87
+ const outputPath = join(targetDir, filename);
88
+ // Check for existing file
89
+ if (existsSync(outputPath) && !this.overwrite) {
90
+ warnings.push(`Skipping existing file: ${outputPath}`);
91
+ continue;
92
+ }
93
+ // Write the spec file
94
+ writeFileSync(outputPath, markdown, 'utf-8');
95
+ specPaths.push(outputPath);
96
+ totalScenarios += spec.scenarios.length;
97
+ console.log(` Created: ${outputPath} (${spec.scenarios.length} scenarios)`);
98
+ }
99
+ console.log(`Converted ${specs.length} feature(s) with ${totalScenarios} scenario(s)`);
100
+ /* eslint-enable no-console */
101
+ return {
102
+ specPaths,
103
+ features: specs,
104
+ totalScenarios,
105
+ warnings,
106
+ };
107
+ }
108
+ /**
109
+ * Convert a FeatureSpecification to Playwright Agent markdown format
110
+ *
111
+ * The format is designed to be consumed by Playwright's Planner and Generator agents.
112
+ * It includes structured test scenarios in Given-When-Then format.
113
+ */
114
+ toPlaywrightMarkdown(spec) {
115
+ const lines = [];
116
+ // Header
117
+ lines.push(`# ${spec.name}`);
118
+ lines.push('');
119
+ // Metadata
120
+ lines.push(`**Priority**: ${spec.priority}`);
121
+ if (spec.targetUrls.length > 0) {
122
+ lines.push(`**Target URLs**: ${spec.targetUrls.join(', ')}`);
123
+ }
124
+ lines.push('');
125
+ // Description
126
+ if (spec.description) {
127
+ lines.push('## Description');
128
+ lines.push('');
129
+ lines.push(spec.description);
130
+ lines.push('');
131
+ }
132
+ // Test Scenarios
133
+ if (spec.scenarios.length > 0) {
134
+ lines.push('## Test Scenarios');
135
+ lines.push('');
136
+ for (const scenario of spec.scenarios) {
137
+ lines.push(this.formatScenario(scenario));
138
+ lines.push('');
139
+ }
140
+ }
141
+ // Acceptance Criteria
142
+ if (spec.acceptanceCriteria.length > 0) {
143
+ lines.push('## Acceptance Criteria');
144
+ lines.push('');
145
+ for (const criterion of spec.acceptanceCriteria) {
146
+ lines.push(`- ${criterion}`);
147
+ }
148
+ lines.push('');
149
+ }
150
+ // Screenshots reference (if any)
151
+ if (spec.screenshots.length > 0) {
152
+ lines.push('## Reference Screenshots');
153
+ lines.push('');
154
+ for (const screenshot of spec.screenshots) {
155
+ lines.push(`- ${screenshot.description}: \`${screenshot.path}\``);
156
+ }
157
+ lines.push('');
158
+ }
159
+ // Metadata footer
160
+ lines.push('---');
161
+ lines.push(`*Generated from: ${basename(spec.sourcePath)}*`);
162
+ lines.push(`*Feature ID: ${spec.id}*`);
163
+ return lines.join('\n');
164
+ }
165
+ /**
166
+ * Format a business scenario in Playwright-friendly markdown
167
+ */
168
+ formatScenario(scenario) {
169
+ const lines = [];
170
+ lines.push(`### ${scenario.name}`);
171
+ lines.push('');
172
+ lines.push(`**Priority**: ${scenario.priority}`);
173
+ lines.push('');
174
+ if (scenario.given) {
175
+ lines.push(`**Given**: ${scenario.given}`);
176
+ }
177
+ if (scenario.when) {
178
+ lines.push(`**When**: ${scenario.when}`);
179
+ }
180
+ if (scenario.then) {
181
+ lines.push(`**Then**: ${scenario.then}`);
182
+ }
183
+ return lines.join('\n');
184
+ }
185
+ /**
186
+ * Parse a spec file without writing output
187
+ * Useful for validation or inspection
188
+ */
189
+ async parseSpec(inputPath) {
190
+ return this.parser.parse(inputPath, 'file');
191
+ }
192
+ /**
193
+ * Validate a specification file
194
+ */
195
+ async validateSpec(inputPath) {
196
+ const specs = await this.parser.parse(inputPath, 'file');
197
+ const allErrors = [];
198
+ let totalScenarios = 0;
199
+ for (const spec of specs) {
200
+ const validation = this.parser.validateSpec(spec);
201
+ if (!validation.valid) {
202
+ allErrors.push(...validation.errors.map((e) => `${spec.name}: ${e}`));
203
+ }
204
+ totalScenarios += spec.scenarios.length;
205
+ }
206
+ return {
207
+ valid: allErrors.length === 0,
208
+ errors: allErrors,
209
+ features: specs.length,
210
+ scenarios: totalScenarios,
211
+ };
212
+ }
213
+ /**
214
+ * Get a summary of the specification
215
+ */
216
+ async getSpecSummary(inputPath) {
217
+ const specs = await this.parser.parse(inputPath, 'file');
218
+ const lines = [];
219
+ lines.push(`Specification Summary: ${basename(inputPath)}`);
220
+ lines.push('='.repeat(50));
221
+ lines.push('');
222
+ for (const spec of specs) {
223
+ const summary = this.parser.getSpecSummary(spec);
224
+ lines.push(`Feature: ${summary.name}`);
225
+ lines.push(` Priority: ${summary.priority}`);
226
+ lines.push(` Scenarios: ${summary.scenarioCount}`);
227
+ lines.push(` - Must-have: ${summary.mustHaveScenarios}`);
228
+ lines.push(` - Should-have: ${summary.shouldHaveScenarios}`);
229
+ lines.push(` - Nice-to-have: ${summary.niceToHaveScenarios}`);
230
+ lines.push(` Acceptance Criteria: ${summary.acceptanceCriteriaCount}`);
231
+ lines.push(` Has Screenshots: ${summary.hasScreenshots ? 'Yes' : 'No'}`);
232
+ lines.push('');
233
+ }
234
+ return lines.join('\n');
235
+ }
236
+ }
237
+ /**
238
+ * Create a SpecBridge with Anthropic provider
239
+ * Convenience function for common use case
240
+ */
241
+ export function createAnthropicBridge(apiKey, outputDir) {
242
+ return new SpecBridge({
243
+ llmConfig: {
244
+ type: 'anthropic',
245
+ config: {
246
+ apiKey: apiKey || process.env.ANTHROPIC_API_KEY || '',
247
+ },
248
+ },
249
+ outputDir,
250
+ });
251
+ }
252
+ /**
253
+ * Create a SpecBridge with Ollama provider (free, local)
254
+ * Convenience function for local development
255
+ */
256
+ export function createOllamaBridge(model, outputDir) {
257
+ return new SpecBridge({
258
+ llmConfig: {
259
+ type: 'ollama',
260
+ config: {
261
+ model: model || 'deepseek-r1:7b',
262
+ baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
263
+ },
264
+ },
265
+ outputDir,
266
+ });
267
+ }
@@ -0,0 +1,267 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ /**
4
+ * Spec Bridge - Bridge between PDF specs and Playwright Agent workflow
5
+ *
6
+ * This module converts specification documents (PDF, Markdown, JSON) into
7
+ * Playwright-compatible markdown files that can be consumed by Playwright's
8
+ * native agents (Planner, Generator, Healer).
9
+ *
10
+ * Flow: PDF → SpecParser → Markdown specs → Playwright Planner
11
+ *
12
+ * The Playwright agents (available in Playwright 1.56+) provide production-ready:
13
+ * - Test planning and exploration
14
+ * - Test code generation
15
+ * - Automatic test healing
16
+ *
17
+ * Usage:
18
+ * ```typescript
19
+ * const bridge = new SpecBridge({type: 'anthropic', apiKey: process.env.ANTHROPIC_API_KEY});
20
+ * const specPaths = await bridge.convertToPlaywrightSpecs('spec.pdf', 'specs/');
21
+ * // Now use Playwright agents: @planner, @generator, @healer
22
+ * ```
23
+ */
24
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
25
+ import { join, basename } from 'path';
26
+ import { LLMProviderFactory } from '../provider_factory.js';
27
+ import { SpecificationParser } from '../e2e-test-gen/spec_parser.js';
28
+ /**
29
+ * Bridge between PDF/JSON specs and Playwright Agent workflow
30
+ *
31
+ * Converts specification documents into Playwright-compatible markdown
32
+ * that can be consumed by Playwright's native agents.
33
+ */
34
+ export class SpecBridge {
35
+ constructor(config) {
36
+ // Handle both single provider and hybrid config
37
+ let llmProvider;
38
+ if ('primary' in config.llmConfig) {
39
+ // It's a HybridConfig
40
+ llmProvider = LLMProviderFactory.createHybrid(config.llmConfig);
41
+ }
42
+ else {
43
+ // It's a ProviderConfig
44
+ llmProvider = LLMProviderFactory.create(config.llmConfig);
45
+ }
46
+ this.parser = new SpecificationParser(llmProvider);
47
+ this.outputDir = config.outputDir || 'specs';
48
+ this.overwrite = config.overwrite !== false;
49
+ }
50
+ /**
51
+ * Convert a specification file to Playwright-compatible markdown specs
52
+ *
53
+ * @param inputPath - Path to the input specification (PDF, MD, or JSON)
54
+ * @param outputDir - Optional override for output directory
55
+ * @returns Conversion result with paths and metadata
56
+ */
57
+ async convertToPlaywrightSpecs(inputPath, outputDir) {
58
+ /* eslint-disable no-console */
59
+ const targetDir = outputDir || this.outputDir;
60
+ const warnings = [];
61
+ console.log(`Converting specification: ${inputPath}`);
62
+ // Parse the specification
63
+ const specs = await this.parser.parse(inputPath, 'file');
64
+ if (specs.length === 0) {
65
+ warnings.push('No features extracted from specification');
66
+ return {
67
+ specPaths: [],
68
+ features: [],
69
+ totalScenarios: 0,
70
+ warnings,
71
+ };
72
+ }
73
+ // Create output directory
74
+ mkdirSync(targetDir, { recursive: true });
75
+ const specPaths = [];
76
+ let totalScenarios = 0;
77
+ for (const spec of specs) {
78
+ // Validate the spec
79
+ const validation = this.parser.validateSpec(spec);
80
+ if (!validation.valid) {
81
+ warnings.push(`Feature "${spec.name}": ${validation.errors.join(', ')}`);
82
+ }
83
+ // Convert to Playwright markdown format
84
+ const markdown = this.toPlaywrightMarkdown(spec);
85
+ // Generate output filename
86
+ const filename = `${spec.id}.md`;
87
+ const outputPath = join(targetDir, filename);
88
+ // Check for existing file
89
+ if (existsSync(outputPath) && !this.overwrite) {
90
+ warnings.push(`Skipping existing file: ${outputPath}`);
91
+ continue;
92
+ }
93
+ // Write the spec file
94
+ writeFileSync(outputPath, markdown, 'utf-8');
95
+ specPaths.push(outputPath);
96
+ totalScenarios += spec.scenarios.length;
97
+ console.log(` Created: ${outputPath} (${spec.scenarios.length} scenarios)`);
98
+ }
99
+ console.log(`Converted ${specs.length} feature(s) with ${totalScenarios} scenario(s)`);
100
+ /* eslint-enable no-console */
101
+ return {
102
+ specPaths,
103
+ features: specs,
104
+ totalScenarios,
105
+ warnings,
106
+ };
107
+ }
108
+ /**
109
+ * Convert a FeatureSpecification to Playwright Agent markdown format
110
+ *
111
+ * The format is designed to be consumed by Playwright's Planner and Generator agents.
112
+ * It includes structured test scenarios in Given-When-Then format.
113
+ */
114
+ toPlaywrightMarkdown(spec) {
115
+ const lines = [];
116
+ // Header
117
+ lines.push(`# ${spec.name}`);
118
+ lines.push('');
119
+ // Metadata
120
+ lines.push(`**Priority**: ${spec.priority}`);
121
+ if (spec.targetUrls.length > 0) {
122
+ lines.push(`**Target URLs**: ${spec.targetUrls.join(', ')}`);
123
+ }
124
+ lines.push('');
125
+ // Description
126
+ if (spec.description) {
127
+ lines.push('## Description');
128
+ lines.push('');
129
+ lines.push(spec.description);
130
+ lines.push('');
131
+ }
132
+ // Test Scenarios
133
+ if (spec.scenarios.length > 0) {
134
+ lines.push('## Test Scenarios');
135
+ lines.push('');
136
+ for (const scenario of spec.scenarios) {
137
+ lines.push(this.formatScenario(scenario));
138
+ lines.push('');
139
+ }
140
+ }
141
+ // Acceptance Criteria
142
+ if (spec.acceptanceCriteria.length > 0) {
143
+ lines.push('## Acceptance Criteria');
144
+ lines.push('');
145
+ for (const criterion of spec.acceptanceCriteria) {
146
+ lines.push(`- ${criterion}`);
147
+ }
148
+ lines.push('');
149
+ }
150
+ // Screenshots reference (if any)
151
+ if (spec.screenshots.length > 0) {
152
+ lines.push('## Reference Screenshots');
153
+ lines.push('');
154
+ for (const screenshot of spec.screenshots) {
155
+ lines.push(`- ${screenshot.description}: \`${screenshot.path}\``);
156
+ }
157
+ lines.push('');
158
+ }
159
+ // Metadata footer
160
+ lines.push('---');
161
+ lines.push(`*Generated from: ${basename(spec.sourcePath)}*`);
162
+ lines.push(`*Feature ID: ${spec.id}*`);
163
+ return lines.join('\n');
164
+ }
165
+ /**
166
+ * Format a business scenario in Playwright-friendly markdown
167
+ */
168
+ formatScenario(scenario) {
169
+ const lines = [];
170
+ lines.push(`### ${scenario.name}`);
171
+ lines.push('');
172
+ lines.push(`**Priority**: ${scenario.priority}`);
173
+ lines.push('');
174
+ if (scenario.given) {
175
+ lines.push(`**Given**: ${scenario.given}`);
176
+ }
177
+ if (scenario.when) {
178
+ lines.push(`**When**: ${scenario.when}`);
179
+ }
180
+ if (scenario.then) {
181
+ lines.push(`**Then**: ${scenario.then}`);
182
+ }
183
+ return lines.join('\n');
184
+ }
185
+ /**
186
+ * Parse a spec file without writing output
187
+ * Useful for validation or inspection
188
+ */
189
+ async parseSpec(inputPath) {
190
+ return this.parser.parse(inputPath, 'file');
191
+ }
192
+ /**
193
+ * Validate a specification file
194
+ */
195
+ async validateSpec(inputPath) {
196
+ const specs = await this.parser.parse(inputPath, 'file');
197
+ const allErrors = [];
198
+ let totalScenarios = 0;
199
+ for (const spec of specs) {
200
+ const validation = this.parser.validateSpec(spec);
201
+ if (!validation.valid) {
202
+ allErrors.push(...validation.errors.map((e) => `${spec.name}: ${e}`));
203
+ }
204
+ totalScenarios += spec.scenarios.length;
205
+ }
206
+ return {
207
+ valid: allErrors.length === 0,
208
+ errors: allErrors,
209
+ features: specs.length,
210
+ scenarios: totalScenarios,
211
+ };
212
+ }
213
+ /**
214
+ * Get a summary of the specification
215
+ */
216
+ async getSpecSummary(inputPath) {
217
+ const specs = await this.parser.parse(inputPath, 'file');
218
+ const lines = [];
219
+ lines.push(`Specification Summary: ${basename(inputPath)}`);
220
+ lines.push('='.repeat(50));
221
+ lines.push('');
222
+ for (const spec of specs) {
223
+ const summary = this.parser.getSpecSummary(spec);
224
+ lines.push(`Feature: ${summary.name}`);
225
+ lines.push(` Priority: ${summary.priority}`);
226
+ lines.push(` Scenarios: ${summary.scenarioCount}`);
227
+ lines.push(` - Must-have: ${summary.mustHaveScenarios}`);
228
+ lines.push(` - Should-have: ${summary.shouldHaveScenarios}`);
229
+ lines.push(` - Nice-to-have: ${summary.niceToHaveScenarios}`);
230
+ lines.push(` Acceptance Criteria: ${summary.acceptanceCriteriaCount}`);
231
+ lines.push(` Has Screenshots: ${summary.hasScreenshots ? 'Yes' : 'No'}`);
232
+ lines.push('');
233
+ }
234
+ return lines.join('\n');
235
+ }
236
+ }
237
+ /**
238
+ * Create a SpecBridge with Anthropic provider
239
+ * Convenience function for common use case
240
+ */
241
+ export function createAnthropicBridge(apiKey, outputDir) {
242
+ return new SpecBridge({
243
+ llmConfig: {
244
+ type: 'anthropic',
245
+ config: {
246
+ apiKey: apiKey || process.env.ANTHROPIC_API_KEY || '',
247
+ },
248
+ },
249
+ outputDir,
250
+ });
251
+ }
252
+ /**
253
+ * Create a SpecBridge with Ollama provider (free, local)
254
+ * Convenience function for local development
255
+ */
256
+ export function createOllamaBridge(model, outputDir) {
257
+ return new SpecBridge({
258
+ llmConfig: {
259
+ type: 'ollama',
260
+ config: {
261
+ model: model || 'deepseek-r1:7b',
262
+ baseUrl: process.env.OLLAMA_BASE_URL || 'http://localhost:11434',
263
+ },
264
+ },
265
+ outputDir,
266
+ });
267
+ }