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');
|
||
});
|
||
});
|