renderer-startup-diagnostic.spec.ts
docs/evidence/renderer-startup-diagnostic.spec.ts
1import { test, expect } from '@playwright/test';
2const token=process.env.DW_EDITOR_TOKEN??'',headers={Authorization:`Bearer ${token}`};
3test.skip(!token,'Requires the isolated editor relay.');
4test('archived invitations stay shared while restoring an older world previews, saves and undoes a new draft',async({page,browser},testInfo)=>{
5 test.setTimeout(120000);
6 await page.addInitScript(()=>{const events:any[]=[];(window as any).__gpuTimings=events;for(const [prototype,name] of [[GPU.prototype,'requestAdapter'],[GPUAdapter.prototype,'requestDevice'],[GPUShaderModule.prototype,'getCompilationInfo'],[GPUDevice.prototype,'createRenderPipelineAsync'],[GPUQueue.prototype,'onSubmittedWorkDone']] as const){const original=(prototype as any)[name];(prototype as any)[name]=function(...args:any[]){const e={name,label:args[0]?.label??this.label??'',start:performance.now(),end:0};events.push(e);const result=original.apply(this,args);return result.finally(()=>e.end=performance.now());};}});
7
8 const errors:string[]=[];page.on('pageerror',e=>errors.push(e.message));const current=async()=>await(await page.request.get('/api/authoring',{headers})).json();
9 const publish=async()=>{const doc=await current(),response=await page.request.post('/api/authoring/publish',{headers,data:{baseRevision:doc.revision,baseHash:doc.sha256}});expect(response.ok()).toBe(true);return doc;};
10 const worlds=async()=>{await page.locator('#release-history').click();await expect(page.locator('#published-status')).toContainText('published worlds');};
11 const row=(id:string)=>page.locator(`.published-card[data-release="${id}"]`);
12 await page.setViewportSize({width:1440,height:1000});await page.goto(`/editor.html#key=${token}`);await expect(page.locator('#scene')).toHaveAttribute('data-ready','true',{timeout:30000});const first=await publish();
13 await page.locator('#search').fill('lantern-crate.plinth');await page.locator('#objects button').first().click();await page.locator('#rotation-1').fill('37');await expect(page.locator('#status')).toContainText('Preview updated');await page.locator('#save').click();await expect(page.locator('#status')).toContainText('Saved revision');const second=await publish();expect(second.sha256).not.toBe(first.sha256);
14 const contexts=[await browser.newContext(),await browser.newContext()],players=await Promise.all(contexts.map(c=>c.newPage()));
15 try{
16 for(const player of players){player.on('pageerror',e=>errors.push(e.message));await player.goto(`/?playtest=${second.sha256}`);await expect(player.locator('#enter')).toBeEnabled({timeout:45000});await player.locator('#enter').focus();await player.keyboard.press('Enter');}for(const player of players)await expect(player.locator('#save-status')).toContainText('2 travelers');
17 await page.bringToFront();await worlds();await row(first.sha256).locator('[data-archive]').click();await expect(row(first.sha256)).toHaveCount(0);await page.locator('#published-filter').selectOption('archived');await expect(row(first.sha256)).toBeVisible();await expect(row(first.sha256).locator('a')).toHaveAttribute('href',new URL(`/?playtest=${first.sha256}`,'http://localhost:5174').href);await page.screenshot({path:'test-results/editor-archived-worlds.png'});
18 await row(first.sha256).locator('[data-restore]').click();await expect(page.locator('#published-worlds')).toBeHidden();await expect(page.locator('#status')).toContainText('Preview updated');await expect(page.locator('#draft-note')).toContainText('new editable revision');expect(await current()).toEqual(second);await expect(page.locator('#save')).toBeEnabled();await page.locator('#discard').click();await expect(page.locator('#status')).toContainText('discarded');expect(await current()).toEqual(second);
19 await worlds();await page.locator('#published-filter').selectOption('archived');await row(first.sha256).locator('[data-restore]').click();await expect(page.locator('#status')).toContainText('Preview updated');await page.locator('#save').click();await expect(page.locator('#status')).toContainText('Saved revision');const restored=await current();expect(restored.world).toEqual(first.world);expect(BigInt(restored.revision)).toBeGreaterThan(BigInt(second.revision));expect(restored.sha256).not.toBe(first.sha256);expect(restored.prefabs).toEqual(first.prefabs);
20 await page.reload();await expect(page.locator('#scene')).toHaveAttribute('data-ready','true',{timeout:30000});await testInfo.attach('reload-gpu',{body:JSON.stringify(await page.evaluate(()=>({now:performance.now(),events:(window as any).__gpuTimings,resources:performance.getEntriesByType('resource').map((r:any)=>({name:r.name,duration:r.duration}))}))),contentType:'application/json'});await page.locator('#undo').click();await expect(page.locator('#status')).toContainText('Saved revision');expect((await current()).world).toEqual(second.world);await page.locator('#redo').click();await expect(page.locator('#status')).toContainText('Saved revision');expect((await current()).world).toEqual(first.world);
21 await worlds();await row(second.sha256).locator('[data-archive]').click();await expect(row(second.sha256)).toHaveCount(0);await page.locator('#published-filter').selectOption('archived');await expect(row(second.sha256)).toBeVisible();expect(await(await page.request.get(`/api/playtests/${second.sha256}/world`)).json()).toEqual(second);
22 for(const player of players){await expect(player.locator('#world')).toHaveAttribute('data-playtest',second.sha256);await expect(player.locator('#save-status')).toContainText('2 travelers');await expect(player.locator('#error-detail')).toHaveCount(0);}
23 await row(first.sha256).locator('[data-archive]').click();await expect(row(first.sha256)).toHaveCount(0);await page.locator('#published-filter').selectOption('active');await expect(row(first.sha256)).toBeVisible();await page.locator('#published-close').click();
24 // A restore preview cannot overwrite a newer edit accepted from another builder.
25 await worlds();await page.locator('#published-filter').selectOption('archived');await row(second.sha256).locator('[data-restore]').click();await expect(page.locator('#status')).toContainText('Preview updated');const base=await current(),entity=base.world.places[0].objects.find((o:any)=>!o.interaction&&!o.platform&&!o.patrol).entityId;
26 const foreign=await page.request.post('/api/authoring',{headers,data:{format:1,baseRevision:base.revision,baseHash:base.sha256,commands:[{kind:'set',entity,property:'material',value:{color:[.2,.7,.4]}}]}});expect(foreign.ok()).toBe(true);const saved=await current();await page.locator('#save').click();await expect(page.locator('#status')).toContainText('world');expect(await current()).toEqual(saved);await page.locator('#discard').click();await expect(page.locator('#status')).toContainText('discarded');await page.locator('#reload').click();await expect(page.locator('#status')).toContainText('Draft loaded');expect(await current()).toEqual(saved);expect(errors).toEqual([]);
27 }finally{for(const context of contexts)await context.close();}
28});
29