Files
tilbudgivern/tests/lars-full-user-test.spec.js

515 lines
19 KiB
JavaScript

// @ts-check
/**
* Lars's Full User Testing Suite
* Testing Tilbudgivern from the perspective of a Danish master carpenter
* 47 years old, 22 years in business, specializes in roofing work (tagarbejde)
*/
const { test, expect } = require('@playwright/test');
const path = require('path');
const fs = require('fs');
const 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 });
}
async function takeScreenshot(page, name) {
const filePath = path.join(SCREENSHOT_DIR, `${name}.png`);
await page.screenshot({ path: filePath, fullPage: true });
console.log(`Screenshot saved: ${filePath}`);
return filePath;
}
async function login(page) {
await page.goto(BASE_URL);
await page.waitForLoadState('networkidle');
// Try to find login form
const usernameField = page.locator('input[type="text"], input[name="username"], input[placeholder*="brugernavn" i], input[placeholder*="user" i]').first();
const passwordField = page.locator('input[type="password"]').first();
if (await usernameField.isVisible()) {
await usernameField.fill('toemrer');
await passwordField.fill('toemrer123');
await page.keyboard.press('Enter');
await page.waitForLoadState('networkidle');
}
}
// ============================================================
// TEST 1: LOGIN FLOW
// ============================================================
test('01 - Login flow', async ({ page }) => {
console.log('\n=== TEST 1: LOGIN FLOW ===');
await page.goto(BASE_URL);
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '01_login_page');
// Check what's on the login page
const pageTitle = await page.title();
const pageContent = await page.textContent('body');
console.log('Page title:', pageTitle);
console.log('Page has login form:', pageContent.includes('login') || pageContent.includes('Login') || pageContent.includes('brugernavn'));
// Find and fill login fields
const inputs = await page.locator('input').all();
console.log('Number of input fields:', inputs.length);
for (const input of inputs) {
const type = await input.getAttribute('type');
const placeholder = await input.getAttribute('placeholder');
const name = await input.getAttribute('name');
console.log(`Input: type=${type}, placeholder=${placeholder}, name=${name}`);
}
// Try login
const usernameInput = page.locator('input[type="text"], input:not([type="password"]):not([type="hidden"])').first();
const passwordInput = page.locator('input[type="password"]').first();
if (await usernameInput.count() > 0) {
await usernameInput.fill('toemrer');
await passwordInput.fill('toemrer123');
await takeScreenshot(page, '01_login_filled');
// Find submit button
const submitBtn = page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Log ind"), button:has-text("Sign in")').first();
if (await submitBtn.count() > 0) {
await submitBtn.click();
} else {
await page.keyboard.press('Enter');
}
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '01_after_login');
const currentUrl = page.url();
console.log('URL after login:', currentUrl);
console.log('Login successful:', !currentUrl.includes('login'));
}
// Report login page elements
const buttons = await page.locator('button').all();
for (const btn of buttons) {
const text = await btn.textContent();
console.log('Button:', text?.trim());
}
expect(page.url()).toBeDefined();
});
// ============================================================
// TEST 2: CREATE NEW PROJECT
// ============================================================
test('02 - Create new project', async ({ page }) => {
console.log('\n=== TEST 2: CREATE NEW PROJECT ===');
await login(page);
await takeScreenshot(page, '02_home_after_login');
// Log all visible text on the main page
const bodyText = await page.textContent('body');
console.log('Main page keywords found:');
const keywords = ['projekt', 'Projekt', 'nyt', 'Nyt', 'opret', 'Opret', 'tilbud', 'Tilbud', 'kunde', 'Kunde', 'new', 'New', 'create', 'Create'];
keywords.forEach(kw => {
if (bodyText.includes(kw)) console.log(` - "${kw}" found`);
});
// Find navigation items
const navItems = await page.locator('nav a, nav button, .nav a, .nav button, [role="navigation"] a, [role="navigation"] button').all();
console.log('Navigation items:');
for (const item of navItems) {
const text = await item.textContent();
const href = await item.getAttribute('href');
console.log(` - "${text?.trim()}" href=${href}`);
}
// Find "New project" button or similar
const createButtons = await page.locator('button, a').filter({ hasText: /ny|nyt|opret|create|new|projekt|tilbud/i }).all();
console.log('Create/New buttons found:', createButtons.length);
for (const btn of createButtons) {
const text = await btn.textContent();
console.log(` - "${text?.trim()}"`);
}
// Click the first "create" or "new" button
const newProjectBtn = page.locator('button, a').filter({ hasText: /nyt projekt|nyt tilbud|opret projekt|ny projekt|new project/i }).first();
if (await newProjectBtn.count() > 0) {
console.log('Found new project button, clicking...');
await newProjectBtn.click();
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '02_new_project_form');
} else {
// Try clicking a "+" button or FAB
const fabBtn = page.locator('[aria-label*="add" i], [aria-label*="new" i], [aria-label*="create" i], button:has-text("+")').first();
if (await fabBtn.count() > 0) {
await fabBtn.click();
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '02_after_fab_click');
}
}
// Check for project form fields
const formInputs = await page.locator('input, textarea').all();
console.log('Form inputs found:', formInputs.length);
for (const input of formInputs) {
const type = await input.getAttribute('type');
const placeholder = await input.getAttribute('placeholder');
const label = await input.getAttribute('aria-label');
const name = await input.getAttribute('name');
console.log(` Input: type=${type}, placeholder="${placeholder}", label="${label}", name="${name}"`);
}
// Fill in project details - try common field patterns
// Project name
const projectNameField = page.locator('input[name*="project" i], input[name*="projekt" i], input[placeholder*="projekt" i], input[placeholder*="navn" i]').first();
if (await projectNameField.count() > 0) {
await projectNameField.fill('Tagpap udskiftning - Møllevej 14');
console.log('Filled project name');
}
// Customer name
const customerNameField = page.locator('input[name*="customer" i], input[name*="kunde" i], input[placeholder*="kunde" i]').first();
if (await customerNameField.count() > 0) {
await customerNameField.fill('Jens Hansen');
console.log('Filled customer name');
}
// Email
const emailField = page.locator('input[type="email"], input[name*="email" i], input[placeholder*="email" i]').first();
if (await emailField.count() > 0) {
await emailField.fill('jens.hansen@gmail.com');
console.log('Filled email');
}
// Description
const descField = page.locator('textarea, input[name*="description" i], input[name*="beskrivelse" i]').first();
if (await descField.count() > 0) {
await descField.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.');
console.log('Filled description');
}
await takeScreenshot(page, '02_project_form_filled');
// Log all visible labels
const labels = await page.locator('label').all();
console.log('Form labels:');
for (const label of labels) {
const text = await label.textContent();
console.log(` - "${text?.trim()}"`);
}
expect(page.url()).toBeDefined();
});
// ============================================================
// TEST 3: GEOMETRY STEP
// ============================================================
test('03 - Geometry step', async ({ page }) => {
console.log('\n=== TEST 3: GEOMETRY STEP ===');
await login(page);
// Navigate to geometry - try finding it in the nav or creating a project first
// First try to navigate directly to a project with geometry
const projectLinks = await page.locator('a[href*="project"], a[href*="projekt"]').all();
if (projectLinks.length > 0) {
await projectLinks[0].click();
await page.waitForLoadState('networkidle');
}
await takeScreenshot(page, '03_geometry_page_init');
// Look for geometry step indicators
const stepIndicators = await page.locator('[class*="step"], [class*="Step"], [class*="wizard"], [aria-label*="step"]').all();
console.log('Step indicators found:', stepIndicators.length);
// Look for geometry-related fields
const geometryKeywords = ['tagareal', 'areal', 'bredde', 'længde', 'hældning', 'grader', 'm2', 'tagtype'];
const bodyText = await page.textContent('body');
console.log('Geometry keywords found:');
geometryKeywords.forEach(kw => {
if (bodyText.toLowerCase().includes(kw.toLowerCase())) {
console.log(` - "${kw}" found`);
}
});
// Find geometry input fields
const numericInputs = await page.locator('input[type="number"]').all();
console.log('Numeric inputs:', numericInputs.length);
for (const input of numericInputs) {
const placeholder = await input.getAttribute('placeholder');
const label = await input.getAttribute('aria-label');
const name = await input.getAttribute('name');
const id = await input.getAttribute('id');
console.log(` Numeric input: placeholder="${placeholder}", label="${label}", name="${name}", id="${id}"`);
}
// Check for roof type selector
const roofTypeSelector = page.locator('select, [role="combobox"], [role="listbox"]').first();
if (await roofTypeSelector.count() > 0) {
console.log('Roof type selector found');
const options = await roofTypeSelector.locator('option').all();
for (const opt of options) {
const text = await opt.textContent();
console.log(` Roof type option: "${text?.trim()}"`);
}
}
await takeScreenshot(page, '03_geometry_fields');
expect(page.url()).toBeDefined();
});
// ============================================================
// TEST 4: SMART PACKAGES
// ============================================================
test('04 - Smart Package selection', async ({ page }) => {
console.log('\n=== TEST 4: SMART PACKAGE SELECTION ===');
await login(page);
await takeScreenshot(page, '04_home_for_packages');
// Look for smart packages in navigation
const smartPakkerLink = page.locator('a, button, [role="tab"]').filter({ hasText: /smart.?pakk|pakk|packages/i }).first();
if (await smartPakkerLink.count() > 0) {
console.log('Found Smart Pakker link, clicking...');
await smartPakkerLink.click();
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '04_smart_pakker_page');
}
// Log page content related to packages
const bodyText = await page.textContent('body');
const packageKeywords = ['pakke', 'package', 'materiale', 'tagsten', 'tagpap', 'spær', 'tagrende'];
console.log('Package keywords found:');
packageKeywords.forEach(kw => {
if (bodyText.toLowerCase().includes(kw.toLowerCase())) {
console.log(` - "${kw}" found`);
}
});
// Find package cards/items
const packageCards = await page.locator('[class*="card"], [class*="Card"], [class*="pakke"], [class*="package"]').all();
console.log('Package cards found:', packageCards.length);
// List package names
const packageTitles = await page.locator('h2, h3, h4, [class*="title"], [class*="name"]').all();
console.log('Package titles found:');
for (const title of packageTitles) {
const text = await title.textContent();
if (text && text.trim().length > 0 && text.trim().length < 100) {
console.log(` - "${text.trim()}"`);
}
}
await takeScreenshot(page, '04_packages_listed');
// Try to select a package
const selectButtons = await page.locator('button').filter({ hasText: /vælg|select|tilføj|add/i }).all();
console.log('Select/Add buttons:', selectButtons.length);
if (selectButtons.length > 0) {
const firstBtnText = await selectButtons[0].textContent();
console.log('Clicking first select button:', firstBtnText);
await selectButtons[0].click();
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '04_package_selected');
}
expect(page.url()).toBeDefined();
});
// ============================================================
// TEST 5: FINAL REVIEW
// ============================================================
test('05 - Final Review', async ({ page }) => {
console.log('\n=== TEST 5: FINAL REVIEW ===');
await login(page);
// Look for review/quote links
const reviewLink = page.locator('a, button').filter({ hasText: /gennemse|review|tilbud|quote|opsummering|summary/i }).first();
if (await reviewLink.count() > 0) {
await reviewLink.click();
await page.waitForLoadState('networkidle');
}
await takeScreenshot(page, '05_final_review');
// Check for pricing elements
const bodyText = await page.textContent('body');
const priceKeywords = ['kr', 'pris', 'total', 'subtotal', 'moms', 'mva', 'inkl', 'eksl'];
console.log('Price-related content:');
priceKeywords.forEach(kw => {
if (bodyText.toLowerCase().includes(kw.toLowerCase())) {
console.log(` - "${kw}" found`);
}
});
// Look for line items / materials table
const tables = await page.locator('table, [class*="table"], [class*="Table"]').all();
console.log('Tables found:', tables.length);
// Check for PDF/send buttons
const actionButtons = await page.locator('button').all();
console.log('Action buttons on review page:');
for (const btn of actionButtons) {
const text = await btn.textContent();
if (text && text.trim().length > 0) {
console.log(` - "${text.trim()}"`);
}
}
await takeScreenshot(page, '05_review_content');
expect(page.url()).toBeDefined();
});
// ============================================================
// TEST 6: EXPLORE THE FULL APP (comprehensive navigation)
// ============================================================
test('06 - Full app exploration', async ({ page }) => {
console.log('\n=== TEST 6: FULL APP EXPLORATION ===');
await login(page);
await page.waitForLoadState('networkidle');
const currentUrl = page.url();
console.log('Landing URL after login:', currentUrl);
await takeScreenshot(page, '06_landing_page');
// Get full page text for analysis
const bodyText = await page.textContent('body');
console.log('\n--- PAGE TEXT (first 2000 chars) ---');
console.log(bodyText.substring(0, 2000));
// Find all navigation links
const allLinks = await page.locator('a[href]').all();
console.log('\n--- ALL LINKS ---');
for (const link of allLinks) {
const text = await link.textContent();
const href = await link.getAttribute('href');
if (text && text.trim() && href) {
console.log(` "${text.trim()}" -> ${href}`);
}
}
// Find all buttons
const allButtons = await page.locator('button:visible').all();
console.log('\n--- ALL VISIBLE BUTTONS ---');
for (const btn of allButtons) {
const text = await btn.textContent();
if (text && text.trim()) {
console.log(` "${text.trim()}"`);
}
}
// Check for sidebar/menu
const sidebar = page.locator('[class*="sidebar"], [class*="Sidebar"], [class*="drawer"], [role="navigation"]').first();
if (await sidebar.count() > 0) {
console.log('\nSidebar found');
const sidebarText = await sidebar.textContent();
console.log('Sidebar text:', sidebarText?.substring(0, 500));
}
// Check MUI tabs/stepper
const tabs = await page.locator('[role="tab"]').all();
console.log('\n--- TABS ---');
for (const tab of tabs) {
const text = await tab.textContent();
console.log(` Tab: "${text?.trim()}"`);
}
expect(page.url()).toBeDefined();
});
// ============================================================
// TEST 7: NAVIGATE TO PLANNING DASHBOARD
// ============================================================
test('07 - Planning Dashboard', async ({ page }) => {
console.log('\n=== TEST 7: PLANNING DASHBOARD ===');
await login(page);
// Try to find planning/planlægning link
const planningLink = page.locator('a, button, [role="tab"]').filter({ hasText: /planlæ|planning|kalender|calendar|schedule/i }).first();
if (await planningLink.count() > 0) {
console.log('Found planning link, clicking...');
await planningLink.click();
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '07_planning_dashboard');
} else {
// Try navigating directly
await page.goto(`${BASE_URL}/planning`);
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '07_planning_direct');
if (page.url().includes('login')) {
await login(page);
await page.goto(`${BASE_URL}/planning`);
await page.waitForLoadState('networkidle');
}
}
const bodyText = await page.textContent('body');
console.log('Planning page text (first 1000 chars):', bodyText.substring(0, 1000));
// Check for calendar elements
const calendarElements = await page.locator('[class*="calendar"], [class*="Calendar"], [class*="cal"]').all();
console.log('Calendar elements found:', calendarElements.length);
await takeScreenshot(page, '07_planning_content');
expect(page.url()).toBeDefined();
});
// ============================================================
// TEST 8: SMART PAKKER MANAGEMENT
// ============================================================
test('08 - Smart Pakker management', async ({ page }) => {
console.log('\n=== TEST 8: SMART PAKKER MANAGEMENT ===');
await login(page);
// Try to find settings or admin area
const settingsLink = page.locator('a, button').filter({ hasText: /indstillinger|settings|admin|konfiguration/i }).first();
if (await settingsLink.count() > 0) {
await settingsLink.click();
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '08_settings_page');
}
// Try direct navigation to smart packages admin
const paths = ['/smart-pakker', '/admin/smart-pakker', '/settings', '/admin'];
for (const urlPath of paths) {
await page.goto(`${BASE_URL}${urlPath}`);
await page.waitForLoadState('networkidle');
const url = page.url();
const bodyText = await page.textContent('body');
if (!url.includes('login') && (bodyText.includes('pakk') || bodyText.includes('package'))) {
console.log(`Found smart packages at: ${urlPath}`);
await takeScreenshot(page, `08_smart_pakker_${urlPath.replace(/\//g, '_')}`);
break;
}
}
const bodyText = await page.textContent('body');
console.log('Page content (first 1000 chars):', bodyText.substring(0, 1000));
// Look for "create new package" button
const createPkgBtn = page.locator('button').filter({ hasText: /ny pakke|opret pakke|new package|create package|tilføj pakke/i }).first();
if (await createPkgBtn.count() > 0) {
console.log('Found create package button');
await createPkgBtn.click();
await page.waitForLoadState('networkidle');
await takeScreenshot(page, '08_create_package_form');
}
expect(page.url()).toBeDefined();
});