// Acceptance against the production frontend and real SQLite backend, with disposable data. import {chromium, expect} from '@playwright/test'; import {spawn, spawnSync} from 'node:child_process'; import {mkdtemp, mkdir, rm, writeFile} from 'node:fs/promises'; import {tmpdir} from 'node:os'; import path from 'node:path'; import net from 'node:net'; import {fileURLToPath} from 'node:url'; import {createRequire} from 'node:module'; const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..'); const {PNG}=createRequire(path.join(root,'backend/package.json'))('pngjs'); const data=await mkdtemp(path.join(tmpdir(),'conductor-identity-')); const artifacts=path.join(root,'test-results/slice10'); await mkdir(artifacts,{recursive:true}); const port=await new Promise(resolve=>{ const socket=net.createServer();socket.listen(0,'127.0.0.1',()=>{const value=socket.address().port;socket.close(()=>resolve(value));}); }); const base='http://127.0.0.1:'+port; const env={...process.env,PORT:String(port),CONDUCTOR_DATA_DIR:data,CONDUCTOR_STATIC_DIR:path.join(root,'frontend/build'),CONDUCTOR_SECRET_KEY:Buffer.alloc(32,19).toString('base64')}; let output=''; const server=spawn(process.execPath,['backend/dist/index.js'],{cwd:root,env,stdio:['ignore','pipe','pipe']}); server.stdout.on('data',chunk=>{output+=chunk;});server.stderr.on('data',chunk=>{output+=chunk;}); let browser,page; const checks=[]; function register(manifest) { const result=spawnSync(process.execPath,['backend/dist/scripts/includedApps.js','register'],{cwd:root,env,input:JSON.stringify(manifest),encoding:'utf8'}); if(result.status!==0)throw new Error(result.stderr||result.stdout); } async function identity(target,title,icon='/favicon.svg') { await expect(target).toHaveTitle(title); await expect.poll(()=>target.locator('#conductor-favicon').evaluate(el=>new URL(el.href).pathname)).toBe(icon); checks.push({url:new URL(target.url()).pathname,title,icon}); } try { for(let attempt=0;attempt<100;attempt++){ if(server.exitCode!==null)throw new Error(output); if(await fetch(base+'/api/health').then(r=>r.ok).catch(()=>false))break; if(attempt===99)throw new Error('Backend did not start.\n'+output); await new Promise(resolve=>setTimeout(resolve,50)); } browser=await chromium.launch({headless:true}); const context=await browser.newContext({baseURL:base,viewport:{width:1440,height:1040}}); page=await context.newPage(); const errors=[];page.on('pageerror',error=>errors.push(error.message)); await page.goto('/'); await identity(page,'Set up · Conductor'); const setup=await context.request.get('/api/auth/setup'); const created=await context.request.post('/api/auth/setup',{headers:{'X-Setup-Token':(await setup.json()).setupToken},data:{username:'identity-admin',password:'identity-test-password',displayName:'Test Administrator'}}); expect(created.status()).toBe(201); const csrf=(await created.json()).csrfToken; const request=(method,url,body)=>context.request.fetch(url,{method,headers:{'X-CSRF-Token':csrf},data:body}); const nav=name=>page.getByRole('navigation').getByRole('button',{name,exact:true}).click(); await page.goto('/'); await identity(page,'Conductor'); for(const name of ['Projects','Visual Editor','JSON Editor','Preview','Actions & Bindings','Execution History','Publishing','Users']){ await nav(name);await identity(page,name+' · Conductor'); } for(const asset of ['/favicon.svg','/favicon.ico','/favicon-32.png','/apple-touch-icon.png'])expect((await context.request.get(asset)).status()).toBe(200); const project=await request('POST','/api/projects',{name:'Workshop Notes',description:'Browser identity acceptance'}); expect(project.status()).toBe(201); const projectId=(await project.json()).id; await nav('Publishing'); const form=page.locator('form').filter({has:page.getByLabel('Saved project')}); await form.getByLabel('Application name',{exact:true}).fill('Workshop Notes'); await form.getByLabel('Application address',{exact:true}).fill('workshop-notes'); await form.getByLabel(/^Access/).selectOption('public'); const png=new PNG({width:32,height:32}); for(let y=0;y<32;y++)for(let x=0;x<32;x++){const offset=(y*32+x)*4;const white=x>=10&&x<22&&y>=7&&y<25;png.data.set(white?[255,255,255,255]:[220,90,20,255],offset);} const fixture={name:'workshop.png',mimeType:'image/png',buffer:PNG.sync.write(png)}; await form.getByLabel('Application icon (optional)').setInputFiles(fixture); await expect(form.getByText('Custom application icon')).toBeVisible(); const customIcon=await form.locator('img').getAttribute('src'); expect(customIcon).toMatch(/^\/app-icons\/[a-f0-9]{64}\.png$/); await form.getByRole('button',{name:'Publish',exact:true}).click(); let card=page.getByRole('article').filter({has:page.getByRole('heading',{name:'Workshop Notes',exact:true})}); await expect(card).toBeVisible(); await expect(card.locator('img').first()).toHaveAttribute('src',customIcon); await page.screenshot({path:path.join(artifacts,'publishing-custom-icon.png'),fullPage:true}); await card.getByRole('link',{name:'Open app'}).click(); await identity(page,'Workshop Notes · Conductor',customIcon); await page.reload();await identity(page,'Workshop Notes · Conductor',customIcon); const anonymous=await browser.newContext({baseURL:base}); const publicPage=await anonymous.newPage(); publicPage.on('pageerror',error=>errors.push(error.message)); await publicPage.goto('/apps/workshop-notes'); await identity(publicPage,'Workshop Notes · Conductor',customIcon); await page.goto('/');await identity(page,'Conductor');await nav('Publishing'); card=page.getByRole('article').filter({has:page.getByRole('heading',{name:'Workshop Notes',exact:true})}); await card.locator('summary').click(); await card.getByRole('button',{name:'Use Conductor icon'}).click(); await expect(card.getByText('Conductor default icon')).toBeVisible(); await publicPage.reload();await identity(publicPage,'Workshop Notes · Conductor'); await card.getByLabel('Application icon (optional)').setInputFiles({name:'invalid.png',mimeType:'image/png',buffer:Buffer.from('not an image')}); await expect(card.getByRole('alert')).toBeVisible(); await expect(card.locator('img').first()).toHaveAttribute('src','/favicon.svg'); await card.getByLabel('Application icon (optional)').setInputFiles(fixture); await expect(card.getByText('Custom application icon')).toBeVisible(); await card.getByRole('button',{name:'Republish',exact:true}).click(); await expect(card.getByText('Public access · Version 2')).toBeVisible(); await publicPage.reload();await identity(publicPage,'Workshop Notes · Conductor',customIcon); const privateApp=await request('POST','/api/admin/published-apps',{sourceProjectId:projectId,displayName:'Private Notes',slug:'private-notes',visibility:'authenticated',iconPath:customIcon}); expect(privateApp.status()).toBe(201); await publicPage.goto('/apps/private-notes'); await identity(publicPage,'Sign in · Conductor'); await publicPage.getByLabel('Username',{exact:true}).fill('identity-admin'); await publicPage.getByLabel('Password',{exact:true}).fill('identity-test-password'); await publicPage.getByRole('button',{name:'Sign in',exact:true}).click(); await identity(publicPage,'Private Notes · Conductor',customIcon); // These fixtures test generic registry metadata matching; application hosting is SLICE11. const manifest={schemaVersion:1,id:'example-tool',name:'Example Tool',description:'Identity fixture',version:'1.0.0',launchPath:'/example-tool',audience:'all-users',iconPath:customIcon}; register(manifest); register({...manifest,id:'missing-icon',name:'Missing Icon',launchPath:'/missing-icon',iconPath:'/not-installed.png'}); await page.goto('/example-tool?view=settings');await identity(page,'Example Tool · Conductor',customIcon); await page.goto('/example-tool/reports');await identity(page,'Example Tool · Conductor',customIcon); await page.goto('/example-toolbox');await identity(page,'Conductor'); await page.goto('/missing-icon');await identity(page,'Missing Icon · Conductor'); await page.goto('/');await identity(page,'Conductor'); await page.getByRole('button',{name:'Sign out',exact:true}).click(); await identity(page,'Sign in · Conductor'); await page.goto('/example-tool');await identity(page,'Sign in · Conductor'); await page.getByLabel('Username',{exact:true}).fill('identity-admin'); await page.getByLabel('Password',{exact:true}).fill('identity-test-password'); await page.getByRole('button',{name:'Sign in',exact:true}).click(); await identity(page,'Example Tool · Conductor',customIcon); await page.goto('/');await nav('Publishing'); await page.setViewportSize({width:1000,height:900}); await page.screenshot({path:path.join(artifacts,'publishing-compact.png'),fullPage:true}); const iconReview=await context.newPage(); await iconReview.setContent('