← back to Wallpaper Paint Antics
series2/delivery-proof.mjs
59 lines
import fs from 'node:fs';
import path from 'node:path';
import assert from 'node:assert/strict';
import crypto from 'node:crypto';
import {execFileSync} from 'node:child_process';
import {createRequire} from 'node:module';
const require=createRequire(import.meta.url),root=import.meta.dirname;
const {chromium}=require('/Users/macstudio3/Projects/Designer-Wallcoverings/node_modules/playwright');
const manifest=JSON.parse(fs.readFileSync(path.join(root,'memes/manifest.json')));
const evidence={at:new Date().toISOString(),memes:[],gallery:{},audio:[]};
const browser=await chromium.launch({executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',headless:true});
try{
const page=await browser.newPage({viewport:{width:1440,height:1200}}),errors=[];
page.on('pageerror',e=>errors.push(e.message));
await page.goto('file://'+path.join(root,'memes/index.html'));
await page.evaluate(async()=>{for(const i of document.images){i.loading='eager';await i.decode();}});
assert.equal(await page.locator('figure').count(),10);
evidence.gallery.offlineImages='10/10 decoded';
const {port}=JSON.parse(fs.readFileSync(path.join(root,'verification/server.json')));
await page.goto('http://127.0.0.1:'+port+'/memes/index.html');
await page.evaluate(async()=>{for(const i of document.images){i.loading='eager';await i.decode();}});
for(const m of manifest){
const data=fs.readFileSync(path.join(root,'memes',m.file));
assert.equal(crypto.createHash('sha256').update(data).digest('hex'),m.sha256);assert(m.width>=1024);assert.equal(m.width,m.height);
const [download]=await Promise.all([page.waitForEvent('download'),page.locator('[id="'+m.id+'"] a[download]').click()]);
assert.equal(download.suggestedFilename(),m.file);assert.equal(await download.failure(),null);
evidence.memes.push({id:m.id,width:m.width,height:m.height,download:'PASS',sha256:m.sha256});
console.log(m.id+' download PASS');
}
// The all-in-one ZIP is a separate user journey from ten rapid individual downloads.
const zipPage=await browser.newPage();await zipPage.goto('http://127.0.0.1:'+port+'/memes/index.html');
const [zip]=await Promise.all([zipPage.waitForEvent('download'),zipPage.locator('a.download').click()]);assert.equal(await zip.failure(),null);
await zipPage.close();console.log('ZIP download PASS');
const entries=execFileSync('unzip',['-Z1',path.join(root,'memes/installer-follies-10-memes.zip')],{encoding:'utf8'}).trim().split('\n');
assert.deepEqual(entries.filter(x=>x.endsWith('.png')).sort(),manifest.map(m=>m.file).sort());
await page.screenshot({path:path.join(root,'verification/memes-gallery.png'),fullPage:true});
await page.setViewportSize({width:390,height:844});await page.screenshot({path:path.join(root,'verification/memes-mobile.png')});
assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));
await page.locator('nav a').first().click();await page.waitForSelector('#presentation');
await page.evaluate(async()=>{const v=document.querySelector('#presentation');v.muted=true;await v.play();});
await page.waitForFunction(()=>document.querySelector('#presentation').currentTime>.4);
for(const t of [0,20,40,60,80]){
await page.locator('[data-jump="'+t+'"]').click();
await page.waitForFunction(t=>{const v=document.querySelector('#presentation');return v.currentTime>=t&&v.currentTime<t+3;},t);
}
await page.evaluate(()=>document.querySelector('#presentation').pause());
assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));
await page.screenshot({path:path.join(root,'verification/presentation-mobile.png'),fullPage:true});
await page.locator('a[href="memes/index.html"]').first().click();await page.waitForSelector('figure');
assert.deepEqual(errors,[]);evidence.gallery={...evidence.gallery,verdict:'PASS',downloadTransport:'localhost HTTP',individualDownloads:10,zipPngs:10,mobileWidth:390,chapterJumps:5,roundTripNavigation:true,errors};
for(const ep of JSON.parse(fs.readFileSync(path.join(root,'episodes.json')))){
const audio=execFileSync('ffmpeg',['-v','error','-i',path.join(root,'renders',ep.slug+'.mp4'),'-vn','-f','f32le','-ac','1','pipe:1'],{maxBuffer:10e6});
let peak=0,sum=0;for(let i=0;i<audio.length;i+=4){const x=audio.readFloatLE(i);peak=Math.max(peak,Math.abs(x));sum+=x*x;}
const rms=Math.sqrt(sum/(audio.length/4));assert(peak>.05&&peak<1);assert(rms>.005);
evidence.audio.push({slug:ep.slug,peak,rms,verdict:'PASS'});
}
evidence.verdict='PASS';console.log('Delivery PASS: 10 memes, all downloads, ZIP, mobile galleries, five chapter jumps and five audio tracks.');
}finally{await browser.close();fs.writeFileSync(path.join(root,'verification/delivery-proof.json'),JSON.stringify(evidence,null,2));}