Files
story/test/e2e/cloud-recordings.mjs
T

33 lines
6.2 KiB
JavaScript
Raw Normal View History

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();}