290 lines
11 KiB
JavaScript
290 lines
11 KiB
JavaScript
const { test, expect } = require('@playwright/test');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
/**
|
|
* 🔨 CARPENTER AGENT TEST
|
|
*
|
|
* A realistic carpenter agent that:
|
|
* - Acts as a real carpenter completing diverse projects
|
|
* - Creates detailed quotes for different project types
|
|
* - Stores reusable test data for future regression testing
|
|
* - Validates the quote generation flow end-to-end
|
|
*
|
|
* The carpenter has experience with various project types and realistic timelines.
|
|
*/
|
|
|
|
const BASE_URL = process.env.BASE_URL || 'http://localhost:4032';
|
|
|
|
// Carpenter personas and their typical projects
|
|
const CARPENTER_PERSONAS = {
|
|
'Lars': {
|
|
name: 'Lars Nielsen',
|
|
experience: '15 år',
|
|
specialty: 'Tag og terrasser',
|
|
projects: [
|
|
{
|
|
type: 'tagdækning',
|
|
description: 'Udbedring af skadede tagsten på 35m² tørvehus. Der skal skiftes ca. 50 stk. beskadige sten og lægges nye dragsten',
|
|
area: 35,
|
|
complexity: 'medium'
|
|
},
|
|
{
|
|
type: 'terrasse',
|
|
description: 'Opbygning af træterrasse på 24m² med gelænder. Størrelse 4x6m med hvid lærketræ og rustfrit stål gelænder',
|
|
area: 24,
|
|
complexity: 'high'
|
|
}
|
|
]
|
|
},
|
|
'Jannick': {
|
|
name: 'Jannick Andersen',
|
|
experience: '12 år',
|
|
specialty: 'Indendørs renovering',
|
|
projects: [
|
|
{
|
|
type: 'gulv',
|
|
description: 'Lægning af egetparket på 45m² i stue, gang og soveværelse. Oprindelige undergulv skal forbedres',
|
|
area: 45,
|
|
complexity: 'high'
|
|
},
|
|
{
|
|
type: 'garderobe',
|
|
description: 'Indvendig garderobe med skydedøre i soveværelset. Størrelse 2.4x1.8m med 2 skydedøre og hylder',
|
|
area: 4,
|
|
complexity: 'medium'
|
|
},
|
|
{
|
|
type: 'skabe',
|
|
description: 'Køkkenskabe til omdisponering af køkken. Nye skabe under arbejdspladen og nye hylder over',
|
|
area: 8,
|
|
complexity: 'medium'
|
|
}
|
|
]
|
|
},
|
|
'Alexander': {
|
|
name: 'Alexander Ørneby Andersen',
|
|
experience: '8 år',
|
|
specialty: 'Moderne bygge- og renoveringsprojekter',
|
|
projects: [
|
|
{
|
|
type: 'dørinstallation',
|
|
description: 'Installation af 3 nye indvendige glas-ali dører til åbent køkkenkoncept. Dørene er 1m brede',
|
|
area: 3,
|
|
complexity: 'medium'
|
|
},
|
|
{
|
|
type: 'håndværkerspil',
|
|
description: 'Installation af håndværkerspil i alle vinduerne gennem huset. I alt 12 vinduer á 1.2x1.0m',
|
|
area: 15,
|
|
complexity: 'low'
|
|
},
|
|
{
|
|
type: 'renovering',
|
|
description: 'Fuld renovering af badeværelse inkl. nye vægge, gulv, og installering af badekar og bruser',
|
|
area: 8,
|
|
complexity: 'high'
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
test.describe('🔨 CARPENTER AGENT - Diverse Project Testing', () => {
|
|
|
|
let quoteStorage = [];
|
|
|
|
test.beforeAll(async () => {
|
|
// Initialize storage directory
|
|
const storageDir = 'test-results/carpenter-agent-data';
|
|
if (!fs.existsSync(storageDir)) {
|
|
fs.mkdirSync(storageDir, { recursive: true });
|
|
}
|
|
});
|
|
|
|
for (const [carpenterId, carpenter] of Object.entries(CARPENTER_PERSONAS)) {
|
|
for (const project of carpenter.projects) {
|
|
const testName = `${carpenter.name} - ${project.type}: ${project.description.substring(0, 40)}...`;
|
|
|
|
test(testName, async ({ page }) => {
|
|
console.log(`\n${'='.repeat(80)}`);
|
|
console.log(`🔨 CARPENTER: ${carpenter.name} (${carpenter.experience})`);
|
|
console.log(`📋 PROJECT: ${project.type.toUpperCase()}`);
|
|
console.log(`📐 AREA: ${project.area}m²`);
|
|
console.log(`💪 COMPLEXITY: ${project.complexity}`);
|
|
console.log(`${'='.repeat(80)}\n`);
|
|
|
|
// Navigate to site
|
|
console.log('🌐 Navigating to site...');
|
|
await page.goto(BASE_URL, { waitUntil: 'networkidle' });
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Handle login if needed
|
|
const loginButton = page.locator('button:has-text("Log ind")');
|
|
if (await loginButton.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
console.log('🔐 Logging in as admin...');
|
|
await page.fill('input[type="text"], input[name="username"]', 'admin');
|
|
await page.fill('input[type="password"]', 'admin123');
|
|
await loginButton.click();
|
|
await page.waitForTimeout(2000);
|
|
}
|
|
|
|
// Take screenshot of initial state
|
|
await page.screenshot({
|
|
path: `test-results/carpenter-agent-data/${carpenterId}-${project.type}-01-home.png`,
|
|
fullPage: true
|
|
});
|
|
|
|
// Look for quote creation interface
|
|
console.log('\n📝 Looking for quote creation interface...');
|
|
|
|
// Try different selectors for quote creation
|
|
const quoteButton = page.locator(
|
|
'button:has-text("Opret Tilbud"), button:has-text("Nyt Tilbud"), button:has-text("Tilbud"), a:has-text("Tilbud")'
|
|
).first();
|
|
|
|
if (await quoteButton.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
console.log('✅ Found quote creation button');
|
|
await quoteButton.click();
|
|
await page.waitForTimeout(1500);
|
|
await page.screenshot({
|
|
path: `test-results/carpenter-agent-data/${carpenterId}-${project.type}-02-quote-form.png`,
|
|
fullPage: true
|
|
});
|
|
} else {
|
|
console.log('⚠️ Quote button not found, checking for form fields...');
|
|
}
|
|
|
|
// Fill in project description
|
|
console.log('\n📋 Filling project description...');
|
|
const descriptionField = page.locator(
|
|
'textarea[placeholder*="projekt" i], textarea[placeholder*="beskrivelse" i], textarea[name="description"], [role="textbox"]'
|
|
).first();
|
|
|
|
if (await descriptionField.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
console.log('✅ Found description field');
|
|
await descriptionField.fill(project.description);
|
|
console.log(` "${project.description.substring(0, 60)}..."`);
|
|
await page.waitForTimeout(500);
|
|
}
|
|
|
|
// Fill in area if form has area field
|
|
console.log('\n📐 Filling project area...');
|
|
const areaField = page.locator(
|
|
'input[type="number"][placeholder*="m²" i], input[placeholder*="område" i], input[name="area"]'
|
|
).first();
|
|
|
|
if (await areaField.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
console.log('✅ Found area field');
|
|
await areaField.fill(project.area.toString());
|
|
console.log(` Area: ${project.area}m²`);
|
|
await page.waitForTimeout(500);
|
|
}
|
|
|
|
// Look for generate/calculate button
|
|
console.log('\n⚙️ Looking for generate button...');
|
|
const generateButton = page.locator(
|
|
'button:has-text("Generer"), button:has-text("Beregn"), button:has-text("Beregning"), button:has-text("Opret")'
|
|
).first();
|
|
|
|
if (await generateButton.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
console.log('✅ Found generate button');
|
|
await generateButton.click();
|
|
console.log('⏳ Waiting for quote generation...');
|
|
await page.waitForTimeout(3000);
|
|
console.log('✅ Quote generated');
|
|
}
|
|
|
|
// Take screenshot of generated quote
|
|
await page.screenshot({
|
|
path: `test-results/carpenter-agent-data/${carpenterId}-${project.type}-03-quote-generated.png`,
|
|
fullPage: true
|
|
});
|
|
|
|
// Extract quote data
|
|
console.log('\n💾 Extracting quote data...');
|
|
const quoteText = await page.textContent('body');
|
|
|
|
// Look for price/amount patterns
|
|
const pricePattern = /(\d+[.,]\d{2}|\d+\.\d{2}|[0-9]+\s?kr)/gi;
|
|
const prices = quoteText.match(pricePattern) || [];
|
|
|
|
const quoteData = {
|
|
timestamp: new Date().toISOString(),
|
|
carpenter: {
|
|
id: carpenterId,
|
|
name: carpenter.name,
|
|
experience: carpenter.experience,
|
|
specialty: carpenter.specialty
|
|
},
|
|
project: {
|
|
type: project.type,
|
|
description: project.description,
|
|
area: project.area,
|
|
complexity: project.complexity
|
|
},
|
|
url: BASE_URL,
|
|
screenshots: [
|
|
`${carpenterId}-${project.type}-01-home.png`,
|
|
`${carpenterId}-${project.type}-02-quote-form.png`,
|
|
`${carpenterId}-${project.type}-03-quote-generated.png`
|
|
],
|
|
extractedPrices: prices.slice(0, 5), // First 5 price matches
|
|
pageLength: quoteText.length
|
|
};
|
|
|
|
// Save quote data
|
|
const storageFile = `test-results/carpenter-agent-data/${carpenterId}-${project.type}.json`;
|
|
fs.writeFileSync(storageFile, JSON.stringify(quoteData, null, 2));
|
|
console.log(`✅ Quote data saved to: ${storageFile}`);
|
|
|
|
quoteStorage.push(quoteData);
|
|
|
|
// Look for save/export button
|
|
const saveButton = page.locator(
|
|
'button:has-text("Gem"), button:has-text("Eksporter"), button:has-text("Download"), button:has-text("Hent")'
|
|
).first();
|
|
|
|
if (await saveButton.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
console.log('✅ Found save/export button');
|
|
// Don't click it to avoid file downloads in test
|
|
}
|
|
|
|
// Summary
|
|
console.log('\n' + '='.repeat(80));
|
|
console.log(`✅ COMPLETED: ${carpenter.name} - ${project.type}`);
|
|
console.log(`📊 Extracted ${prices.length} prices from quote`);
|
|
console.log(`💾 Test data saved for future regression testing`);
|
|
console.log('='.repeat(80) + '\n');
|
|
});
|
|
}
|
|
}
|
|
|
|
test.afterAll(async () => {
|
|
// Save comprehensive test summary
|
|
const summary = {
|
|
timestamp: new Date().toISOString(),
|
|
totalQuotesGenerated: quoteStorage.length,
|
|
carpenters: Object.keys(CARPENTER_PERSONAS),
|
|
quotes: quoteStorage,
|
|
usageNotes: {
|
|
description: 'Reusable test data generated by carpenter agent',
|
|
purpose: 'Use these quotes for regression testing and validation',
|
|
files: 'All quote data stored in test-results/carpenter-agent-data/',
|
|
screenshots: 'UI screenshots for visual regression testing'
|
|
}
|
|
};
|
|
|
|
const summaryFile = 'test-results/carpenter-agent-data/TEST_SUMMARY.json';
|
|
fs.writeFileSync(summaryFile, JSON.stringify(summary, null, 2));
|
|
|
|
console.log('\n' + '='.repeat(80));
|
|
console.log('📊 CARPENTER AGENT TEST SUMMARY');
|
|
console.log('='.repeat(80));
|
|
console.log(`✅ Total quotes generated: ${quoteStorage.length}`);
|
|
console.log(`👷 Carpenters involved: ${Object.keys(CARPENTER_PERSONAS).length}`);
|
|
console.log(`📁 Test data location: test-results/carpenter-agent-data/`);
|
|
console.log(`📄 Summary file: ${summaryFile}`);
|
|
console.log('='.repeat(80) + '\n');
|
|
});
|
|
});
|