From 631c2bc73af069616713358c3ddd581708305237 Mon Sep 17 00:00:00 2001 From: archipelago Date: Tue, 6 Oct 2026 02:52:33 -0400 Subject: [PATCH] test: retain repeatable real V4V media bridge qualification --- docs/container-store-ownership-followup.md | 15 +++++++- tests/lifecycle/v4v-media-bridge.cjs | 44 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 tests/lifecycle/v4v-media-bridge.cjs diff --git a/docs/container-store-ownership-followup.md b/docs/container-store-ownership-followup.md index 283c1c3a..97d17c81 100644 --- a/docs/container-store-ownership-followup.md +++ b/docs/container-store-ownership-followup.md @@ -44,11 +44,22 @@ only the backend's storage- and owner-scoped cleanup remains. The fixture then survived a complete scheduled doctor run. Candidate deployment exposed a separate packaging/startup problem: an older -runtime script remained inside the frontend payload, which startup promotes into -`/opt`. The embedded repair then failed with EROFS under `ProtectSystem=strict`. +runtime payload remained on disk from a previous OTA; the qualification UI archive +did not replace it. Startup promoted those retained scripts back into `/opt`. The embedded repair then failed with EROFS under `ProtectSystem=strict`. The dev box's safe helper was restored; Yaya rollout is held until verification. The repair now uses the established host command mechanism, checks executable permissions, and runs synchronously after runtime promotion before reconciliation. Regression tests cover stale content, missing execute permission, idempotence and installation failure. Actual sandboxed service restart remains an acceptance gate; unit tests alone do not prove escape from the production mount namespace. + +Repeatable fixture-only browser check: `tests/lifecycle/v4v-media-bridge.cjs`. +Set `ARCHY_TEST_CDP` to an authorized Chromium debugging endpoint and +`V4V_TEST_PASSWORD_FILE` to the private fixture password file. Optional +`V4V_TEST_ORIGIN` and `ARCHY_TEST_PARENT_ORIGIN` select same-host origins reachable +by that browser (default loopback ports 32908 and 80). Install frontend test +dependencies first. The runner creates and closes only its own browser contexts; +it never closes the kiosk browser. It plays bundled demo audio muted, checks real +media time advances while hidden, pauses/resumes, and preserves iframe identity. +Mobile 390px and desktop 1440px pass on the final isolated image. This harness +check does not substitute for the actual dashboard player/catalog deployment. diff --git a/tests/lifecycle/v4v-media-bridge.cjs b/tests/lifecycle/v4v-media-bridge.cjs new file mode 100644 index 00000000..6494da4d --- /dev/null +++ b/tests/lifecycle/v4v-media-bridge.cjs @@ -0,0 +1,44 @@ +// Disposable V4V fixture only: never point this test at a paid production catalog. +// See docs/container-store-ownership-followup.md for fixture isolation requirements. +const fs = require('node:fs'); +const { chromium, expect } = require('../../neode-ui/node_modules/@playwright/test'); +const cdp = process.env.ARCHY_TEST_CDP; +const passwordFile = process.env.V4V_TEST_PASSWORD_FILE; +const fixtureOrigin = new URL(process.env.V4V_TEST_ORIGIN || 'http://127.0.0.1:32908').origin; +const parentOrigin = new URL(process.env.ARCHY_TEST_PARENT_ORIGIN || 'http://127.0.0.1').origin; +if (!cdp || !passwordFile) throw Error('ARCHY_TEST_CDP and V4V_TEST_PASSWORD_FILE are required'); +if (new URL(parentOrigin).hostname !== new URL(fixtureOrigin).hostname) throw Error('Fixture and parent must use the same hostname'); +const fixturePassword = fs.readFileSync(passwordFile, 'utf8').trim(); +(async()=>{const browser=await chromium.connectOverCDP(cdp);const contexts=[]; +try{for(const width of [390,1440]){ + const context=await browser.newContext({viewport:{width,height:900},serviceWorkers:'block'});contexts.push(context); context.setDefaultTimeout(15000); context.setDefaultNavigationTimeout(30000); console.log("stage",width,"context"); + await context.addInitScript(()=>{window.__qualificationMedia=[];const play=HTMLMediaElement.prototype.play;HTMLMediaElement.prototype.play=function(...args){this.muted=true;if(!window.__qualificationMedia.includes(this))window.__qualificationMedia.push(this);return play.apply(this,args)}}); + const response=await context.request.post(fixtureOrigin + '/api/alpha/login',{data:{password:fixturePassword},timeout:30000});if(!response.ok())throw Error('Fixture login failed HTTP '+response.status()); + const harness=`