@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.
- package/LICENSE +168 -0
- package/README.md +620 -0
- package/dist/agent/analysis.d.ts +62 -0
- package/dist/agent/analysis.d.ts.map +1 -0
- package/dist/agent/analysis.js +292 -0
- package/dist/agent/blast_radius.d.ts +4 -0
- package/dist/agent/blast_radius.d.ts.map +1 -0
- package/dist/agent/blast_radius.js +37 -0
- package/dist/agent/cache_utils.d.ts +38 -0
- package/dist/agent/cache_utils.d.ts.map +1 -0
- package/dist/agent/cache_utils.js +67 -0
- package/dist/agent/config.d.ts +148 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +640 -0
- package/dist/agent/dependency_graph.d.ts +14 -0
- package/dist/agent/dependency_graph.d.ts.map +1 -0
- package/dist/agent/dependency_graph.js +227 -0
- package/dist/agent/feedback.d.ts +55 -0
- package/dist/agent/feedback.d.ts.map +1 -0
- package/dist/agent/feedback.js +257 -0
- package/dist/agent/flags.d.ts +23 -0
- package/dist/agent/flags.d.ts.map +1 -0
- package/dist/agent/flags.js +171 -0
- package/dist/agent/flow_catalog.d.ts +25 -0
- package/dist/agent/flow_catalog.d.ts.map +1 -0
- package/dist/agent/flow_catalog.js +106 -0
- package/dist/agent/flow_mapping.d.ts +10 -0
- package/dist/agent/flow_mapping.d.ts.map +1 -0
- package/dist/agent/flow_mapping.js +84 -0
- package/dist/agent/framework.d.ts +13 -0
- package/dist/agent/framework.d.ts.map +1 -0
- package/dist/agent/framework.js +149 -0
- package/dist/agent/gap_suggestions.d.ts +14 -0
- package/dist/agent/gap_suggestions.d.ts.map +1 -0
- package/dist/agent/gap_suggestions.js +101 -0
- package/dist/agent/generator.d.ts +10 -0
- package/dist/agent/generator.d.ts.map +1 -0
- package/dist/agent/generator.js +115 -0
- package/dist/agent/git.d.ts +11 -0
- package/dist/agent/git.d.ts.map +1 -0
- package/dist/agent/git.js +90 -0
- package/dist/agent/handoff.d.ts +22 -0
- package/dist/agent/handoff.d.ts.map +1 -0
- package/dist/agent/handoff.js +180 -0
- package/dist/agent/impact-analyzer.d.ts +114 -0
- package/dist/agent/impact-analyzer.d.ts.map +1 -0
- package/dist/agent/impact-analyzer.js +557 -0
- package/dist/agent/index.d.ts +21 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +38 -0
- package/dist/agent/model-router.d.ts +57 -0
- package/dist/agent/model-router.d.ts.map +1 -0
- package/dist/agent/model-router.js +154 -0
- package/dist/agent/operational_insights.d.ts +41 -0
- package/dist/agent/operational_insights.d.ts.map +1 -0
- package/dist/agent/operational_insights.js +126 -0
- package/dist/agent/pipeline.d.ts +23 -0
- package/dist/agent/pipeline.d.ts.map +1 -0
- package/dist/agent/pipeline.js +609 -0
- package/dist/agent/plan.d.ts +91 -0
- package/dist/agent/plan.d.ts.map +1 -0
- package/dist/agent/plan.js +331 -0
- package/dist/agent/playwright_report.d.ts +8 -0
- package/dist/agent/playwright_report.d.ts.map +1 -0
- package/dist/agent/playwright_report.js +126 -0
- package/dist/agent/report-generator.d.ts +24 -0
- package/dist/agent/report-generator.d.ts.map +1 -0
- package/dist/agent/report-generator.js +250 -0
- package/dist/agent/report.d.ts +81 -0
- package/dist/agent/report.d.ts.map +1 -0
- package/dist/agent/report.js +147 -0
- package/dist/agent/runner.d.ts +7 -0
- package/dist/agent/runner.d.ts.map +1 -0
- package/dist/agent/runner.js +576 -0
- package/dist/agent/selectors.d.ts +10 -0
- package/dist/agent/selectors.d.ts.map +1 -0
- package/dist/agent/selectors.js +75 -0
- package/dist/agent/spec-bridge.d.ts +101 -0
- package/dist/agent/spec-bridge.d.ts.map +1 -0
- package/dist/agent/spec-bridge.js +273 -0
- package/dist/agent/spec-builder.d.ts +102 -0
- package/dist/agent/spec-builder.d.ts.map +1 -0
- package/dist/agent/spec-builder.js +273 -0
- package/dist/agent/subsystem_risk.d.ts +23 -0
- package/dist/agent/subsystem_risk.d.ts.map +1 -0
- package/dist/agent/subsystem_risk.js +207 -0
- package/dist/agent/telemetry.d.ts +84 -0
- package/dist/agent/telemetry.d.ts.map +1 -0
- package/dist/agent/telemetry.js +220 -0
- package/dist/agent/test_path.d.ts +2 -0
- package/dist/agent/test_path.d.ts.map +1 -0
- package/dist/agent/test_path.js +23 -0
- package/dist/agent/tests.d.ts +18 -0
- package/dist/agent/tests.d.ts.map +1 -0
- package/dist/agent/tests.js +106 -0
- package/dist/agent/traceability.d.ts +22 -0
- package/dist/agent/traceability.d.ts.map +1 -0
- package/dist/agent/traceability.js +183 -0
- package/dist/agent/traceability_capture.d.ts +18 -0
- package/dist/agent/traceability_capture.d.ts.map +1 -0
- package/dist/agent/traceability_capture.js +313 -0
- package/dist/agent/traceability_ingest.d.ts +21 -0
- package/dist/agent/traceability_ingest.d.ts.map +1 -0
- package/dist/agent/traceability_ingest.js +237 -0
- package/dist/agent/utils.d.ts +13 -0
- package/dist/agent/utils.d.ts.map +1 -0
- package/dist/agent/utils.js +152 -0
- package/dist/agent/validators/selector-validator.d.ts +74 -0
- package/dist/agent/validators/selector-validator.d.ts.map +1 -0
- package/dist/agent/validators/selector-validator.js +165 -0
- package/dist/anthropic_provider.d.ts +65 -0
- package/dist/anthropic_provider.d.ts.map +1 -0
- package/dist/anthropic_provider.js +332 -0
- package/dist/api.d.ts +48 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +113 -0
- package/dist/base_provider.d.ts +53 -0
- package/dist/base_provider.d.ts.map +1 -0
- package/dist/base_provider.js +81 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +843 -0
- package/dist/custom_provider.d.ts +20 -0
- package/dist/custom_provider.d.ts.map +1 -0
- package/dist/custom_provider.js +276 -0
- package/dist/e2e-test-gen/index.d.ts +51 -0
- package/dist/e2e-test-gen/index.d.ts.map +1 -0
- package/dist/e2e-test-gen/index.js +57 -0
- package/dist/e2e-test-gen/spec_parser.d.ts +142 -0
- package/dist/e2e-test-gen/spec_parser.d.ts.map +1 -0
- package/dist/e2e-test-gen/spec_parser.js +786 -0
- package/dist/e2e-test-gen/types.d.ts +185 -0
- package/dist/e2e-test-gen/types.d.ts.map +1 -0
- package/dist/e2e-test-gen/types.js +4 -0
- package/dist/esm/agent/analysis.js +287 -0
- package/dist/esm/agent/blast_radius.js +34 -0
- package/dist/esm/agent/cache_utils.js +63 -0
- package/dist/esm/agent/config.js +637 -0
- package/dist/esm/agent/dependency_graph.js +224 -0
- package/dist/esm/agent/feedback.js +253 -0
- package/dist/esm/agent/flags.js +160 -0
- package/dist/esm/agent/flow_catalog.js +103 -0
- package/dist/esm/agent/flow_mapping.js +81 -0
- package/dist/esm/agent/framework.js +145 -0
- package/dist/esm/agent/gap_suggestions.js +98 -0
- package/dist/esm/agent/generator.js +112 -0
- package/dist/esm/agent/git.js +87 -0
- package/dist/esm/agent/handoff.js +177 -0
- package/dist/esm/agent/impact-analyzer.js +548 -0
- package/dist/esm/agent/index.js +22 -0
- package/dist/esm/agent/model-router.js +150 -0
- package/dist/esm/agent/operational_insights.js +123 -0
- package/dist/esm/agent/pipeline.js +605 -0
- package/dist/esm/agent/plan.js +324 -0
- package/dist/esm/agent/playwright_report.js +123 -0
- package/dist/esm/agent/report-generator.js +247 -0
- package/dist/esm/agent/report.js +144 -0
- package/dist/esm/agent/runner.js +572 -0
- package/dist/esm/agent/selectors.js +71 -0
- package/dist/esm/agent/spec-bridge.js +267 -0
- package/dist/esm/agent/spec-builder.js +267 -0
- package/dist/esm/agent/subsystem_risk.js +204 -0
- package/dist/esm/agent/telemetry.js +216 -0
- package/dist/esm/agent/test_path.js +20 -0
- package/dist/esm/agent/tests.js +101 -0
- package/dist/esm/agent/traceability.js +180 -0
- package/dist/esm/agent/traceability_capture.js +310 -0
- package/dist/esm/agent/traceability_ingest.js +234 -0
- package/dist/esm/agent/utils.js +138 -0
- package/dist/esm/agent/validators/selector-validator.js +160 -0
- package/dist/esm/anthropic_provider.js +324 -0
- package/dist/esm/api.js +105 -0
- package/dist/esm/base_provider.js +77 -0
- package/dist/esm/cli.js +841 -0
- package/dist/esm/custom_provider.js +272 -0
- package/dist/esm/e2e-test-gen/index.js +50 -0
- package/dist/esm/e2e-test-gen/spec_parser.js +782 -0
- package/dist/esm/e2e-test-gen/types.js +3 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/logger.js +89 -0
- package/dist/esm/mcp-server.js +465 -0
- package/dist/esm/ollama_provider.js +300 -0
- package/dist/esm/openai_provider.js +242 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/plan-and-test-constants.js +126 -0
- package/dist/esm/provider_factory.js +336 -0
- package/dist/esm/provider_interface.js +23 -0
- package/dist/esm/provider_utils.js +96 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/logger.d.ts +23 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +93 -0
- package/dist/mcp-server.d.ts +35 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +469 -0
- package/dist/ollama_provider.d.ts +65 -0
- package/dist/ollama_provider.d.ts.map +1 -0
- package/dist/ollama_provider.js +308 -0
- package/dist/openai_provider.d.ts +23 -0
- package/dist/openai_provider.d.ts.map +1 -0
- package/dist/openai_provider.js +250 -0
- package/dist/plan-and-test-constants.d.ts +110 -0
- package/dist/plan-and-test-constants.d.ts.map +1 -0
- package/dist/plan-and-test-constants.js +132 -0
- package/dist/provider_factory.d.ts +99 -0
- package/dist/provider_factory.d.ts.map +1 -0
- package/dist/provider_factory.js +341 -0
- package/dist/provider_interface.d.ts +358 -0
- package/dist/provider_interface.d.ts.map +1 -0
- package/dist/provider_interface.js +28 -0
- package/dist/provider_utils.d.ts +39 -0
- package/dist/provider_utils.d.ts.map +1 -0
- package/dist/provider_utils.js +103 -0
- package/package.json +101 -0
- package/schemas/gap.schema.json +18 -0
- package/schemas/impact.schema.json +418 -0
- package/schemas/plan.schema.json +285 -0
- package/schemas/subsystem-risk-map.schema.json +62 -0
- 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
|
+
}
|