// Run against a built frontend with PLAYWRIGHT_MODULE pointing to an installed Playwright package. // All API calls are isolated fixtures; no real account or application data is used. import assert from 'node:assert/strict' const { chromium } = await import(process.env.PLAYWRIGHT_MODULE || 'playwright') const base = process.env.TIME_V2_FRONTEND_URL || 'http://127.0.0.1:3148' const browser = await chromium.launch({headless:true}) const page = await browser.newPage({viewport:{width:1440,height:1100}}) const errors = [] page.on('pageerror',e => errors.push(e.message)) const user = '11111111-1111-4111-8111-111111111111' const month = new Date().toISOString().slice(0,7) const start = `${month}-01T06:00:00.000Z` let item = {id:'22222222-2222-4222-8222-222222222222',user_id:user,type:'work',description:'Test Anwesenheit',started_at:start, ended_at:`${month}-01T14:30:00.000Z`,date_from:null,date_to:null,timezone:'Europe/Berlin',status:'factual',version:1, breaks:[{started_at:`${month}-01T10:00:00.000Z`,ended_at:`${month}-01T10:30:00.000Z`}],projects:[]} const profile={id:user,user_id:user,full_name:'Anna Beispiel',first_name:'Anna',last_name:'Beispiel',pinned_on_navigation:[],temp_config:{},role:{rights:[]}} const tenant={id:1,name:'Testbetrieb',extraModules:[],features:{},locked:'none'} const commands=[] await page.addInitScript(() => localStorage.setItem('token','fixture-only')) await page.route('**/*',async route => { const url=new URL(route.request().url()) if(url.origin!==new URL(base).origin) return route.abort() if(!url.pathname.startsWith('/api/') && !url.pathname.startsWith('/auth/')) return route.continue() let body=[] if(url.pathname==='/api/me') body={user:{id:user,email:'anna@example.test',is_admin:true},profile,permissions:[],tenants:[tenant],activeTenant:1} else if(url.pathname==='/api/tenant') body=tenant else if(url.pathname==='/api/tenant/profiles') body={data:[profile]} else if(url.pathname==='/api/v2/time/context') body={profiles:[{...profile,activated_at:start}],projects:[{id:1,name:'Projekt Nord'}],permissions:{readAll:true,writeAll:true,approve:true,activate:true}} else if(url.pathname==='/api/v2/time/spans') body={items:[item],active:item.ended_at ? null : item,account:{activated_at:start}} else if(url.pathname==='/api/v2/time/evaluation') body={summary:{timeSpanWorkingMinutes:480,presenceMinutes:510,breakMinutes:30,sumWorkingMinutesApproved:480,sumWorkingMinutesSubmitted:480,saldoApproved:0,saldoSubmitted:0,sumVacationDays:0,sumSickDays:0,sumRecreationDays:0}} else if(url.pathname==='/api/v2/time/commands') { const data=route.request().postDataJSON();commands.push(data) const c=data.command if(c.action==='update') item={...item,...c.data,status:'factual',version:item.version+1} else if(c.action==='submit') item={...item,status:'submitted',version:item.version+1} else if(c.action==='approve') item={...item,status:'approved',version:item.version+1} body=item } else if(url.pathname.endsWith('/history')) body=[{id:'history-1',span_id:item.id,actor_id:user,action:'create',reason:'',before_data:null,after_data:item,created_at:start}] return route.fulfill({contentType:'application/json',body:JSON.stringify(body)}) }) try { await page.goto(`${base}/v2/time`,{waitUntil:'networkidle'}) await page.getByRole('button',{name:'Manuell erfassen',exact:true}).waitFor() await page.getByRole('button',{name:'Zeitstrahl',exact:true}).click() await page.getByRole('img',{name:'Anwesenheit mit 1 Pausen'}).waitFor() await page.getByRole('button',{name:'Bearbeiten',exact:true}).click() await page.getByLabel('Beschreibung',{exact:true}).fill('Korrigierte Anwesenheit') await page.getByLabel('Korrekturgrund',{exact:true}).fill('Notiz korrigiert') await page.getByRole('button',{name:'Speichern',exact:true}).click() await page.getByText('Zeit gespeichert.',{exact:true}).waitFor() assert.equal(commands.at(-1).command.data.breaks.length,1) assert.equal(commands.at(-1).command.version,1) assert.equal(commands.at(-1).command.reason,'Notiz korrigiert') await page.getByRole('button',{name:'Einreichen',exact:true}).click() await page.getByRole('button',{name:'Genehmigen',exact:true}).click() await page.getByRole('button',{name:'Verlauf',exact:true}).click() await page.getByText('Verlauf ·',{exact:false}).waitFor() await page.getByRole('button',{name:'Schließen',exact:true}).click() await page.getByRole('button',{name:'Auswertung',exact:true}).click() await page.getByText('Saldo genehmigt',{exact:true}).waitFor() await page.screenshot({path:process.env.TIME_V2_SCREENSHOT_DESKTOP || '/tmp/fedeo-time-v2-desktop.png',fullPage:true}) await page.setViewportSize({width:390,height:844}) await page.getByRole('button',{name:'Meine Zeiten',exact:true}).click() await page.getByRole('button',{name:'Manuell erfassen',exact:true}).click() await page.getByLabel(/^Art/).selectOption('vacation') await page.getByLabel('Ganztägig',{exact:true}).check() await page.getByLabel('Erster Tag',{exact:true}).waitFor() await page.screenshot({path:process.env.TIME_V2_SCREENSHOT_MOBILE || '/tmp/fedeo-time-v2-mobile.png',fullPage:true}) assert.equal(await page.evaluate(() => document.documentElement.scrollWidth>window.innerWidth),false,'Mobile layout overflows') assert.deepEqual(errors,[],'Browser runtime errors') console.log('Time 2.0 browser checks passed: editing with pauses, submission, approval, history, evaluation, mobile absence form.') } catch (error) { await page.screenshot({path:'/tmp/fedeo-time-v2-error.png',fullPage:true}); throw error } finally {await browser.close()}