1023 lines
45 KiB
JavaScript
1023 lines
45 KiB
JavaScript
// @ts-check
|
|
/**
|
|
* Lars's Comprehensive User Testing Suite for Tilbudgivern
|
|
* Perspective: Danish master carpenter (tømrermester), 47 years old
|
|
* 22 years in business, specializes in roofing (tagarbejde), 3 employees
|
|
*
|
|
* This test suite evaluates ALL major flows from a real carpenter's daily workflow.
|
|
*/
|
|
|
|
const { test, expect } = require('@playwright/test');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://tilbudsgiveren.alw.dk';
|
|
const SCREENSHOT_DIR = '/mnt/HC_Volume_103713257/tilbudgivern/screenshots/lars-test';
|
|
|
|
// Ensure screenshot directory exists
|
|
if (!fs.existsSync(SCREENSHOT_DIR)) {
|
|
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
|
}
|
|
|
|
const RESULTS = {
|
|
flows: {}
|
|
};
|
|
|
|
async function screenshot(page, name, notes = '') {
|
|
const filePath = path.join(SCREENSHOT_DIR, `${name}.png`);
|
|
await page.screenshot({ path: filePath, fullPage: true });
|
|
console.log(`[SCREENSHOT] ${name}.png${notes ? ' - ' + notes : ''}`);
|
|
return filePath;
|
|
}
|
|
|
|
async function loginToApp(page) {
|
|
await page.goto(BASE_URL + '/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Check if already logged in
|
|
const hasPasswordField = await page.locator('input[type="password"]').isVisible({ timeout: 3000 }).catch(() => false);
|
|
if (!hasPasswordField) {
|
|
console.log('[AUTH] Already logged in or no login required');
|
|
return true;
|
|
}
|
|
|
|
// Fill login form
|
|
await page.locator('input[type="text"]').first().fill('toemrer');
|
|
await page.locator('input[type="password"]').first().fill('tilbud2024');
|
|
await page.waitForTimeout(300);
|
|
|
|
// Try the submit button
|
|
const loginBtn = page.getByRole('button', { name: /log ind/i });
|
|
if (await loginBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await loginBtn.click();
|
|
} else {
|
|
await page.keyboard.press('Enter');
|
|
}
|
|
|
|
await page.waitForTimeout(3000);
|
|
await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => {});
|
|
|
|
const stillOnLogin = await page.locator('input[type="password"]').isVisible({ timeout: 2000 }).catch(() => false);
|
|
if (stillOnLogin) {
|
|
console.log('[AUTH] Login may have failed, trying again...');
|
|
return false;
|
|
}
|
|
console.log('[AUTH] Login successful');
|
|
return true;
|
|
}
|
|
|
|
// Wait for React app to fully render
|
|
async function waitForApp(page) {
|
|
await page.waitForTimeout(2000);
|
|
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
// Wait until body has substantial content (React has rendered)
|
|
await page.waitForFunction(() => {
|
|
const body = document.body.textContent || '';
|
|
return body.length > 100 && !body.includes('Du skal aktivere JavaScript');
|
|
}, { timeout: 15000 }).catch(() => {});
|
|
}
|
|
|
|
// ============================================================
|
|
// TEST 1: LOGIN FLOW
|
|
// ============================================================
|
|
test.describe('Lars User Testing Suite', () => {
|
|
test.setTimeout(300000); // 5 minutes per test
|
|
|
|
test('FLOW 1 - Login og første indtryk', async ({ page }) => {
|
|
console.log('\n════════════════════════════════════════');
|
|
console.log('FLOW 1: LOGIN OG FØRSTE INDTRYK');
|
|
console.log('════════════════════════════════════════');
|
|
|
|
// Navigate to the app
|
|
await page.goto(BASE_URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
await page.waitForTimeout(2000);
|
|
await screenshot(page, '01_01_initial_load', 'First look at the app');
|
|
|
|
// Check login page elements
|
|
const pageTitle = await page.title();
|
|
console.log(`[LOGIN] Page title: "${pageTitle}"`);
|
|
|
|
// Look at what's on the login page
|
|
const bodyText = await page.textContent('body');
|
|
console.log(`[LOGIN] Page content: "${bodyText.substring(0, 200)}"`);
|
|
|
|
// Check for branding
|
|
const hasLogo = bodyText.includes('Tilbudgivern') || bodyText.includes('Tilbudsgivern');
|
|
console.log(`[LOGIN] Has app name/branding: ${hasLogo}`);
|
|
|
|
// Check form fields
|
|
const usernameField = page.locator('input[type="text"]').first();
|
|
const passwordField = page.locator('input[type="password"]').first();
|
|
const loginButton = page.getByRole('button', { name: /log ind/i });
|
|
|
|
const hasUsername = await usernameField.isVisible({ timeout: 3000 }).catch(() => false);
|
|
const hasPassword = await passwordField.isVisible({ timeout: 3000 }).catch(() => false);
|
|
const hasLoginBtn = await loginButton.isVisible({ timeout: 3000 }).catch(() => false);
|
|
|
|
console.log(`[LOGIN] Username field visible: ${hasUsername}`);
|
|
console.log(`[LOGIN] Password field visible: ${hasPassword}`);
|
|
console.log(`[LOGIN] Login button visible: ${hasLoginBtn}`);
|
|
|
|
// Check for placeholder text / labels
|
|
const labels = await page.locator('label').all();
|
|
console.log(`[LOGIN] Form labels:`);
|
|
for (const label of labels) {
|
|
const text = await label.textContent();
|
|
console.log(` - "${text?.trim()}"`);
|
|
}
|
|
|
|
// Check input placeholders
|
|
const inputs = await page.locator('input').all();
|
|
for (const input of inputs) {
|
|
const type = await input.getAttribute('type');
|
|
const placeholder = await input.getAttribute('placeholder');
|
|
console.log(`[LOGIN] Input[type=${type}] placeholder="${placeholder}"`);
|
|
}
|
|
|
|
// Attempt login
|
|
if (hasUsername && hasPassword) {
|
|
await usernameField.fill('toemrer');
|
|
await passwordField.fill('tilbud2024');
|
|
await screenshot(page, '01_02_login_filled', 'Credentials entered');
|
|
|
|
if (hasLoginBtn) {
|
|
await loginButton.click();
|
|
} else {
|
|
await page.keyboard.press('Enter');
|
|
}
|
|
|
|
await page.waitForTimeout(3000);
|
|
await page.waitForLoadState('networkidle', { timeout: 20000 }).catch(() => {});
|
|
await waitForApp(page);
|
|
|
|
await screenshot(page, '01_03_after_login', 'After login attempt');
|
|
|
|
const currentUrl = page.url();
|
|
const loginSuccess = !currentUrl.includes('login') && !(await page.locator('input[type="password"]').isVisible({ timeout: 2000 }).catch(() => false));
|
|
console.log(`[LOGIN] Login result - URL: ${currentUrl}, Success: ${loginSuccess}`);
|
|
|
|
// Capture the main app landing page
|
|
const appContent = await page.textContent('body');
|
|
console.log(`[LOGIN] App landing content (first 500 chars): "${appContent.substring(0, 500)}"`);
|
|
|
|
// Check navigation elements
|
|
const navLinks = await page.locator('nav a, [role="navigation"] a, .MuiDrawer-root a, [class*="sidebar"] a').all();
|
|
console.log(`[LOGIN] Navigation links found: ${navLinks.length}`);
|
|
for (const link of navLinks) {
|
|
const text = await link.textContent();
|
|
const href = await link.getAttribute('href');
|
|
console.log(` Nav: "${text?.trim()}" -> ${href}`);
|
|
}
|
|
|
|
// Find all clickable navigation
|
|
const allTabs = await page.locator('[role="tab"], [class*="tab"]').all();
|
|
console.log(`[LOGIN] Tabs found: ${allTabs.length}`);
|
|
for (const tab of allTabs) {
|
|
const text = await tab.textContent();
|
|
console.log(` Tab: "${text?.trim()}"`);
|
|
}
|
|
|
|
expect(loginSuccess).toBe(true);
|
|
}
|
|
|
|
await screenshot(page, '01_04_logged_in_home', 'Home page after login');
|
|
console.log('[FLOW 1] DONE');
|
|
});
|
|
|
|
// ============================================================
|
|
// TEST 2: CREATE NEW PROJECT
|
|
// ============================================================
|
|
test('FLOW 2 - Opret nyt projekt', async ({ page }) => {
|
|
console.log('\n════════════════════════════════════════');
|
|
console.log('FLOW 2: OPRET NYT PROJEKT');
|
|
console.log('════════════════════════════════════════');
|
|
|
|
await loginToApp(page);
|
|
await waitForApp(page);
|
|
await screenshot(page, '02_01_home', 'Home after login');
|
|
|
|
// Examine the main app structure
|
|
const bodyText = await page.textContent('body');
|
|
console.log(`[PROJECT] Home page content: "${bodyText.substring(0, 800)}"`);
|
|
|
|
// Find all buttons on the main page
|
|
const allButtons = await page.locator('button:visible').all();
|
|
console.log(`[PROJECT] Visible buttons on home:`);
|
|
for (const btn of allButtons) {
|
|
const text = await btn.textContent();
|
|
const disabled = await btn.isDisabled();
|
|
if (text?.trim()) console.log(` Btn: "${text.trim()}" disabled=${disabled}`);
|
|
}
|
|
|
|
// Look for "Nyt Projekt" button
|
|
const nytProjektBtn = page.getByRole('button', { name: /nyt projekt/i });
|
|
const hasNytProjekt = await nytProjektBtn.isVisible({ timeout: 5000 }).catch(() => false);
|
|
console.log(`[PROJECT] "Nyt Projekt" button visible: ${hasNytProjekt}`);
|
|
|
|
if (hasNytProjekt) {
|
|
await nytProjektBtn.click();
|
|
await page.waitForTimeout(1000);
|
|
await screenshot(page, '02_02_nyt_projekt_clicked', 'After clicking Nyt Projekt');
|
|
}
|
|
|
|
// Check for project form
|
|
await waitForApp(page);
|
|
const formContent = await page.textContent('body');
|
|
console.log(`[PROJECT] Form content: "${formContent.substring(0, 1000)}"`);
|
|
|
|
// List all visible labels in the form
|
|
const allLabels = await page.locator('label:visible').all();
|
|
console.log(`[PROJECT] Form labels:`);
|
|
for (const label of allLabels) {
|
|
const text = await label.textContent();
|
|
const forAttr = await label.getAttribute('for');
|
|
console.log(` Label: "${text?.trim()}" for="${forAttr}"`);
|
|
}
|
|
|
|
// List all visible inputs
|
|
const allInputs = await page.locator('input:visible, textarea:visible').all();
|
|
console.log(`[PROJECT] Form inputs: ${allInputs.length}`);
|
|
for (const input of allInputs) {
|
|
const type = await input.getAttribute('type');
|
|
const id = await input.getAttribute('id');
|
|
const name = await input.getAttribute('name');
|
|
const placeholder = await input.getAttribute('placeholder');
|
|
const labelText = await page.locator(`label[for="${id}"]`).textContent().catch(() => '');
|
|
console.log(` Input[type=${type}, id=${id}] label="${labelText}" placeholder="${placeholder}"`);
|
|
}
|
|
|
|
// Fill project form - realistic carpenter scenario
|
|
// Project name field
|
|
const projectNameInput = page.locator('#field-projectName');
|
|
if (await projectNameInput.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await projectNameInput.fill('Tagpap udskiftning - Møllevej 14, Horsens');
|
|
console.log('[PROJECT] Filled project name via #field-projectName');
|
|
} else {
|
|
// Fallback
|
|
const firstTextInput = page.locator('input[type="text"]:visible').first();
|
|
if (await firstTextInput.isVisible().catch(() => false)) {
|
|
await firstTextInput.fill('Tagpap udskiftning - Møllevej 14, Horsens');
|
|
console.log('[PROJECT] Filled project name via first text input');
|
|
}
|
|
}
|
|
|
|
// Customer name
|
|
const customerNameInput = page.locator('#field-customerName');
|
|
if (await customerNameInput.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await customerNameInput.fill('Jens Hansen');
|
|
console.log('[PROJECT] Filled customer name');
|
|
}
|
|
|
|
// Email
|
|
const emailInput = page.locator('#field-customerEmail, input[type="email"]').first();
|
|
if (await emailInput.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await emailInput.fill('jens.hansen@gmail.com');
|
|
console.log('[PROJECT] Filled email');
|
|
}
|
|
|
|
// Address
|
|
const addressInput = page.locator('#field-address, input[placeholder*="adresse" i], input[placeholder*="vejnavn" i]').first();
|
|
if (await addressInput.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await addressInput.fill('Møllevej 14, 8700 Horsens');
|
|
console.log('[PROJECT] Filled address');
|
|
}
|
|
|
|
// Description - the realistic project description Lars would enter
|
|
const descInput = page.locator('#field-projectDescription, textarea:visible').first();
|
|
if (await descInput.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await descInput.fill('Udskiftning af tagpap på enfamilieshus, ca. 120 m2 fladt tag. Eksisterende tagpap er gammel og utæt, skal fjernes og erstattes med ny tagpap med 2 lag. Inkl. ny tagbrønde og tætning ved gennemføringer.');
|
|
console.log('[PROJECT] Filled description');
|
|
}
|
|
|
|
await screenshot(page, '02_03_form_filled', 'Project form filled in');
|
|
|
|
// Check for any tooltips or help text
|
|
const helperTexts = await page.locator('[class*="helper"], [class*="hint"], [class*="Helper"]').all();
|
|
console.log(`[PROJECT] Helper texts: ${helperTexts.length}`);
|
|
for (const helper of helperTexts) {
|
|
const text = await helper.textContent();
|
|
if (text?.trim()) console.log(` Helper: "${text.trim()}"`);
|
|
}
|
|
|
|
// Find submit / next button
|
|
const submitBtns = await page.locator('button:visible').all();
|
|
console.log('[PROJECT] Submit/Next buttons:');
|
|
for (const btn of submitBtns) {
|
|
const text = await btn.textContent();
|
|
const disabled = await btn.isDisabled();
|
|
if (text?.trim()) console.log(` "${text.trim()}" disabled=${disabled}`);
|
|
}
|
|
|
|
// Click submit / gem projekt
|
|
const gemProjektBtn = page.getByRole('button', { name: /gem projekt|opret projekt|næste|fortsæt/i });
|
|
const hasGem = await gemProjektBtn.isVisible({ timeout: 3000 }).catch(() => false);
|
|
if (hasGem) {
|
|
console.log('[PROJECT] Clicking submit button...');
|
|
await gemProjektBtn.click();
|
|
await page.waitForTimeout(3000);
|
|
await waitForApp(page);
|
|
await screenshot(page, '02_04_after_submit', 'After project creation');
|
|
|
|
const afterContent = await page.textContent('body');
|
|
console.log(`[PROJECT] After submit content (first 500 chars): "${afterContent.substring(0, 500)}"`);
|
|
}
|
|
|
|
await screenshot(page, '02_05_project_result', 'Final state after project creation');
|
|
console.log('[FLOW 2] DONE');
|
|
});
|
|
|
|
// ============================================================
|
|
// TEST 3: GEOMETRY STEP
|
|
// ============================================================
|
|
test('FLOW 3 - Geometri (tagmål)', async ({ page }) => {
|
|
console.log('\n════════════════════════════════════════');
|
|
console.log('FLOW 3: GEOMETRI (TAGMÅL)');
|
|
console.log('════════════════════════════════════════');
|
|
|
|
await loginToApp(page);
|
|
await waitForApp(page);
|
|
|
|
// First create or find an existing project, then navigate to geometry
|
|
// Click Nyt Projekt first
|
|
const nytProjektBtn = page.getByRole('button', { name: /nyt projekt/i });
|
|
if (await nytProjektBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await nytProjektBtn.click();
|
|
await page.waitForTimeout(1000);
|
|
}
|
|
|
|
// Fill minimum required fields
|
|
const projectNameInput = page.locator('#field-projectName');
|
|
if (await projectNameInput.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await projectNameInput.fill('Lars Test - Geometri');
|
|
}
|
|
const customerNameInput = page.locator('#field-customerName');
|
|
if (await customerNameInput.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await customerNameInput.fill('Test Kunde');
|
|
}
|
|
const emailInput = page.locator('#field-customerEmail, input[type="email"]').first();
|
|
if (await emailInput.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await emailInput.fill('test@test.dk');
|
|
}
|
|
const descInput = page.locator('#field-projectDescription, textarea:visible').first();
|
|
if (await descInput.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await descInput.fill('Fladtag 120 m2 tagpap');
|
|
}
|
|
|
|
// Submit to get to geometry
|
|
const gemBtn = page.getByRole('button', { name: /gem projekt/i });
|
|
if (await gemBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await gemBtn.click();
|
|
await page.waitForTimeout(4000);
|
|
await waitForApp(page);
|
|
}
|
|
|
|
await screenshot(page, '03_01_after_project_create', 'After project creation - should be on geometry');
|
|
|
|
// Check if we're on geometry step
|
|
const bodyText = await page.textContent('body');
|
|
console.log(`[GEOMETRY] Page content after project create: "${bodyText.substring(0, 1000)}"`);
|
|
|
|
// Look for roof type selector
|
|
const roofTypeElements = await page.locator('select, [role="combobox"], [class*="tagtype"], [class*="rooftype"]').all();
|
|
console.log(`[GEOMETRY] Roof type selectors: ${roofTypeElements.length}`);
|
|
|
|
// Check for geometry labels
|
|
const geometryKeywords = {
|
|
'bredde': 'Width',
|
|
'længde': 'Length',
|
|
'hældning': 'Pitch/Slope',
|
|
'grader': 'Degrees',
|
|
'areal': 'Area',
|
|
'tagareal': 'Roof area',
|
|
'm2': 'Square meters',
|
|
'tagtype': 'Roof type',
|
|
'sadeltag': 'Saddle roof',
|
|
'fladtag': 'Flat roof',
|
|
'valmtag': 'Hip roof',
|
|
'rygning': 'Ridge',
|
|
'facade': 'Facade',
|
|
'spær': 'Rafters',
|
|
};
|
|
|
|
console.log('[GEOMETRY] Keywords found on page:');
|
|
for (const [kw, meaning] of Object.entries(geometryKeywords)) {
|
|
if (bodyText.toLowerCase().includes(kw.toLowerCase())) {
|
|
console.log(` ✓ "${kw}" (${meaning})`);
|
|
} else {
|
|
console.log(` ✗ "${kw}" (${meaning}) - NOT FOUND`);
|
|
}
|
|
}
|
|
|
|
// Find all numeric inputs (typical for dimensions)
|
|
const numInputs = await page.locator('input[type="number"]:visible').all();
|
|
console.log(`[GEOMETRY] Numeric inputs: ${numInputs.length}`);
|
|
for (const input of numInputs) {
|
|
const placeholder = await input.getAttribute('placeholder');
|
|
const id = await input.getAttribute('id');
|
|
const name = await input.getAttribute('name');
|
|
// Find associated label
|
|
const labelEl = page.locator(`label[for="${id}"]`);
|
|
const labelText = await labelEl.textContent().catch(() => '');
|
|
console.log(` NumInput[id=${id}, placeholder=${placeholder}] label="${labelText}"`);
|
|
}
|
|
|
|
// Try to fill in geometry fields
|
|
// Bredde (width)
|
|
const breddeByPlaceholder = page.locator('input[type="number"][placeholder*="10" i], input[type="number"][placeholder*="bredde" i]').first();
|
|
const breddeInput = page.locator('label').filter({ hasText: /bredde/i }).locator('xpath=following-sibling::*//input, following::input').first();
|
|
|
|
// Try multiple approaches to find width input
|
|
let filledBredde = false;
|
|
const numInputsList = await page.locator('input[type="number"]:visible').all();
|
|
if (numInputsList.length >= 1) {
|
|
await numInputsList[0].fill('8');
|
|
console.log('[GEOMETRY] Filled first numeric input (bredde?) with 8');
|
|
filledBredde = true;
|
|
}
|
|
if (numInputsList.length >= 2) {
|
|
await numInputsList[1].fill('15');
|
|
console.log('[GEOMETRY] Filled second numeric input (længde?) with 15');
|
|
}
|
|
if (numInputsList.length >= 3) {
|
|
await numInputsList[2].fill('30');
|
|
console.log('[GEOMETRY] Filled third numeric input (hældning?) with 30');
|
|
}
|
|
|
|
await screenshot(page, '03_02_geometry_inputs', 'Geometry inputs - before calculate');
|
|
|
|
// Look for calculate button
|
|
const calcBtn = page.getByRole('button', { name: /beregn|beregn areal|calculate|gem nu|opdater/i });
|
|
const hasCalc = await calcBtn.isVisible({ timeout: 3000 }).catch(() => false);
|
|
console.log(`[GEOMETRY] Calculate button visible: ${hasCalc}`);
|
|
|
|
if (hasCalc) {
|
|
await calcBtn.click();
|
|
await page.waitForTimeout(3000);
|
|
await waitForApp(page);
|
|
await screenshot(page, '03_03_after_calculate', 'After area calculation');
|
|
|
|
const calcResult = await page.textContent('body');
|
|
const hasAreaResult = calcResult.includes('m²') || calcResult.includes('m2');
|
|
console.log(`[GEOMETRY] Area result shown (m²): ${hasAreaResult}`);
|
|
|
|
// Find calculated area value
|
|
const areaMatches = calcResult.match(/(\d+[.,]?\d*)\s*m[²2]/g);
|
|
if (areaMatches) {
|
|
console.log(`[GEOMETRY] Area values found: ${areaMatches.join(', ')}`);
|
|
}
|
|
}
|
|
|
|
// Check for visual SVG/diagram of roof
|
|
const svgElements = await page.locator('svg').all();
|
|
console.log(`[GEOMETRY] SVG visualizations: ${svgElements.length}`);
|
|
|
|
// Check for roof type options
|
|
const roofOptions = await page.locator('[class*="roofType"], [class*="tagtype"], [data-testid*="roof"]').all();
|
|
console.log(`[GEOMETRY] Roof type option elements: ${roofOptions.length}`);
|
|
|
|
// Look for specific roof type buttons/selectors
|
|
const roofTypeButtons = await page.locator('button, [role="option"], [class*="chip"]').filter({ hasText: /sadeltag|fladtag|valmtag|mansard|pult|kviste|københavner/i }).all();
|
|
console.log(`[GEOMETRY] Roof type buttons: ${roofTypeButtons.length}`);
|
|
for (const btn of roofTypeButtons) {
|
|
const text = await btn.textContent();
|
|
console.log(` Roof type: "${text?.trim()}"`);
|
|
}
|
|
|
|
// Try to find "Fortsæt" button to move to next step
|
|
const fortsætBtn = page.getByRole('button', { name: /fortsæt|næste|spring over/i });
|
|
const hasFortsæt = await fortsætBtn.isVisible({ timeout: 3000 }).catch(() => false);
|
|
console.log(`[GEOMETRY] "Fortsæt/Næste" button visible: ${hasFortsæt}`);
|
|
|
|
await screenshot(page, '03_04_geometry_complete', 'Geometry step complete');
|
|
console.log('[FLOW 3] DONE');
|
|
});
|
|
|
|
// ============================================================
|
|
// TEST 4: SMART PACKAGES
|
|
// ============================================================
|
|
test('FLOW 4 - Smart Pakke valg', async ({ page }) => {
|
|
console.log('\n════════════════════════════════════════');
|
|
console.log('FLOW 4: SMART PAKKE VALG');
|
|
console.log('════════════════════════════════════════');
|
|
|
|
await loginToApp(page);
|
|
await waitForApp(page);
|
|
|
|
// Create a project first to reach smart pakke step
|
|
const nytProjektBtn = page.getByRole('button', { name: /nyt projekt/i });
|
|
if (await nytProjektBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await nytProjektBtn.click();
|
|
await page.waitForTimeout(500);
|
|
}
|
|
|
|
const projectName = page.locator('#field-projectName');
|
|
if (await projectName.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await projectName.fill('Lars Smart Pakke Test');
|
|
}
|
|
const customerName = page.locator('#field-customerName');
|
|
if (await customerName.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await customerName.fill('Peter Andersen');
|
|
}
|
|
const email = page.locator('#field-customerEmail, input[type="email"]').first();
|
|
if (await email.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await email.fill('peter@andersen.dk');
|
|
}
|
|
const desc = page.locator('#field-projectDescription, textarea:visible').first();
|
|
if (await desc.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await desc.fill('Nyt sadeltag med teglsten, 180 m2');
|
|
}
|
|
|
|
const gemBtn = page.getByRole('button', { name: /gem projekt/i });
|
|
if (await gemBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await gemBtn.click();
|
|
await page.waitForTimeout(4000);
|
|
await waitForApp(page);
|
|
}
|
|
|
|
// If on geometry step, fill it minimally and continue
|
|
const numInputsOnGeom = await page.locator('input[type="number"]:visible').all();
|
|
if (numInputsOnGeom.length > 0) {
|
|
for (const input of numInputsOnGeom) {
|
|
await input.fill('10');
|
|
}
|
|
const gemNuBtn = page.getByRole('button', { name: /gem nu|beregn|fortsæt/i });
|
|
if (await gemNuBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await gemNuBtn.click();
|
|
await page.waitForTimeout(4000);
|
|
await waitForApp(page);
|
|
}
|
|
}
|
|
|
|
// Try to skip geometry or proceed to smart pakke
|
|
const skipBtn = page.getByRole('button', { name: /spring over|hop over|skip/i });
|
|
if (await skipBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await skipBtn.click();
|
|
await page.waitForTimeout(2000);
|
|
}
|
|
|
|
const fortsætBtn = page.getByRole('button', { name: /fortsæt til smart pakke|fortsæt/i });
|
|
if (await fortsætBtn.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
await fortsætBtn.click();
|
|
await page.waitForTimeout(3000);
|
|
await waitForApp(page);
|
|
}
|
|
|
|
await screenshot(page, '04_01_smart_pakke_step', 'Smart Pakke step');
|
|
|
|
const bodyText = await page.textContent('body');
|
|
console.log(`[SMART PAKKE] Page content: "${bodyText.substring(0, 1500)}"`);
|
|
|
|
// Look for package options
|
|
const packageOptions = await page.locator('.package-option, [class*="packageOption"], [class*="pakke"]').all();
|
|
console.log(`[SMART PAKKE] Package option elements: ${packageOptions.length}`);
|
|
|
|
// Look for package names/cards
|
|
const packageCards = await page.locator('[class*="card"], [class*="Card"]').all();
|
|
console.log(`[SMART PAKKE] Card elements: ${packageCards.length}`);
|
|
|
|
// List all headings to find package names
|
|
const headings = await page.locator('h1, h2, h3, h4, h5, h6').all();
|
|
console.log('[SMART PAKKE] Headings on page:');
|
|
for (const heading of headings) {
|
|
const text = await heading.textContent();
|
|
if (text?.trim()) console.log(` "${text.trim()}"`);
|
|
}
|
|
|
|
// Check for specific package types relevant for tømrer
|
|
const packageKeywords = [
|
|
'tagsten', 'tagpap', 'tegl', 'betontagsten', 'fibercementtagsten',
|
|
'tagrende', 'nedløb', 'tagvinduer', 'vindskede', 'sternbræt',
|
|
'undertag', 'spær', 'isol', 'tagbrønd'
|
|
];
|
|
console.log('[SMART PAKKE] Relevant material keywords:');
|
|
for (const kw of packageKeywords) {
|
|
if (bodyText.toLowerCase().includes(kw.toLowerCase())) {
|
|
console.log(` ✓ "${kw}"`);
|
|
}
|
|
}
|
|
|
|
// Find material count information
|
|
const materialCount = bodyText.match(/(\d+)\s*materiale/i);
|
|
if (materialCount) console.log(`[SMART PAKKE] Material count: ${materialCount[0]}`);
|
|
|
|
// Look for labor descriptions
|
|
const laborKeywords = ['timer', 'mandetimer', 'opgave', 'timeløn', 'arbejdstime'];
|
|
console.log('[SMART PAKKE] Labor keywords:');
|
|
for (const kw of laborKeywords) {
|
|
if (bodyText.toLowerCase().includes(kw.toLowerCase())) {
|
|
console.log(` ✓ "${kw}"`);
|
|
}
|
|
}
|
|
|
|
// Try to select a package
|
|
const firstPackage = page.locator('.package-option').first();
|
|
if (await firstPackage.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
console.log('[SMART PAKKE] Clicking first package option...');
|
|
await firstPackage.click();
|
|
await page.waitForTimeout(3000);
|
|
await waitForApp(page);
|
|
await screenshot(page, '04_02_package_selected', 'After package selection');
|
|
|
|
const afterSelect = await page.textContent('body');
|
|
console.log(`[SMART PAKKE] After selection: "${afterSelect.substring(0, 800)}"`);
|
|
|
|
// Check for material list after selection
|
|
const materialItems = await page.locator('[class*="material"], [class*="Material"]').all();
|
|
console.log(`[SMART PAKKE] Material items visible: ${materialItems.length}`);
|
|
} else {
|
|
// Try MUI select or dropdown
|
|
const selectElements = await page.locator('select:visible, [role="listbox"]:visible').all();
|
|
console.log(`[SMART PAKKE] Select elements: ${selectElements.length}`);
|
|
}
|
|
|
|
await screenshot(page, '04_03_smart_pakke_overview', 'Smart Pakke overview');
|
|
console.log('[FLOW 4] DONE');
|
|
});
|
|
|
|
// ============================================================
|
|
// TEST 5 & 6: FINAL REVIEW + AI TILBUDSTEKST (combined)
|
|
// ============================================================
|
|
test('FLOW 5+6 - Final Review og AI Tilbudstekst', async ({ page }) => {
|
|
console.log('\n════════════════════════════════════════');
|
|
console.log('FLOW 5+6: FINAL REVIEW OG AI TILBUDSTEKST');
|
|
console.log('════════════════════════════════════════');
|
|
|
|
await loginToApp(page);
|
|
await waitForApp(page);
|
|
await screenshot(page, '05_01_home', 'Home page for final review test');
|
|
|
|
// Look for existing projects to review
|
|
const existingProjectLinks = await page.locator('a[href*="/project"], a[href*="/projekt"], [class*="project-item"], [class*="projectItem"]').all();
|
|
console.log(`[FINAL REVIEW] Existing project links: ${existingProjectLinks.length}`);
|
|
|
|
if (existingProjectLinks.length > 0) {
|
|
const linkText = await existingProjectLinks[0].textContent();
|
|
console.log(`[FINAL REVIEW] Clicking first project: "${linkText?.trim()}"`);
|
|
await existingProjectLinks[0].click();
|
|
await page.waitForTimeout(2000);
|
|
await waitForApp(page);
|
|
await screenshot(page, '05_02_existing_project', 'Existing project page');
|
|
} else {
|
|
// Try looking for projects in a list
|
|
const projectRows = await page.locator('[class*="projektRow"], [class*="project-row"], tr').all();
|
|
console.log(`[FINAL REVIEW] Project rows: ${projectRows.length}`);
|
|
}
|
|
|
|
// Check current page content
|
|
const bodyText = await page.textContent('body');
|
|
console.log(`[FINAL REVIEW] Page content: "${bodyText.substring(0, 1000)}"`);
|
|
|
|
// Navigate to Final Review step if possible
|
|
const finalReviewStep = page.locator('[class*="step"]').filter({ hasText: /final review|tilbudstekst|gennemse/i });
|
|
if (await finalReviewStep.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await finalReviewStep.click();
|
|
await page.waitForTimeout(2000);
|
|
}
|
|
|
|
await screenshot(page, '05_03_final_review_page', 'Final Review page');
|
|
|
|
// Examine final review content
|
|
const reviewContent = await page.textContent('body');
|
|
|
|
// Check for price elements
|
|
const priceKeywords = {
|
|
'kr.': 'Danish price format',
|
|
'kr,': 'Danish price format alt',
|
|
',00': 'Price with decimals',
|
|
'moms': 'VAT (25%)',
|
|
'total': 'Total price',
|
|
'subtotal': 'Subtotal',
|
|
'pris': 'Price',
|
|
'tilbud': 'Quote/Offer',
|
|
};
|
|
console.log('[FINAL REVIEW] Price-related elements:');
|
|
for (const [kw, meaning] of Object.entries(priceKeywords)) {
|
|
if (reviewContent.toLowerCase().includes(kw.toLowerCase())) {
|
|
console.log(` ✓ "${kw}" (${meaning})`);
|
|
}
|
|
}
|
|
|
|
// Check for line items
|
|
const lineItems = await page.locator('[class*="lineItem"], [class*="line-item"], tr').all();
|
|
console.log(`[FINAL REVIEW] Line items: ${lineItems.length}`);
|
|
|
|
// Check for action buttons (PDF, send, ordrestyring)
|
|
const actionBtns = await page.locator('button:visible').all();
|
|
console.log('[FINAL REVIEW] Action buttons:');
|
|
for (const btn of actionBtns) {
|
|
const text = await btn.textContent();
|
|
const disabled = await btn.isDisabled();
|
|
if (text?.trim()) console.log(` "${text.trim()}" disabled=${disabled}`);
|
|
}
|
|
|
|
// Check for AI quote generation
|
|
const aiGenBtn = page.getByRole('button', { name: /generer.*tilbudstekst|generer ai|ai tilbud/i });
|
|
const hasAiBtn = await aiGenBtn.isVisible({ timeout: 3000 }).catch(() => false);
|
|
console.log(`[AI TILBUD] AI generate button visible: ${hasAiBtn}`);
|
|
|
|
// Check for radio buttons (manual vs AI)
|
|
const radioButtons = await page.locator('input[type="radio"]').all();
|
|
console.log(`[AI TILBUD] Radio buttons: ${radioButtons.length}`);
|
|
for (const radio of radioButtons) {
|
|
const value = await radio.getAttribute('value');
|
|
const checked = await radio.isChecked();
|
|
console.log(` Radio[value=${value}] checked=${checked}`);
|
|
}
|
|
|
|
// Check for textarea for quote text
|
|
const quoteTextarea = page.locator('.quote-textarea, textarea[placeholder*="tilbud" i], textarea[placeholder*="tekst" i]');
|
|
const hasQuoteTextarea = await quoteTextarea.isVisible({ timeout: 3000 }).catch(() => false);
|
|
console.log(`[AI TILBUD] Quote textarea visible: ${hasQuoteTextarea}`);
|
|
|
|
if (hasAiBtn) {
|
|
console.log('[AI TILBUD] Attempting to generate AI quote...');
|
|
await aiGenBtn.click();
|
|
await page.waitForTimeout(5000);
|
|
await screenshot(page, '05_04_generating_ai', 'AI generation in progress');
|
|
|
|
// Wait for generation
|
|
let elapsed = 0;
|
|
let generating = true;
|
|
while (generating && elapsed < 60000) {
|
|
await page.waitForTimeout(3000);
|
|
elapsed += 3000;
|
|
const spinnerVisible = await page.locator('[class*="spinner"], [class*="loading"], [aria-label*="loading"]').isVisible({ timeout: 1000 }).catch(() => false);
|
|
const genBtnText = await page.getByRole('button', { name: /genererer/i }).isVisible({ timeout: 1000 }).catch(() => false);
|
|
generating = spinnerVisible || genBtnText;
|
|
if (elapsed % 9000 === 0) console.log(`[AI TILBUD] Generating... ${elapsed / 1000}s`);
|
|
}
|
|
|
|
await screenshot(page, '05_05_after_ai_generation', 'After AI generation');
|
|
|
|
const generatedText = await page.textContent('body');
|
|
const hasLongText = generatedText.length > 5000;
|
|
console.log(`[AI TILBUD] Page content length after generation: ${generatedText.length}`);
|
|
|
|
// Try to get quote text content
|
|
if (hasQuoteTextarea) {
|
|
const textareaValue = await quoteTextarea.inputValue().catch(() => '');
|
|
console.log(`[AI TILBUD] Quote text length: ${textareaValue.length}`);
|
|
if (textareaValue.length > 50) {
|
|
console.log(`[AI TILBUD] Quote preview: "${textareaValue.substring(0, 400)}"`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for Ordrestyring integration button
|
|
const ordrestyringBtn = page.getByRole('button', { name: /ordrestyring|send tilbud|opret i ordrestyring/i });
|
|
const hasOrdrestyring = await ordrestyringBtn.isVisible({ timeout: 3000 }).catch(() => false);
|
|
console.log(`[FINAL REVIEW] Ordrestyring button visible: ${hasOrdrestyring}`);
|
|
|
|
// Check for PDF button
|
|
const pdfBtn = page.getByRole('button', { name: /pdf|download|hent/i });
|
|
const hasPdf = await pdfBtn.isVisible({ timeout: 3000 }).catch(() => false);
|
|
console.log(`[FINAL REVIEW] PDF button visible: ${hasPdf}`);
|
|
|
|
await screenshot(page, '05_06_final_review_complete', 'Final review complete');
|
|
console.log('[FLOW 5+6] DONE');
|
|
});
|
|
|
|
// ============================================================
|
|
// TEST 7: PLANNING DASHBOARD
|
|
// ============================================================
|
|
test('FLOW 7 - Planlægningsdashboard', async ({ page }) => {
|
|
console.log('\n════════════════════════════════════════');
|
|
console.log('FLOW 7: PLANLÆGNINGSDASHBOARD');
|
|
console.log('════════════════════════════════════════');
|
|
|
|
await loginToApp(page);
|
|
await waitForApp(page);
|
|
|
|
await screenshot(page, '07_01_home', 'Home page for planning test');
|
|
|
|
// Find planning/calendar link
|
|
const planningNavItem = page.locator('a, button, [role="tab"]').filter({ hasText: /planlæ|planning|kalender|calendar/i }).first();
|
|
const hasPlanningNav = await planningNavItem.isVisible({ timeout: 5000 }).catch(() => false);
|
|
console.log(`[PLANNING] Planning nav item visible: ${hasPlanningNav}`);
|
|
|
|
if (hasPlanningNav) {
|
|
const planText = await planningNavItem.textContent();
|
|
console.log(`[PLANNING] Planning nav text: "${planText?.trim()}"`);
|
|
await planningNavItem.click();
|
|
await page.waitForTimeout(2000);
|
|
await waitForApp(page);
|
|
} else {
|
|
// Try direct URL navigation
|
|
await page.goto(`${BASE_URL}/planlæggning`, { waitUntil: 'domcontentloaded' }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
if (page.url().includes('login')) {
|
|
await loginToApp(page);
|
|
await page.goto(`${BASE_URL}/planning`, { waitUntil: 'domcontentloaded' }).catch(() => {});
|
|
await page.waitForTimeout(2000);
|
|
}
|
|
}
|
|
|
|
await screenshot(page, '07_02_planning_page', 'Planning dashboard page');
|
|
|
|
const bodyText = await page.textContent('body');
|
|
console.log(`[PLANNING] Planning page content: "${bodyText.substring(0, 1000)}"`);
|
|
|
|
// Check for calendar elements
|
|
const calendarKeywords = [
|
|
'kalender', 'calendar', 'mandag', 'tirsdag', 'uge', 'måned', 'dag',
|
|
'ordre', 'job', 'opgave', 'medarbejder', 'projekt'
|
|
];
|
|
console.log('[PLANNING] Calendar keywords:');
|
|
for (const kw of calendarKeywords) {
|
|
if (bodyText.toLowerCase().includes(kw.toLowerCase())) {
|
|
console.log(` ✓ "${kw}"`);
|
|
}
|
|
}
|
|
|
|
// Look for calendar component
|
|
const calElements = await page.locator('[class*="calendar"], [class*="Calendar"], [class*="cal"]').all();
|
|
console.log(`[PLANNING] Calendar elements: ${calElements.length}`);
|
|
|
|
// Check for schedule/event items
|
|
const events = await page.locator('[class*="event"], [class*="Event"], [class*="appointment"]').all();
|
|
console.log(`[PLANNING] Events/appointments: ${events.length}`);
|
|
|
|
// Find navigation buttons (prev/next month)
|
|
const navBtns = await page.locator('button').filter({ hasText: /i dag|forrige|næste|<|>|←|→/i }).all();
|
|
console.log(`[PLANNING] Navigation buttons: ${navBtns.length}`);
|
|
for (const btn of navBtns) {
|
|
const text = await btn.textContent();
|
|
console.log(` "${text?.trim()}"`);
|
|
}
|
|
|
|
// Check for employee scheduling features
|
|
const employeeKeywords = ['medarbejder', 'ansat', 'Lars', 'hold'];
|
|
for (const kw of employeeKeywords) {
|
|
if (bodyText.toLowerCase().includes(kw.toLowerCase())) {
|
|
console.log(`[PLANNING] Employee feature found: "${kw}"`);
|
|
}
|
|
}
|
|
|
|
await screenshot(page, '07_03_planning_detail', 'Planning dashboard detail');
|
|
console.log('[FLOW 7] DONE');
|
|
});
|
|
|
|
// ============================================================
|
|
// TEST 8: SMART PAKKER MANAGEMENT
|
|
// ============================================================
|
|
test('FLOW 8 - Smart Pakker administration', async ({ page }) => {
|
|
console.log('\n════════════════════════════════════════');
|
|
console.log('FLOW 8: SMART PAKKER ADMINISTRATION');
|
|
console.log('════════════════════════════════════════');
|
|
|
|
await loginToApp(page);
|
|
await waitForApp(page);
|
|
|
|
await screenshot(page, '08_01_home', 'Home for smart pakker admin test');
|
|
|
|
// Try to find smart pakker admin - look in main app body for tabs/buttons
|
|
const bodyText = await page.textContent('body');
|
|
console.log(`[SMART ADMIN] Home content: "${bodyText.substring(0, 500)}"`);
|
|
|
|
// Look for "Smart Pakker" section in navigation
|
|
const smartPakkerNavItems = await page.locator('a, button, [role="tab"]').filter({ hasText: /smart.?pakk|pakker/i }).all();
|
|
console.log(`[SMART ADMIN] Smart Pakker nav items: ${smartPakkerNavItems.length}`);
|
|
for (const item of smartPakkerNavItems) {
|
|
const text = await item.textContent();
|
|
const href = await item.getAttribute('href');
|
|
console.log(` "${text?.trim()}" href=${href}`);
|
|
}
|
|
|
|
if (smartPakkerNavItems.length > 0) {
|
|
await smartPakkerNavItems[0].click();
|
|
await page.waitForTimeout(2000);
|
|
await waitForApp(page);
|
|
await screenshot(page, '08_02_smart_pakker_page', 'Smart Pakker management page');
|
|
}
|
|
|
|
// Also look in the main toggle/stepper area
|
|
const toggleBtns = await page.locator('[class*="toggle"], [class*="Toggle"], [role="tab"]').all();
|
|
console.log(`[SMART ADMIN] Toggle buttons: ${toggleBtns.length}`);
|
|
for (const btn of toggleBtns) {
|
|
const text = await btn.textContent();
|
|
console.log(` Toggle: "${text?.trim()}"`);
|
|
}
|
|
|
|
// Look for the main dashboard button if exists
|
|
const dashboardBtn = page.getByRole('button', { name: /dashboard|oversigt|smart pakk/i });
|
|
if (await dashboardBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await dashboardBtn.click();
|
|
await page.waitForTimeout(2000);
|
|
await waitForApp(page);
|
|
}
|
|
|
|
const adminContent = await page.textContent('body');
|
|
console.log(`[SMART ADMIN] Admin page content: "${adminContent.substring(0, 1500)}"`);
|
|
|
|
// Look for "Create new package" button
|
|
const createPkgBtn = await page.locator('button').filter({ hasText: /ny pakke|opret pakke|ny.*pakk|tilføj pakke|create|opret/i }).all();
|
|
console.log(`[SMART ADMIN] Create package buttons: ${createPkgBtn.length}`);
|
|
for (const btn of createPkgBtn) {
|
|
const text = await btn.textContent();
|
|
console.log(` Create btn: "${text?.trim()}"`);
|
|
}
|
|
|
|
// Look for existing packages list
|
|
const pkgList = await page.locator('[class*="packageList"], [class*="pakkeList"], table').all();
|
|
console.log(`[SMART ADMIN] Package list elements: ${pkgList.length}`);
|
|
|
|
// Count packages visible
|
|
const pkgItems = await page.locator('[class*="package-item"], [class*="packageItem"], tbody tr').all();
|
|
console.log(`[SMART ADMIN] Package items: ${pkgItems.length}`);
|
|
|
|
// Try to find and click "Ny Pakke" or similar
|
|
const nyPakkeBtn = page.getByRole('button', { name: /ny pakke|opret ny|create new/i });
|
|
if (await nyPakkeBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
console.log('[SMART ADMIN] Clicking "Ny Pakke" button...');
|
|
await nyPakkeBtn.click();
|
|
await page.waitForTimeout(2000);
|
|
await waitForApp(page);
|
|
await screenshot(page, '08_03_new_package_form', 'New package creation form');
|
|
|
|
const formContent = await page.textContent('body');
|
|
console.log(`[SMART ADMIN] New package form: "${formContent.substring(0, 800)}"`);
|
|
}
|
|
|
|
await screenshot(page, '08_04_smart_admin_final', 'Smart Pakker admin final state');
|
|
console.log('[FLOW 8] DONE');
|
|
});
|
|
|
|
// ============================================================
|
|
// TEST 9: EXPLORE ALL APP SECTIONS
|
|
// ============================================================
|
|
test('FLOW 9 - Komplet UI gennemgang', async ({ page }) => {
|
|
console.log('\n════════════════════════════════════════');
|
|
console.log('FLOW 9: KOMPLET UI GENNEMGANG');
|
|
console.log('════════════════════════════════════════');
|
|
|
|
await loginToApp(page);
|
|
await waitForApp(page);
|
|
|
|
const homeUrl = page.url();
|
|
console.log(`[UI] Home URL: ${homeUrl}`);
|
|
|
|
await screenshot(page, '09_01_full_home', 'Full home page');
|
|
|
|
// Get complete page structure
|
|
const bodyText = await page.textContent('body');
|
|
console.log(`\n[UI] FULL PAGE TEXT (2000 chars):\n${bodyText.substring(0, 2000)}`);
|
|
|
|
// Find ALL clickable elements
|
|
const allClickable = await page.locator('button:visible, a:visible, [role="tab"]:visible').all();
|
|
console.log(`\n[UI] ALL VISIBLE CLICKABLE ELEMENTS (${allClickable.length}):`);
|
|
for (const el of allClickable) {
|
|
const tagName = await el.evaluate(el => el.tagName);
|
|
const text = await el.textContent();
|
|
const href = await el.getAttribute('href');
|
|
const role = await el.getAttribute('role');
|
|
if (text?.trim()) {
|
|
console.log(` <${tagName.toLowerCase()}> "${text.trim()}" href=${href} role=${role}`);
|
|
}
|
|
}
|
|
|
|
// Map out the full app structure by clicking each nav item
|
|
const mainNavItems = await page.locator('[role="tab"]:visible, nav a:visible, [class*="NavItem"]:visible').all();
|
|
console.log(`\n[UI] MAIN NAV ITEMS: ${mainNavItems.length}`);
|
|
for (const item of mainNavItems) {
|
|
const text = await item.textContent();
|
|
console.log(` Nav: "${text?.trim()}"`);
|
|
}
|
|
|
|
// Check the main toggle (Nyt Projekt / Eksisterende)
|
|
const toggleGroup = await page.locator('[class*="ToggleButtonGroup"], [class*="toggle-group"]').all();
|
|
console.log(`[UI] Toggle groups: ${toggleGroup.length}`);
|
|
|
|
// Check stepper
|
|
const stepperItems = await page.locator('[class*="Step"], [class*="step"], .MuiStep-root').all();
|
|
console.log(`[UI] Stepper items: ${stepperItems.length}`);
|
|
for (const step of stepperItems) {
|
|
const text = await step.textContent();
|
|
const classes = await step.getAttribute('class');
|
|
console.log(` Step: "${text?.trim().substring(0, 50)}" classes="${classes?.substring(0, 80)}"`);
|
|
}
|
|
|
|
// Look at the existing project view (not new project)
|
|
const eksisterendeProjektBtn = page.getByRole('button', { name: /eksisterende projekt|gem.*eksisterende/i });
|
|
if (await eksisterendeProjektBtn.isVisible({ timeout: 3000 }).catch(() => false)) {
|
|
await eksisterendeProjektBtn.click();
|
|
await page.waitForTimeout(2000);
|
|
await screenshot(page, '09_02_existing_projects', 'Existing projects view');
|
|
|
|
const projectsContent = await page.textContent('body');
|
|
console.log(`[UI] Existing projects view: "${projectsContent.substring(0, 1000)}"`);
|
|
}
|
|
|
|
// Try to find and access Smart Pakker admin section
|
|
const tabBarItems = await page.locator('[role="tab"]').all();
|
|
console.log(`[UI] Tab bar items: ${tabBarItems.length}`);
|
|
for (const tab of tabBarItems) {
|
|
const text = await tab.textContent();
|
|
const selected = await tab.getAttribute('aria-selected');
|
|
console.log(` Tab: "${text?.trim()}" selected=${selected}`);
|
|
}
|
|
|
|
await screenshot(page, '09_03_complete_ui', 'Complete UI overview');
|
|
console.log('[FLOW 9] DONE');
|
|
});
|
|
});
|