// Test an installed Conductor build with its real bridge and a read-only scheduler fixture. import {chromium,expect} from '@playwright/test'; import {spawn,spawnSync} from 'node:child_process'; import fs from 'node:fs/promises'; import path from 'node:path'; import http from 'node:http'; import net from 'node:net'; import {tmpdir} from 'node:os'; const build=path.resolve(process.argv[2]||'.local/catalog-build-verification/.local/conductor-build'); const data=await fs.mkdtemp(path.join(tmpdir(),'scheduler-catalog-')); const artifact=path.resolve('test-results/slice10-social-integration');await fs.mkdir(artifact,{recursive:true}); const fixture=http.createServer((req,res)=>{ let value; if(req.method==='GET'&&req.url==='/social-scheduler/session')value={displayName:'Customer administrator',role:req.headers['x-conductor-role'],workspaces:[{id:'customer',name:'Customer workspace'}],preview:false,publishingEnabled:false}; else if(req.method==='GET'&&req.url==='/social-scheduler/workspaces/customer')value={accounts:[],posts:[],publishingEnabled:false}; else {res.writeHead(404);res.end();return;} res.writeHead(200,{'content-type':'application/json'});res.end(JSON.stringify(value)); }); await new Promise(resolve=>fixture.listen(0,'127.0.0.1',resolve)); const port=await new Promise(resolve=>{const probe=net.createServer();probe.listen(0,'127.0.0.1',()=>{const port=probe.address().port;probe.close(()=>resolve(port));});}); const base='http://127.0.0.1:'+port; const env={...process.env,PORT:String(port),CONDUCTOR_DATA_DIR:data,CONDUCTOR_STATIC_DIR:path.join(build,'frontend/build'),CONDUCTOR_SECRET_KEY:Buffer.alloc(32,21).toString('base64'),SOCIAL_API_ORIGIN:'http://127.0.0.1:'+fixture.address().port+'/social-scheduler',SOCIAL_INTERNAL_TOKEN:'local-test-fixture',SOCIAL_WORKSPACE_ID:'customer'}; const server=spawn(process.execPath,['backend/dist/index.js'],{cwd:build,env,stdio:['ignore','pipe','pipe']}); let output='';server.stdout.on('data',c=>{output+=c;});server.stderr.on('data',c=>{output+=c;}); let browser,page; try { for(let n=0;n<100;n++){ if(server.exitCode!==null)throw new Error(output); if(await fetch(base+'/api/health').then(r=>r.ok).catch(()=>false))break; if(n===99)throw new Error('Conductor did not start.'); await new Promise(resolve=>setTimeout(resolve,50)); } browser=await chromium.launch({headless:true}); const context=await browser.newContext({baseURL:base,viewport:{width:1360,height:980}}); const setup=await context.request.get('/api/auth/setup'); const response=await context.request.post('/api/auth/setup',{headers:{'X-Setup-Token':(await setup.json()).setupToken},data:{username:'catalog-test-admin',password:'catalog-test-password',displayName:'Customer Administrator'}}); expect(response.status()).toBe(201); page=await context.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message)); await page.goto('/'); await expect(page).toHaveTitle('Conductor'); await page.getByRole('navigation').getByRole('button',{name:'Publishing',exact:true}).click(); await expect(page).toHaveTitle('Publishing · Conductor'); await expect(page.getByText('No included applications are installed yet.',{exact:false})).toBeVisible(); await expect(page.getByRole('link',{name:'Open Social Scheduler',exact:true})).toHaveCount(0); const manifest=await fs.readFile(path.join(path.resolve(process.argv[3]||'../social-scheduler'),'integration/included-app.json'),'utf8'); for(let n=0;n<2;n++){ const result=spawnSync(process.execPath,['backend/dist/scripts/includedApps.js','register'],{cwd:build,env,input:manifest,encoding:'utf8'}); expect(result.status,result.stderr).toBe(0); } await page.getByRole('button',{name:'Refresh applications',exact:true}).click(); await expect(page.getByRole('heading',{name:'Social Scheduler',exact:true})).toHaveCount(1); await expect(page.getByRole('link',{name:'Open Social Scheduler',exact:true})).toHaveAttribute('href','/social-scheduler'); await page.screenshot({path:path.join(artifact,'publishing-social-scheduler.png'),fullPage:true}); await page.getByRole('link',{name:'Configure',exact:true}).click(); await expect(page).toHaveURL(base+'/social-scheduler?view=accounts'); await expect(page.getByRole('heading',{name:'Connect social media'})).toBeVisible(); await expect(page.getByLabel('Bluesky handle')).toBeVisible(); await expect(page).toHaveTitle('Social Scheduler · Conductor'); await expect(page.locator('#conductor-favicon')).toHaveAttribute('href','/favicon.svg'); await page.screenshot({path:path.join(artifact,'configure-social-scheduler.png'),fullPage:true}); await page.goto('/'); await page.getByRole('link',{name:'Open Social Scheduler',exact:true}).click(); await expect(page.getByRole('heading',{name:'Compose a post'})).toBeVisible(); await expect(page).toHaveTitle('Social Scheduler · Conductor'); await expect(page.locator('#conductor-favicon')).toHaveAttribute('href','/favicon.svg'); await expect(page.getByRole('button',{name:'Queue post now'})).toBeDisabled(); await page.goto('/');await expect(page).toHaveTitle('Conductor'); expect(errors).toEqual([]); console.log('PASS: installed Conductor build, empty-before-registration, real manifest upsert, Publishing card, Configure/Open through authenticated bridge, inherited app title/default icon, reset on return home.'); } catch(error){ if(page)await page.screenshot({path:path.join(artifact,'failure.png'),fullPage:true}).catch(()=>{}); throw error; } finally { await browser?.close(); if(server.exitCode===null)await new Promise(resolve=>{server.once('exit',resolve);server.kill('SIGTERM');}); fixture.closeAllConnections();await new Promise(resolve=>fixture.close(resolve)); await fs.rm(data,{recursive:true,force:true}); }