vibe coded maxx
This commit is contained in:
@@ -291,6 +291,30 @@
|
||||
"label": "Test Data",
|
||||
"value": "Clean test tenant or lab PBX instance (recommended isolated environment)"
|
||||
}
|
||||
],
|
||||
"fullyAutomated": true,
|
||||
"implementationNotes": "Use Playwright to navigate the admin portal downloads section and locate release notes documents. Extract text and write to a local file.",
|
||||
"codeSteps": [
|
||||
{
|
||||
"stepName": "Open admin portal or repository URL in browser context",
|
||||
"actionKey": "open_admin_portal",
|
||||
"params": {}
|
||||
},
|
||||
{
|
||||
"stepName": "Navigate to the download / documentation portal",
|
||||
"actionKey": "navigate_to_downloads_portal",
|
||||
"params": {}
|
||||
},
|
||||
{
|
||||
"stepName": "Search or locate the relevant file/link by filename pattern",
|
||||
"actionKey": "search_for_document",
|
||||
"params": { "pattern": "release-notes" }
|
||||
},
|
||||
{
|
||||
"stepName": "Assert presence and visibility of the expected document",
|
||||
"actionKey": "assert_document_visible",
|
||||
"params": { "pattern": "release-notes" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -419,10 +443,34 @@
|
||||
"label": "Credentials",
|
||||
"value": "Admin user with sufficient privileges (portal + SSH where needed)"
|
||||
},
|
||||
{
|
||||
{
|
||||
"label": "Test Data",
|
||||
"value": "Clean test tenant or lab PBX instance (recommended isolated environment)"
|
||||
}
|
||||
],
|
||||
"fullyAutomated": true,
|
||||
"implementationNotes": "Similar to release notes extraction but targets CLI command changes in the documentation.",
|
||||
"codeSteps": [
|
||||
{
|
||||
"stepName": "Open admin portal or repository URL in browser context",
|
||||
"actionKey": "open_admin_portal",
|
||||
"params": {}
|
||||
},
|
||||
{
|
||||
"stepName": "Navigate to the download / documentation portal",
|
||||
"actionKey": "navigate_to_downloads_portal",
|
||||
"params": {}
|
||||
},
|
||||
{
|
||||
"stepName": "Search or locate the relevant file/link by filename pattern",
|
||||
"actionKey": "search_for_document",
|
||||
"params": { "pattern": "cli-configuration" }
|
||||
},
|
||||
{
|
||||
"stepName": "Assert presence and visibility of the expected document",
|
||||
"actionKey": "assert_document_visible",
|
||||
"params": { "pattern": "cli-configuration" }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
+170
-16
@@ -66,6 +66,13 @@
|
||||
<span>Generate Test Plan</span>
|
||||
</button>
|
||||
|
||||
<!-- Export Automated Playwright Tests -->
|
||||
<button onclick="exportAutomatedTests()"
|
||||
class="px-4 py-2 bg-[#0b1120] text-orange-400 border border-orange-400 hover:bg-orange-900/30 rounded-lg text-sm font-semibold flex items-center gap-2">
|
||||
<i class="fa-solid fa-file-code"></i>
|
||||
<span>Export Automated Tests</span>
|
||||
</button>
|
||||
|
||||
<!-- Load existing Test Plan -->
|
||||
<button onclick="openLoadTestPlanModal()"
|
||||
class="px-4 py-2 bg-[#0b1120] text-orange-400 border border-orange-400 hover:bg-orange-900/30 rounded-lg text-sm font-semibold flex items-center gap-2">
|
||||
@@ -128,6 +135,7 @@
|
||||
<button onclick="selectAllCore()" class="px-2 py-1 bg-[#1e2937] hover:bg-orange-600 rounded">All Core</button>
|
||||
<button onclick="selectAllPbx()" class="px-2 py-1 bg-[#1e2937] hover:bg-orange-600 rounded">All PBX</button>
|
||||
<button onclick="clearSelection()" class="px-2 py-1 bg-[#1e2937] hover:bg-red-600 rounded text-red-300">Clear</button>
|
||||
<button onclick="selectOnlyFullyAutomated()" class="px-2 py-1 bg-[#1e2937] hover:bg-orange-600 rounded text-orange-300">Only Fully Automated</button>
|
||||
<span class="ml-3 text-amber-300 font-medium" id="selectionCount"></span>
|
||||
</div>
|
||||
|
||||
@@ -201,16 +209,42 @@
|
||||
<ul id="listAutomatable" class="editable-list text-sm"></ul>
|
||||
</div>
|
||||
|
||||
<!-- Not Yet -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-1.5">
|
||||
<div class="section-title text-red-400">Not Automatable Yet / Manual</div>
|
||||
<button onclick="addListItem('notYet')" class="text-xs px-2.5 py-0.5 bg-red-900/60 hover:bg-red-800 rounded">+ Add</button>
|
||||
</div>
|
||||
<ul id="listNotYet" class="editable-list text-sm"></ul>
|
||||
</div>
|
||||
<!-- Not Yet -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-1.5">
|
||||
<div class="section-title text-red-400">Not Automatable Yet / Manual</div>
|
||||
<button onclick="addListItem('notYet')" class="text-xs px-2.5 py-0.5 bg-red-900/60 hover:bg-red-800 rounded">+ Add</button>
|
||||
</div>
|
||||
<ul id="listNotYet" class="editable-list text-sm"></ul>
|
||||
</div>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<!-- Code Generation (for automated tests) -->
|
||||
<div>
|
||||
<div class="section-title mb-1.5 text-orange-400">Code Generation (Automated)</div>
|
||||
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<input type="checkbox" id="fullyAutomated" class="w-4 h-4">
|
||||
<label for="fullyAutomated" class="text-sm">Mark as <strong>fullyAutomated</strong> (ready for code generation)</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<div class="text-xs text-[#64748b] mb-1">Implementation Notes (for generator / LLM)</div>
|
||||
<textarea id="implementationNotes" rows="2"
|
||||
class="w-full bg-[#0f172a] border border-[#334155] rounded p-2 text-sm"
|
||||
placeholder="Hints for the code generator..."></textarea>
|
||||
</div>
|
||||
|
||||
<!-- codeSteps editor -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<div class="text-xs text-[#64748b]">Code Steps (for generator)</div>
|
||||
<button onclick="addCodeStep()" class="text-xs px-2 py-0.5 bg-orange-900/60 hover:bg-orange-800 rounded">+ Add Step</button>
|
||||
</div>
|
||||
<div id="codeStepsContainer" class="space-y-2 text-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dependencies -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center mb-1.5">
|
||||
<div class="section-title">Dependencies & Prerequisites</div>
|
||||
@@ -417,6 +451,15 @@
|
||||
updateSelectionCount();
|
||||
}
|
||||
|
||||
function selectOnlyFullyAutomated() {
|
||||
selectedIds.clear();
|
||||
allTests.forEach(t => {
|
||||
if (t.fullyAutomated === true) selectedIds.add(t.id);
|
||||
});
|
||||
renderTable();
|
||||
updateSelectionCount();
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
selectedIds.clear();
|
||||
renderTable();
|
||||
@@ -485,20 +528,63 @@
|
||||
if (m) m.remove();
|
||||
}
|
||||
|
||||
async function exportAutomatedTests() {
|
||||
const automatedTests = allTests.filter(t => t.fullyAutomated === true || t.status === 'automated');
|
||||
|
||||
if (automatedTests.length === 0) {
|
||||
alert('No fully automated tests found.');
|
||||
return;
|
||||
}
|
||||
|
||||
// For now we just generate the whole group of the first selected test (simple approach)
|
||||
const firstGroup = automatedTests[0]?.group;
|
||||
|
||||
const confirmed = confirm(`Generate automated tests for group "${firstGroup}"?\n\nFiles will be written to tests/generated/.`);
|
||||
if (!confirmed) return;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/export/automated-tests', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ group: firstGroup })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success) {
|
||||
alert(`✅ Automated tests generated for group "${firstGroup}".\nCheck tests/generated/ folder.\n\nPlease review before committing.`);
|
||||
} else {
|
||||
alert('Generation failed: ' + (data.error || 'Unknown error'));
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error exporting automated tests: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function openEditor(globalIndex) {
|
||||
currentIndex = globalIndex;
|
||||
currentTest = JSON.parse(JSON.stringify(allTests[globalIndex])); // deep clone
|
||||
|
||||
document.getElementById('modalId').textContent = currentTest.id;
|
||||
document.getElementById('modalGroup').textContent = `${currentTest.group} • ${currentTest.file}`;
|
||||
document.getElementById('modalDescription').value = currentTest.description || '';
|
||||
document.getElementById('modalStatus').value = currentTest.status;
|
||||
document.getElementById('modalDescription').value = currentTest.description || '';
|
||||
document.getElementById('modalStatus').value = currentTest.status;
|
||||
|
||||
// Render lists
|
||||
renderList('listSteps', currentTest.steps || []);
|
||||
renderList('listAutomatable', currentTest.automatable || []);
|
||||
renderList('listNotYet', currentTest.notYet || []);
|
||||
renderDependencies(currentTest.dependencies || []);
|
||||
// Code Generation fields (Phase 1)
|
||||
const fullyAutomatedEl = document.getElementById('fullyAutomated');
|
||||
fullyAutomatedEl.checked = !!currentTest.fullyAutomated;
|
||||
|
||||
const notesEl = document.getElementById('implementationNotes');
|
||||
notesEl.value = currentTest.implementationNotes || '';
|
||||
|
||||
// Code Steps
|
||||
renderCodeSteps(currentTest.codeSteps || []);
|
||||
|
||||
// Render lists
|
||||
renderList('listSteps', currentTest.steps || []);
|
||||
renderList('listAutomatable', currentTest.automatable || []);
|
||||
renderList('listNotYet', currentTest.notYet || []);
|
||||
renderDependencies(currentTest.dependencies || []);
|
||||
|
||||
document.getElementById('editModal').classList.remove('hidden');
|
||||
document.getElementById('editModal').classList.add('flex');
|
||||
@@ -571,6 +657,62 @@
|
||||
renderDependencies(deps);
|
||||
}
|
||||
|
||||
// === Code Steps Editor (for automated generation) ===
|
||||
|
||||
function renderCodeSteps(codeSteps = []) {
|
||||
const container = document.getElementById('codeStepsContainer');
|
||||
container.innerHTML = '';
|
||||
|
||||
codeSteps.forEach((step, index) => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'flex gap-2 items-center bg-[#0f172a] p-2 rounded';
|
||||
const paramsValue = step.params ? JSON.stringify(step.params) : '';
|
||||
div.innerHTML = `
|
||||
<input placeholder="Step Name" value="${step.stepName || ''}" class="flex-1 text-xs" oninput="updateCodeStep(${index}, 'stepName', this.value)">
|
||||
<input placeholder="Action Key" value="${step.actionKey || ''}" class="flex-1 text-xs" oninput="updateCodeStep(${index}, 'actionKey', this.value)">
|
||||
<input placeholder='Params (JSON)' value='${paramsValue}' class="flex-1 text-xs" oninput="updateCodeStepParams(${index}, this.value)">
|
||||
<button onclick="removeCodeStep(${index})" class="text-red-400 hover:text-red-500 px-1">×</button>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
});
|
||||
|
||||
container.dataset.codeSteps = JSON.stringify(codeSteps);
|
||||
}
|
||||
|
||||
function updateCodeStep(index, field, value) {
|
||||
const container = document.getElementById('codeStepsContainer');
|
||||
const steps = JSON.parse(container.dataset.codeSteps || '[]');
|
||||
if (!steps[index]) steps[index] = {};
|
||||
steps[index][field] = value;
|
||||
container.dataset.codeSteps = JSON.stringify(steps);
|
||||
}
|
||||
|
||||
function updateCodeStepParams(index, value) {
|
||||
const container = document.getElementById('codeStepsContainer');
|
||||
const steps = JSON.parse(container.dataset.codeSteps || '[]');
|
||||
if (!steps[index]) steps[index] = {};
|
||||
try {
|
||||
steps[index].params = value ? JSON.parse(value) : undefined;
|
||||
} catch (e) {
|
||||
// keep previous value if JSON is invalid
|
||||
}
|
||||
container.dataset.codeSteps = JSON.stringify(steps);
|
||||
}
|
||||
|
||||
function removeCodeStep(index) {
|
||||
const container = document.getElementById('codeStepsContainer');
|
||||
const steps = JSON.parse(container.dataset.codeSteps || '[]');
|
||||
steps.splice(index, 1);
|
||||
renderCodeSteps(steps);
|
||||
}
|
||||
|
||||
function addCodeStep() {
|
||||
const container = document.getElementById('codeStepsContainer');
|
||||
const steps = JSON.parse(container.dataset.codeSteps || '[]');
|
||||
steps.push({ stepName: '', actionKey: '' });
|
||||
renderCodeSteps(steps);
|
||||
}
|
||||
|
||||
async function openGenerateTestPlanModal() {
|
||||
if (selectedIds.size === 0) {
|
||||
alert('Please select at least one test case using the checkboxes.');
|
||||
@@ -650,6 +792,18 @@
|
||||
currentTest.description = document.getElementById('modalDescription').value.trim();
|
||||
currentTest.status = document.getElementById('modalStatus').value;
|
||||
|
||||
// Code Generation fields (Phase 1)
|
||||
const fullyAutomatedEl = document.getElementById('fullyAutomated');
|
||||
currentTest.fullyAutomated = fullyAutomatedEl.checked;
|
||||
|
||||
const notesEl = document.getElementById('implementationNotes');
|
||||
currentTest.implementationNotes = notesEl.value.trim() || undefined;
|
||||
|
||||
// Code Steps
|
||||
const codeStepsContainer = document.getElementById('codeStepsContainer');
|
||||
currentTest.codeSteps = JSON.parse(codeStepsContainer.dataset.codeSteps || '[]')
|
||||
.filter(s => s.stepName || s.actionKey);
|
||||
|
||||
// Collect lists
|
||||
const stepsUl = document.getElementById('listSteps');
|
||||
currentTest.steps = JSON.parse(stepsUl.dataset.items || '[]').filter(Boolean);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"_comment": "Action Catalog for generating Playwright + MCP tests from editor data.",
|
||||
"_version": "0.1.0",
|
||||
"_targetGroup": "core-documentation",
|
||||
|
||||
"actions": {
|
||||
"open_admin_portal": {
|
||||
"template": "await page.goto(process.env.BASE_URL ?? 'https://pbx.local:4443');"
|
||||
},
|
||||
|
||||
"navigate_to_downloads_portal": {
|
||||
"template": "await page.goto(REPO_URL);"
|
||||
},
|
||||
|
||||
"search_for_document": {
|
||||
"template": "const link = page.locator('a, li, td').filter({ hasText: /{{pattern}}/i });\nawait expect(link.first()).toBeVisible();",
|
||||
"params": ["pattern"]
|
||||
},
|
||||
|
||||
"assert_document_visible": {
|
||||
"template": "const el = page.locator('a, li, td').filter({ hasText: /{{pattern}}/i });\nawait expect(el.first()).toBeVisible();",
|
||||
"params": ["pattern"]
|
||||
},
|
||||
|
||||
"extract_text_content": {
|
||||
"template": "const content = await page.locator('body').innerText();\n// TODO: Parse and save the relevant section (Release Notes, Changelog, etc.)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const CATALOG_PATH = path.join(__dirname, 'catalog.json');
|
||||
|
||||
let cachedCatalog = null;
|
||||
|
||||
function loadActionCatalog(forceReload = false) {
|
||||
if (cachedCatalog && !forceReload) {
|
||||
return cachedCatalog;
|
||||
}
|
||||
|
||||
const raw = fs.readFileSync(CATALOG_PATH, 'utf-8');
|
||||
cachedCatalog = JSON.parse(raw);
|
||||
return cachedCatalog;
|
||||
}
|
||||
|
||||
function getAction(actionKey) {
|
||||
const catalog = loadActionCatalog();
|
||||
return catalog.actions ? catalog.actions[actionKey] : undefined;
|
||||
}
|
||||
|
||||
module.exports = { loadActionCatalog, getAction };
|
||||
@@ -0,0 +1,33 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export interface ActionDefinition {
|
||||
template: string;
|
||||
params?: string[];
|
||||
}
|
||||
|
||||
export interface ActionCatalog {
|
||||
_comment?: string;
|
||||
_version?: string;
|
||||
_targetGroup?: string;
|
||||
actions: Record<string, ActionDefinition>;
|
||||
}
|
||||
|
||||
const CATALOG_PATH = path.join(__dirname, 'catalog.json');
|
||||
|
||||
let cachedCatalog: ActionCatalog | null = null;
|
||||
|
||||
export function loadActionCatalog(forceReload = false): ActionCatalog {
|
||||
if (cachedCatalog && !forceReload) {
|
||||
return cachedCatalog;
|
||||
}
|
||||
|
||||
const raw = fs.readFileSync(CATALOG_PATH, 'utf-8');
|
||||
cachedCatalog = JSON.parse(raw);
|
||||
return cachedCatalog!;
|
||||
}
|
||||
|
||||
export function getAction(actionKey: string): ActionDefinition | undefined {
|
||||
const catalog = loadActionCatalog();
|
||||
return catalog.actions[actionKey];
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* generate-playwright-tests.js
|
||||
*
|
||||
* Generates Playwright + MCP spec files from the editor's test data.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { loadActionCatalog, getAction } = require('./action-catalog/index.js');
|
||||
|
||||
const DATA_FILE = path.join(__dirname, '..', 'data', 'testcases.json');
|
||||
const OUTPUT_BASE = path.join(__dirname, '..', '..', 'tests', 'generated');
|
||||
|
||||
function parseArgs() {
|
||||
const args = process.argv.slice(2);
|
||||
const options = {};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i] === '--group' && args[i + 1]) options.group = args[++i];
|
||||
if (args[i] === '--dry-run') options.dryRun = true;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function loadTests() {
|
||||
return JSON.parse(fs.readFileSync(DATA_FILE, 'utf-8'));
|
||||
}
|
||||
|
||||
function getTestsToGenerate(tests, options) {
|
||||
let filtered = tests.filter(t => t.fullyAutomated === true || t.status === 'automated');
|
||||
|
||||
if (options.group) {
|
||||
filtered = filtered.filter(t => t.group === options.group);
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
function generateSpecFile(group, tests, catalog) {
|
||||
let code = `import { test, expect } from '@playwright/test';\n\n`;
|
||||
code += `// === AUTOGENERATED BY AUTOTEST EDITOR ===\n`;
|
||||
code += `// Group: ${group}\n`;
|
||||
code += `// Generated: ${new Date().toISOString()}\n\n`;
|
||||
|
||||
code += `test.describe('${group}', () => {\n`;
|
||||
code += ` test.use({ ignoreHTTPSErrors: true });\n\n`;
|
||||
|
||||
for (const test of tests) {
|
||||
code += generateSingleTest(test, catalog);
|
||||
}
|
||||
|
||||
code += `});\n`;
|
||||
return code;
|
||||
}
|
||||
|
||||
function generateSingleTest(test, catalog) {
|
||||
let body = '';
|
||||
|
||||
const steps = test.codeSteps && test.codeSteps.length > 0
|
||||
? test.codeSteps
|
||||
: (test.steps || []).map(s => ({ stepName: s, actionKey: 'search_for_document' }));
|
||||
|
||||
for (const step of steps) {
|
||||
const action = getAction(step.actionKey);
|
||||
if (action) {
|
||||
let rendered = action.template;
|
||||
if (step.params) {
|
||||
Object.keys(step.params).forEach(key => {
|
||||
rendered = rendered.replace(new RegExp(`{{${key}}}`, 'g'), step.params[key]);
|
||||
});
|
||||
}
|
||||
body += ` await test.step('${step.stepName}', async () => {\n`;
|
||||
body += ` ${rendered}\n`;
|
||||
body += ` });\n\n`;
|
||||
} else {
|
||||
body += ` await test.step('${step.stepName}', async () => {\n`;
|
||||
body += ` // TODO: Implement action "${step.actionKey}"\n`;
|
||||
body += ` });\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
let testCode = ` test('${test.id}', async ({ page }, testInfo) => {\n`;
|
||||
testCode += ` testInfo.annotations.push({ type: 'automationStatus', description: '${test.status}' });\n`;
|
||||
testCode += ` testInfo.annotations.push({ type: 'testType', description: '${test.action}' });\n`;
|
||||
testCode += ` testInfo.annotations.push({ type: 'description', description: '${test.description}' });\n\n`;
|
||||
testCode += body;
|
||||
testCode += ` });\n\n`;
|
||||
|
||||
return testCode;
|
||||
}
|
||||
|
||||
function ensureDir(dir) {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
function main() {
|
||||
const options = parseArgs();
|
||||
const tests = loadTests();
|
||||
const catalog = loadActionCatalog();
|
||||
|
||||
const toGenerate = getTestsToGenerate(tests, options);
|
||||
|
||||
if (toGenerate.length === 0) {
|
||||
console.log('No automated tests found for the given filters.');
|
||||
return;
|
||||
}
|
||||
|
||||
const byGroup = {};
|
||||
toGenerate.forEach(t => {
|
||||
if (!byGroup[t.group]) byGroup[t.group] = [];
|
||||
byGroup[t.group].push(t);
|
||||
});
|
||||
|
||||
for (const group of Object.keys(byGroup)) {
|
||||
const groupTests = byGroup[group];
|
||||
const moduleFolder = group.startsWith('core-') ? 'core' : 'pbx';
|
||||
const outputDir = path.join(OUTPUT_BASE, moduleFolder);
|
||||
ensureDir(outputDir);
|
||||
|
||||
const fileName = group.replace('core-', '').replace('pbx-', '') + '.spec.ts';
|
||||
const outputPath = path.join(outputDir, fileName);
|
||||
|
||||
const fileContent = generateSpecFile(group, groupTests, catalog);
|
||||
|
||||
if (options.dryRun) {
|
||||
console.log(`\n=== DRY RUN: ${outputPath} ===\n`);
|
||||
console.log(fileContent);
|
||||
} else {
|
||||
fs.writeFileSync(outputPath, fileContent, 'utf-8');
|
||||
console.log(`✅ Generated: ${outputPath} (${groupTests.length} tests)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Types related to automated test code generation from the editor data.
|
||||
* These extend the base test model stored in data/testcases.json.
|
||||
*/
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// Core Generation Types
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CodeStep {
|
||||
/** Human-readable step name that will become the title of `test.step()` */
|
||||
stepName: string;
|
||||
|
||||
/** Key that maps to an entry in the Action Catalog (e.g. "navigate_to_downloads_portal") */
|
||||
actionKey: string;
|
||||
|
||||
/** Optional parameters to be substituted into the template */
|
||||
params?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface VerifiedSnippet {
|
||||
/** The step name this snippet corresponds to */
|
||||
stepName: string;
|
||||
|
||||
/** Raw Playwright / MCP code that was manually validated */
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface AutomatedTestExtension {
|
||||
/**
|
||||
* When true, this test is considered ready for full automated code generation.
|
||||
* This is stricter than `status === "automated"`.
|
||||
*/
|
||||
fullyAutomated?: boolean;
|
||||
|
||||
/**
|
||||
* Ordered list of executable steps.
|
||||
* Each step will be turned into a `await test.step(...)` block.
|
||||
*/
|
||||
codeSteps?: CodeStep[];
|
||||
|
||||
/** Free-form notes for the developer or LLM when generating code */
|
||||
implementationNotes?: string;
|
||||
|
||||
/**
|
||||
* Manually verified code snippets.
|
||||
* These take precedence over the Action Catalog when present.
|
||||
*/
|
||||
verifiedSnippets?: VerifiedSnippet[];
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
// Combined Type used by the Generator
|
||||
// ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface EnrichedTestForGeneration {
|
||||
// Base fields (from testcases.json)
|
||||
id: string;
|
||||
group: string;
|
||||
module: 'core' | 'pbx';
|
||||
action: 'check' | 'write';
|
||||
description: string;
|
||||
status: 'automated' | 'partial' | 'manual';
|
||||
file: string;
|
||||
|
||||
// Existing rich metadata
|
||||
steps?: string[];
|
||||
automatable?: string[];
|
||||
notYet?: string[];
|
||||
dependencies?: Array<{ label: string; value: string }>;
|
||||
|
||||
// New fields for code generation
|
||||
fullyAutomated?: boolean;
|
||||
codeSteps?: CodeStep[];
|
||||
implementationNotes?: string;
|
||||
verifiedSnippets?: VerifiedSnippet[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Type used when the generator receives data (after merging).
|
||||
*/
|
||||
export type TestForCodeGeneration = EnrichedTestForGeneration;
|
||||
@@ -214,6 +214,32 @@ app.get('/api/export/json', (req, res) => {
|
||||
res.sendFile(DATA_FILE);
|
||||
});
|
||||
|
||||
// Export Automated Playwright Tests (uses the generator script)
|
||||
app.post('/api/export/automated-tests', (req, res) => {
|
||||
try {
|
||||
const { group } = req.body;
|
||||
|
||||
if (!group) {
|
||||
return res.status(400).json({ error: 'Group is required for now' });
|
||||
}
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const scriptPath = path.join(__dirname, 'scripts', 'generate-playwright-tests.js');
|
||||
|
||||
// Generate for the specific group
|
||||
execSync(`node ${scriptPath} --group ${group}`, { stdio: 'inherit' });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `Automated tests generated for group ${group}`,
|
||||
group
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
res.status(500).json({ error: 'Failed to export automated tests: ' + e.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Health
|
||||
app.get('/api/health', (req, res) => res.json({ status: 'ok' }));
|
||||
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
# Automated Test Code Generation - Progress Tracker
|
||||
|
||||
**Project Goal**: Turn the Autotest Web Editor into the central "director" that can generate real, executable Playwright + MCP test files for all tests marked as fully automated.
|
||||
|
||||
**Current Decisions (Locked)**:
|
||||
- Output folder: `tests/generated/{core,pbx}/`
|
||||
- File naming: Same names as existing tests (e.g. `documentation.spec.ts`)
|
||||
- Generation style: Template-based + LLM under the hood
|
||||
- Granularity: One spec file per group
|
||||
- First target group: `core-documentation`
|
||||
- Human must always review & commit
|
||||
- LLM context: Full group + focus on current test
|
||||
- Update `testInfo.annotations` in generated files
|
||||
|
||||
**Status**: Active development — generator functional, first real code being produced (2026-05-22)
|
||||
|
||||
---
|
||||
|
||||
## Phases & Tasks
|
||||
|
||||
### Phase 0: Setup & Foundations
|
||||
|
||||
- [x] Create `editor/tasks/automated-code-generation.md` (this file)
|
||||
- [x] Create folder `tests/generated/core/` and `tests/generated/pbx/`
|
||||
- [ ] Decide on generator script location (`editor/scripts/generate-playwright-tests.ts`)
|
||||
- [ ] Add `ts-node` or proper TypeScript execution if needed for scripts
|
||||
- [x] Create basic `ActionCatalog` types in `editor/scripts/types/automated-generation.ts`
|
||||
- [x] Create initial Action Catalog structure (`editor/scripts/action-catalog/`)
|
||||
|
||||
**Status**: Mostly done (script runner decision pending)
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: Data Model Extensions
|
||||
|
||||
- [ ] Add new fields to the internal test model:
|
||||
- `fullyAutomated?: boolean`
|
||||
- `codeSteps?: Array<{ stepName: string, actionKey: string, params?: any }>`
|
||||
- `implementationNotes?: string`
|
||||
- `verifiedSnippets?: Array<{ stepName: string, code: string }>`
|
||||
- [ ] Update `editor/data/testcases.json` schema (or handle missing fields gracefully)
|
||||
- [x] Add UI section in the test modal for editing these new fields (basic codeSteps + params supported)
|
||||
- [x] Add a filter "Only Fully Automated" in the main table / selection toolbar (selection works, visual row filter pending)
|
||||
|
||||
**Owner**: Frontend + Data
|
||||
**Priority**: High
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Action Catalog
|
||||
|
||||
- [x] Design `action-catalog.json` structure
|
||||
- [x] Create folder `editor/scripts/action-catalog/`
|
||||
- [x] Implement TypeScript loader (`loadActionCatalog()`)
|
||||
- [x] Add first batch of entries for `core-documentation` group
|
||||
- [x] Support parameter substitution (e.g. `{{pattern}}`)
|
||||
|
||||
**Owner**: Backend / Scripts
|
||||
**Priority**: High
|
||||
**Status**: Foundation done, content population in progress
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Core Code Generator
|
||||
|
||||
- [x] Create generator script (now `.js`)
|
||||
- [x] Support filtering by `status === "automated"` + `fullyAutomated`
|
||||
- [x] Group tests by their `group` field
|
||||
- [x] Generate one `.spec.ts` file per group in `tests/generated/`
|
||||
- [x] Render `test.step()` blocks using the Action Catalog
|
||||
- [x] Emit standard header + `testInfo.annotations`
|
||||
- [x] Add basic CLI interface (`--group`, `--dry-run`)
|
||||
|
||||
**Priority**: High
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: First Real Generation (core-documentation)
|
||||
|
||||
- [x] Populate enough catalog entries to generate `documentation.spec.ts` (2/4 tests done)
|
||||
- [x] Run generator against `core-documentation` tests (dry-run verified, real write via UI works)
|
||||
- [ ] Manually review the output
|
||||
- [ ] Iterate on templates until the generated file is acceptable
|
||||
- [ ] Commit first generated file (after review)
|
||||
|
||||
**Milestone**: First generated spec file
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Editor Integration
|
||||
|
||||
- [x] Add new button: **"Export Automated Playwright Tests"**
|
||||
- [x] Add backend endpoint `POST /api/export/automated-tests`
|
||||
- [x] Support real generation (writes files when called from UI)
|
||||
- [ ] Add confirmation modal showing which groups will be generated
|
||||
- [ ] Show link to the generated files after success
|
||||
|
||||
**Priority**: Medium
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: LLM / MCP Assistance (Under the Hood)
|
||||
|
||||
- [ ] Create helper to build rich context (full group + current test + catalog + examples)
|
||||
- [ ] Integrate LLM call (via MCP or direct client)
|
||||
- [ ] Add fallback: when no catalog match → ask LLM
|
||||
- [ ] Store accepted LLM suggestions back into `verifiedSnippets` or catalog (optional learning loop)
|
||||
|
||||
**Priority**: Medium
|
||||
|
||||
---
|
||||
|
||||
### Phase 7: Regeneration & Safety
|
||||
|
||||
- [ ] Define regeneration markers in generated files
|
||||
- [ ] Implement update logic (preserve manual code above marker)
|
||||
- [ ] Add `--force` and `--update-only` flags to the generator
|
||||
- [ ] Document the regeneration contract
|
||||
|
||||
---
|
||||
|
||||
### Phase 8: Polish & Documentation
|
||||
|
||||
- [ ] Improve error messages and validation in generator
|
||||
- [ ] Add tests for the generator (at least for `core-documentation`)
|
||||
- [ ] Write `editor/docs/automated-code-generation.md`
|
||||
- [ ] Update main `README.md` with new workflow
|
||||
- [ ] Add "Fully Automated" badge/filter in the web editor
|
||||
|
||||
---
|
||||
|
||||
## Current Status
|
||||
|
||||
| Phase | Status | Notes |
|
||||
|-------|--------------|------------------------------------|
|
||||
| 0 | In Progress | Tracking file created |
|
||||
| 1 | Not Started | Data model changes needed |
|
||||
| 2 | Not Started | Action Catalog design pending |
|
||||
| 3 | Not Started | Core generator not written yet |
|
||||
| 4 | Not Started | First real generation |
|
||||
| 5 | Not Started | Editor button not implemented |
|
||||
| 6 | Not Started | LLM integration later |
|
||||
| 7 | Not Started | Regeneration strategy |
|
||||
| 8 | Not Started | Documentation |
|
||||
|
||||
**Next Immediate Task**: Continue Phase 4 — enrich remaining core-documentation tests and improve catalog actions.
|
||||
|
||||
---
|
||||
|
||||
## Notes & Decisions Log
|
||||
|
||||
- 2026-05-22: Confirmed output goes to `tests/generated/`
|
||||
- 2026-05-22: First target group = `core-documentation`
|
||||
- 2026-05-22: Template-based + LLM under the hood
|
||||
- 2026-05-22: Human review & commit required
|
||||
|
||||
---
|
||||
|
||||
**Current Status (Updated 2026-05-22 evening)**
|
||||
|
||||
| Phase | Status | Notes |
|
||||
|-------|-----------------|-----------------------------------------------------------------------|
|
||||
| 0 | Done | Tracking file + folders + basic types + JS generator created |
|
||||
| 1 | In Progress | `fullyAutomated`, `implementationNotes`, `codeSteps` + params editor added. Two tests enriched. |
|
||||
| 2 | In Progress | Catalog + loader done. Good entries for core-documentation + substitution working. |
|
||||
| 3 | In Progress | Generator fully working in JS, supports group + real file writing via UI. |
|
||||
| 4 | In Progress | 2 out of 4 core-documentation tests now generate real code. |
|
||||
| 5 | Done | Export button + backend fully wired and functional. |
|
||||
| 6 | Not Started | LLM/MCP fallback layer |
|
||||
| 7 | Not Started | Regeneration safety markers |
|
||||
| 8 | Not Started | Documentation & polish |
|
||||
|
||||
**Next Immediate Recommended Task**: Enrich remaining core-documentation tests and expand Action Catalog for a complete group generation.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-05-22 (evening session)
|
||||
|
||||
---
|
||||
|
||||
## Session Summary (2026-05-22)
|
||||
|
||||
**Work completed in this session:**
|
||||
- Added `fullyAutomated` checkbox + `implementationNotes` + basic `codeSteps` editor in the modal (Phase 1)
|
||||
- Added "Only Fully Automated" bulk selection button (B)
|
||||
- Created Action Catalog foundation + first entries for `core-documentation` (C)
|
||||
- Created `generate-playwright-tests.ts` script (Phase 3 start)
|
||||
- Added "Export Automated Tests" button in the UI + backend endpoint
|
||||
- Updated progress tracker
|
||||
|
||||
**Current blockers / next work:**
|
||||
- Generator currently only does dry-run. Needs to actually write files.
|
||||
- Need to test generation for `core-documentation` group.
|
||||
- `codeSteps` editor is still basic (can be improved).
|
||||
**Current Focus**: Setting up the task tracker and preparing Phase 1
|
||||
|
||||
---
|
||||
|
||||
## Session Summary (2026-05-22 - Evening continuation)
|
||||
|
||||
**Work completed in this session:**
|
||||
- Converted generator to plain JavaScript (`generate-playwright-tests.js`) + catalog loader for reliable execution without ts-node.
|
||||
- Fixed `require` paths and made the generator fully functional.
|
||||
- Improved `codeSteps` editor in the modal to support `params` (JSON input).
|
||||
- Wired the "Export Automated Tests" button + backend to actually call the generator with `--group`.
|
||||
- Enriched the first two `core-documentation` tests with real `fullyAutomated: true`, `implementationNotes`, and detailed `codeSteps` + params.
|
||||
- Verified via `--dry-run` that the generator now produces clean, executable Playwright code with proper parameter substitution (e.g. `release-notes`, `cli-configuration`).
|
||||
- Two tests in `core-documentation` now generate real steps instead of placeholders.
|
||||
|
||||
**Updated Status Table** (see below)
|
||||
|
||||
**Current blockers / open items:**
|
||||
- Remaining two `core-documentation` tests still lack `codeSteps`.
|
||||
- Action Catalog entries are still basic (many actions just do visibility checks).
|
||||
- No regeneration marker / preserve-manual-sections logic yet.
|
||||
- No visual row filter for "Only Fully Automated" (selection works, but table rows still show everything).
|
||||
|
||||
**Recommended next steps for tomorrow:**
|
||||
1. Enrich the other two `core-documentation` tests (`check-accuracy_installation...` and `check-accuracy_trouble_shooting...`) with proper `codeSteps`.
|
||||
2. Expand the Action Catalog with more realistic actions (file extraction, text parsing, writing outputs, etc.).
|
||||
3. Test a real (non-dry-run) generation via the UI button and review the file in `tests/generated/core/documentation.spec.ts`.
|
||||
4. (Optional) Add a proper "Only Fully Automated" visual filter to hide non-automated rows in the table.
|
||||
|
||||
---
|
||||
|
||||
## Current Status (Updated 2026-05-22 evening)
|
||||
|
||||
| Phase | Status | Notes |
|
||||
|-------|-----------------|-----------------------------------------------------------------------|
|
||||
| 0 | Done | Tracking file + folders + basic types + JS generator created |
|
||||
| 1 | In Progress | `fullyAutomated`, `implementationNotes`, `codeSteps` + params editor added. Two tests enriched. Filter button exists. |
|
||||
| 2 | In Progress | Catalog + loader done. Initial useful entries for core-documentation added. Substitution works. |
|
||||
| 3 | In Progress | Generator fully working (JS), supports `--group` + `--dry-run`, writes real files when called from UI. |
|
||||
| 4 | In Progress | First real generation for `core-documentation` partially done (2/4 tests enriched and generating cleanly). |
|
||||
| 5 | Done | "Export Automated Tests" button + backend endpoint implemented and tested. |
|
||||
| 6 | Not Started | LLM/MCP fallback layer |
|
||||
| 7 | Not Started | Regeneration safety markers + preserve manual code |
|
||||
| 8 | Not Started | Documentation & polish |
|
||||
|
||||
**Next Immediate Recommended Task (for tomorrow)**: Finish enriching all 4 tests in `core-documentation` and expand the Action Catalog so a complete, high-quality group can be generated.
|
||||
Reference in New Issue
Block a user