ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
33 lines
6.2 KiB
JavaScript
33 lines
6.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import {createRequire} from 'node:module';
|
|
import {mkdir,writeFile} from 'node:fs/promises';
|
|
import {resolve} from 'node:path';
|
|
import {pathToFileURL} from 'node:url';
|
|
const require=createRequire(process.env.PLAYWRIGHT_REQUIRE_FROM??import.meta.url);
|
|
const {chromium}=require('playwright');
|
|
const {createServer}=await import(pathToFileURL(resolve('web/node_modules/vite/dist/node/index.js')));
|
|
const output=resolve('.tmp-tos-recordings/evidence');await mkdir(output,{recursive:true});
|
|
const server=await createServer({configFile:false,root:resolve('.tmp-tos-recordings/candidate/web'),oxc:{jsx:{runtime:'automatic'}},logLevel:'error',server:{host:'127.0.0.1',port:5325,strictPort:true},plugins:[{
|
|
name:'recording-test',configureServer(server){server.middlewares.use('/__test',(req,res)=>{res.setHeader('Content-Type','text/html');res.end('<!doctype html><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><div id="root"></div><script type="module" src="/@id/__recording-test.tsx"></script>');});},resolveId(id){if(id==='__recording-test.tsx')return '\0__recording-test.tsx';},load(id){if(id==='\0__recording-test.tsx')return `
|
|
import React from 'react';import {createRoot} from 'react-dom/client';import {CallRecordings} from '/src/components/CallRecordings.tsx';import '/src/components/home/customer-portal.css';
|
|
function App(){const [sid,set]=React.useState('call1');return React.createElement('main',{className:'companion-portal',style:{maxWidth:700,margin:'30px auto',padding:20}},React.createElement('button',{onClick:()=>set('call2')},'切换通话'),React.createElement(CallRecordings,{key:sid,sessionID:sid,elderID:'elder',accessToken:'test-token'}))};createRoot(document.getElementById('root')).render(React.createElement(App));`;}}]});
|
|
await server.listen();const browser=await chromium.launch({headless:true,executablePath:process.env.CHROME_PATH??'C:/Program Files/Google/Chrome/Application/chrome.exe'});const page=await browser.newPage({viewport:{width:1100,height:850}});const problems=[],checks=[];page.on('pageerror',e=>problems.push(e.message));
|
|
let canPlay=false,canDownload=false,slow=false,denied=false,playbackReady=true;const requests=[];
|
|
await page.route('**/api/v1/**',async route=>{const path=new URL(route.request().url()).pathname,body=route.request().postDataJSON();requests.push({path,body});
|
|
if(path.endsWith('recording-access-grants')){if(body.permission==='PLAY')canPlay=true;else canDownload=true;return route.fulfill({json:{data:{}}});}
|
|
if(path.endsWith('calls/recordings'))return route.fulfill({json:{data:{items:body.business_session_id==='call1'?[{id:'user',speaker:'USER',duration_seconds:58,bytes:1045859},{id:'bot',speaker:'ASSISTANT',duration_seconds:103,bytes:1845555}]:[],can_play:canPlay,can_download:canDownload,can_manage:true,consent:true,requester_id:'self',state:'READY',playback_ready:playbackReady}}});
|
|
if(path.endsWith('calls/recording-access')){if(slow)await new Promise(r=>setTimeout(r,450));if(denied)return route.fulfill({status:403,json:{code:'RECORDING_ACCESS_DENIED',message:'denied'}});return route.fulfill({json:{data:{access_url:'https://memory927.tos-cn-beijing.volces.com/test.mp4?signature=synthetic',access_until:new Date(Date.now()+300000).toISOString()}}});}
|
|
return route.fulfill({status:404,json:{}});});
|
|
// Synthetic metadata only. No actual customer audio is loaded in this test.
|
|
await page.route('https://memory927.tos-cn-beijing.volces.com/**',route=>route.abort());
|
|
try{
|
|
await page.goto('http://127.0.0.1:5325/__test');await page.getByText('长者',{exact:true}).waitFor();assert(await page.getByRole('button',{name:'播放录音'}).first().isDisabled());checks.push('separate speakers and authorization required');
|
|
await page.getByRole('button',{name:'授权本账号播放 24 小时'}).click();await page.getByRole('button',{name:'播放录音'}).first().waitFor();await page.getByRole('button',{name:'播放录音'}).first().click();await page.locator('audio').waitFor();assert.match(await page.locator('audio').getAttribute('src'),/^https:\/\/memory927\.tos-cn-beijing\.volces\.com\//);assert.equal(await page.locator('audio').getAttribute('preload'),'none');checks.push('player uses direct TOS URL');
|
|
await page.getByRole('button',{name:'授权本账号下载 24 小时'}).click();await page.getByRole('button',{name:'获取下载链接'}).first().click();await page.getByRole('link',{name:'从火山下载录音'}).waitFor();assert.match(await page.getByRole('link',{name:'从火山下载录音'}).getAttribute('href'),/^https:\/\/memory927\.tos-cn-beijing\.volces\.com\//);checks.push('download is a direct link, not a platform blob');
|
|
await page.screenshot({path:resolve(output,'recordings-desktop.png'),fullPage:true});await page.setViewportSize({width:390,height:844});assert(await page.evaluate(()=>document.documentElement.scrollWidth-document.documentElement.clientWidth)<3);await page.screenshot({path:resolve(output,'recordings-mobile.png'),fullPage:true});checks.push('mobile layout');
|
|
denied=true;await page.getByRole('button',{name:'播放录音'}).first().click();await page.getByRole('alert').waitFor();assert.equal(await page.locator('audio').count(),0);assert.equal(await page.getByRole('link',{name:'从火山下载录音'}).count(),0);checks.push('revoked access clears prior addresses');
|
|
denied=false;slow=true;await page.getByRole('button',{name:'播放录音'}).first().click();await page.getByRole('button',{name:'切换通话'}).click();await page.getByText(/暂未找到录音/).waitFor();await page.waitForTimeout(650);assert.equal(await page.locator('audio').count(),0);checks.push('late URL cannot leak into another call');
|
|
playbackReady=false;await page.reload();await page.getByText(/在线播放尚未开通/).waitFor();assert.equal(await page.getByRole('button',{name:'播放录音'}).count(),0);assert.equal(await page.getByRole('button',{name:'获取下载链接'}).count(),2);checks.push('default TOS domain exposes download without a broken play button');
|
|
assert.deepEqual(problems,[]);await writeFile(resolve(output,'result.json'),JSON.stringify({checks,problems},null,2));console.log(JSON.stringify({checks,problems}));
|
|
}catch(e){console.log(JSON.stringify({problems,requests,body:await page.locator('body').innerText()}));throw e;}finally{await browser.close();await server.close();}
|