diff --git a/system/tests/oskiewar-practice.browser.mjs b/system/tests/oskiewar-practice.browser.mjs new file mode 100644 index 0000000000..2ecf416bab --- /dev/null +++ b/system/tests/oskiewar-practice.browser.mjs @@ -0,0 +1,38 @@ +// Full game renderer regression on Poorslice. Local changed assets over the +// deployed shell; synthetic appearance, no model call or account mutation. +import assert from 'node:assert/strict'; +import { readFile, mkdir } from 'node:fs/promises'; +import puppeteer from 'puppeteer'; +const dir = process.env.OSKIEWAR_SHOTS || '/tmp/oskiewar-practice-browser'; +await mkdir(dir, {recursive:true}); +const browser = await puppeteer.launch({headless:true, executablePath:process.env.CHROME_BIN || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'}); +const page = await browser.newPage(), errors = [], sockets = [], replays = []; +page.on('pageerror', e => errors.push(e.message)); +const cdp = await page.createCDPSession(); await cdp.send('Network.enable'); +cdp.on('Network.webSocketCreated', e => sockets.push(e.url)); +await page.setRequestInterception(true); +page.on('request', async request => { + const url = new URL(request.url()); + if (url.pathname.includes('oskiewar-replays') && request.method() === 'POST') replays.push(url.pathname); + let file; + if (url.origin === 'https://oskiewar.com') { + if (url.pathname === '/') file = 'mac-test.html'; + else if (['/oskiewar.js','/oskiewar-fighter.mjs','/oskiewar-wizard.mjs'].includes(url.pathname)) file = url.pathname.slice(1); + } + if (file) await request.respond({status:200,contentType:file.endsWith('html')?'text/html':'text/javascript',body:await readFile(new URL('../../xbox/live/'+file,import.meta.url))}); + else await request.continue(); +}); +try { + await page.setViewport({width:1200,height:900}); + await page.goto('https://oskiewar.com/?practice',{waitUntil:'domcontentloaded'}); + await page.waitForFunction(()=>globalThis.__oskiewarTouch?.screen,{timeout:60000}); + await page.evaluate(()=>{globalThis.__oskiewarFighterAppearance={handle:'@fixture',validUntil:Date.now()+600000,appearance:{skin:[217,164,126],hair:[54,42,34],shirt:[54,95,144],pants:[24,55,104],shoes:[235,235,235],hairStyle:'short',beard:false,glasses:false,sleeves:'long'}};}); + await page.keyboard.down('Enter'); await new Promise(r=>setTimeout(r,300)); await page.keyboard.up('Enter'); + await page.waitForFunction(()=>globalThis.__oskiewarTouch?.practiceFighter==='@fixture',{timeout:30000}); + await page.screenshot({path:dir+'/practice.png'}); + await page.evaluate(()=>{globalThis.__oskiewarFighterAppearance=null;}); + await page.waitForFunction(()=>!globalThis.__oskiewarTouch?.practiceFighter); + assert.deepEqual(errors,[]); assert.deepEqual(sockets,[]); assert.deepEqual(replays,[]); + console.log('PASS: actual game renders generated appearance, clears it on withdrawal, and opens no game sockets or replay uploads.'); +} catch(error) { await page.screenshot({path:dir+'/failure.png'}); throw error; } +finally { await browser.close(); } diff --git a/system/tests/oskiewar-turnstile-live.mjs b/system/tests/oskiewar-turnstile-live.mjs index 2d179082cb..8be052a146 100644 --- a/system/tests/oskiewar-turnstile-live.mjs +++ b/system/tests/oskiewar-turnstile-live.mjs @@ -71,11 +71,16 @@ try { assert.equal(await page.$eval('#wizard-go',el=>el.textContent),'Accept & use in practice',await page.$eval('#wizard-note',el=>el.textContent)); assert.equal(await page.evaluate(()=>!!globalThis.__oskiewarFighterAppearance),false,'review cannot equip before acceptance'); await page.screenshot({path:dir+'/02-generated.png'}); - await page.click('#wizard-go');await wait(()=>!!globalThis.__oskiewarFighterAppearance); + assert.equal(await page.$eval('#wizard-card canvas',el=>getComputedStyle(el).position),'static'); + await Promise.all([page.waitForNavigation({waitUntil:'domcontentloaded'}),page.click('#wizard-go')]); + await wait(()=>!!globalThis.__oskiewarFighterAppearance); + assert.ok(new URL(page.url()).searchParams.has('practice')); const account=await post({action:'account'});assert.equal(account.status,200);assert.equal(account.body.status,'accepted'); assert.equal(account.body.handle.toLowerCase(),handle.toLowerCase());acceptedHash=account.body.fighter.hash; await page.click('#wizard-back');await page.keyboard.press('Space'); await wait(()=>globalThis.__oskiewarTouch?.screen==='game'); + await wait(()=>!!globalThis.__oskiewarTouch?.practiceFighter); + assert.equal(await page.evaluate(()=>globalThis.__oskiewarTouch.practiceFighter.toLowerCase()),handle.toLowerCase()); await page.keyboard.down('ArrowRight');await new Promise(r=>setTimeout(r,1000));await page.keyboard.up('ArrowRight'); assert.ok(await page.evaluate(()=>Array.isArray(globalThis.__oskiewarFighterAppearance?.appearance.skin))); await page.screenshot({path:dir+'/03-local-practice.png'}); @@ -87,6 +92,7 @@ try { assert.equal((await post({action:'status',hash:source,capability:grant.slice(0,-4)+'AAAA'})).status,403,'tampered capability refused'); await page.click('button::-p-text(Withdraw my material)');await wait(()=>document.querySelector('#wizard-note').textContent.startsWith('Withdrawn.')); assert.equal(await page.evaluate(()=>!!globalThis.__oskiewarFighterAppearance),false); + await wait(()=>!globalThis.__oskiewarTouch?.practiceFighter); assert.equal((await post({action:'account'})).body.status,'empty'); assert.equal((await post({action:'status',hash:source,capability:grant})).status,403,'withdrawn grant refuses old preview'); await page.screenshot({path:dir+'/05-withdrawn.png'}); diff --git a/system/tests/oskiewar-turnstile.browser.mjs b/system/tests/oskiewar-turnstile.browser.mjs index 67cdd3050a..7a8b5c6987 100644 --- a/system/tests/oskiewar-turnstile.browser.mjs +++ b/system/tests/oskiewar-turnstile.browser.mjs @@ -13,7 +13,7 @@ let saved = false, denied = false, missingHandle = false, modelCalls = 0, accept const result = status => ({status,handle:'@fixture',fighter,validUntil:Date.now()+600000}); const server = createServer(async (req,res) => { const send = (body,status=200,type='application/json') => {res.writeHead(status,{'content-type':type});res.end(type==='application/json'?JSON.stringify(body):body);}; - if(req.url === '/') return send(``,200,'text/html'); @@ -47,6 +47,7 @@ try { const upload=await page.$('input[type=file]');await upload.uploadFile(new URL('./oskiewar-generation.test.mjs',import.meta.url).pathname); await page.click('#wizard-go');await wait(()=>document.querySelector('#wizard-go').textContent==='Accept & use in practice'); assert.equal(saved,false);assert.equal(await page.evaluate(()=>!!globalThis.__oskiewarFighterAppearance),false); + assert.equal(await page.$eval('#wizard-card canvas',el=>getComputedStyle(el).position),'static'); await page.screenshot({path:shotDir+'/01-review.png'}); await page.click('#wizard-go');await wait(()=>!!globalThis.__oskiewarFighterAppearance); assert.equal(acceptedCalls,1);assert.equal(saved,true);assert.match(await note(),/@fixture/); diff --git a/xbox/OSKIEWAR-RELEASE.md b/xbox/OSKIEWAR-RELEASE.md index f3969a005c..f0e7319ddf 100644 --- a/xbox/OSKIEWAR-RELEASE.md +++ b/xbox/OSKIEWAR-RELEASE.md @@ -9,13 +9,16 @@ yourself restores it without another model call. Handle changes retain ownership Withdrawal removes the account's saved fighter and source material. While equipped, authority is checked every 15 seconds; a failed check clears the selection, and its current expiry is also enforced at render time. Presentation is -limited to local practice; online play uses the standard fighter. The REGARDE +limited to local practice: acceptance opens `/?practice`, restores the accepted +fighter against its live grant, and disables game broadcasts and replay uploads. +Online play uses the standard fighter. The REGARDE gate still signs with its documented demo key. Run browser checks on Poorslice under Node 24: ```sh node system/tests/oskiewar-turnstile.browser.mjs +node system/tests/oskiewar-practice.browser.mjs ``` That browser regression uses synthetic API fixtures. A live test must also diff --git a/xbox/live/mac-test.html b/xbox/live/mac-test.html index 9e8f1e3001..c4e0db9e0b 100644 --- a/xbox/live/mac-test.html +++ b/xbox/live/mac-test.html @@ -41,8 +41,9 @@ // self-play casts all want the fronts they were built against. { const harness = new URLSearchParams(location.search); + globalThis.__oskiewarLocalPractice = harness.has("practice"); globalThis.__oskiewarVersusCapable = !harness.has("social-preview") && - !harness.has("replay-oven") && !harness.has("self-play") && !harness.has("workshop") && !harness.has("map"); + !harness.has("practice") && !harness.has("replay-oven") && !harness.has("self-play") && !harness.has("workshop") && !harness.has("map"); }