Files

64 lines
9.4 KiB
JavaScript
Raw Permalink 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-portal-records-fix/evidence');await mkdir(output,{recursive:true});
const server=await createServer({configFile:false,root:resolve('web'),oxc:{jsx:{runtime:'automatic'}},logLevel:'error',server:{host:'127.0.0.1',port:5322,strictPort:true},plugins:[{
name:'portal-records-test',configureServer(server){server.middlewares.use('/__records-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/__records-module.tsx"></script>');});},resolveId(id){if(id==='__records-module.tsx')return '\0__records-module.tsx';},load(id){if(id==='\0__records-module.tsx')return `
import React from 'react';import {createRoot} from 'react-dom/client';
import {CallHistory} from '/src/pages/CallHistory.tsx';import {MemoryWorkspace} from '/src/pages/MemoryWorkspace.tsx';
import {installPortalFixtures} from '/src/dev/portal-fixtures.ts';import {AuthProvider} from '/src/auth/useAuth.ts';import {CustomerPortal} from '/src/pages/CustomerPortal.tsx';
import '/src/styles.css';import '/src/cal-theme.css';import '/src/customer-responsive.css';import '/src/components/home/customer-portal.css';
const params=new URL(location.href).searchParams,mode=params.get('mode');
if(mode==='portal') {
const nativeFetch=window.fetch.bind(window);installPortalFixtures();const previous=window.fetch.bind(window);let desired=60,actual=60,revision=1,reported=1;
window.applyReported=()=>{actual=desired;reported=revision;};
window.fetch=async(input,init)=>{const path=new URL(typeof input==='string'?input:input.url,location.origin).pathname;
if(path.includes('/memories/calls/'))return nativeFetch(input,init);
if(path.endsWith('/twin')) { if(init?.method==='PATCH'){desired=JSON.parse(init.body).desired_config.volume.speaker;revision++;}
return new Response(JSON.stringify({data:{desired_config:{volume:{speaker:desired},power:{mode:'active'}},reported_config:{volume:{speaker:actual},power:{mode:'active'}},desired_revision:revision,reported_revision:reported,sync_state:revision===reported?'applied':'pending'}}),{headers:{'Content-Type':'application/json'}}); }
return previous(input,init);
};
createRoot(document.getElementById('root')).render(React.createElement(AuthProvider,null,React.createElement(CustomerPortal)));
} else {function App(){const [elder,setElder]=React.useState('elder-a');return React.createElement('div',{className:'companion-portal'},React.createElement('button',{onClick:()=>setElder('elder-b')},'切换长者'),mode==='memory'?React.createElement(MemoryWorkspace,{elderID:elder,accessToken:'synthetic',section:params.get('section')??undefined}):React.createElement(CallHistory,{key:elder,elderID:elder,accessToken:'synthetic'}))};createRoot(document.getElementById('root')).render(React.createElement(App));}`;}
}]});
await server.listen();const browser=await chromium.launch({headless:true,executablePath:'C:/Program Files/Google/Chrome/Application/chrome.exe'});
const checks=[],problems=[];
try {
const page=await browser.newPage({viewport:{width:1440,height:1000}});page.setDefaultTimeout(12000);page.on('pageerror',e=>problems.push(e.message));
let denied=false,slow=false;const requests=[];
const record=(id)=>({id,created_at:'2026-09-26T01:00:00Z',ended_at:'2026-09-26T01:02:00Z',status:'ENDED',transcript_state:'COMPLETE',duration_ms:120000});
await page.route('**/api/v1/memories/**',async route=>{const body=route.request().postDataJSON(),op=new URL(route.request().url()).pathname.split('/memories/')[1];requests.push({op,body});let data={};
if(denied) return route.fulfill({status:403,json:{error:{code:'TRANSCRIPT_ACCESS_DENIED',message:'denied'}}});
if(op==='calls/query') {if(body.elder_id==='elder-b')data={items:[],next_cursor:''};else data={items:body.cursor?[record('second')]:[record('first')],next_cursor:body.cursor?'':'page2'};}
if(op==='calls/turns'){if(slow)await new Promise(r=>setTimeout(r,500));data={items:[{id:body.cursor?'u2':'u1',speaker:body.cursor?'ASSISTANT':'USER',final_text:body.cursor?'今天阳光很好,我们一起聊聊天。':'今天我去公园散步了。',created_at:'2026-09-26T01:00:00Z',interrupted:false}],next_cursor:body.cursor?'':'turn2'};}
if(op==='query')data={items:[{memory_id:'event',memory_type:'EVENT',content:'今天去公园散步',proactive_policy:'ALLOWED',source_refs:[],updated_at:new Date().toISOString()},{memory_id:'preference',memory_type:'PREFERENCE',content:'喜欢喝淡茶',proactive_policy:'RESPOND_ONLY',source_refs:[],updated_at:new Date().toISOString()}],mutations:[],next_cursor:'',can_write:true};
if(op==='sync/status')data={pending_count:0};
await route.fulfill({json:{data}});
});
await page.goto('http://127.0.0.1:5322/__records-test');
await page.locator('.cp-memory-row').first().click();await page.getByText('今天我去公园散步了。',{exact:true}).waitFor();
await page.getByRole('button',{name:'加载更多对话'}).click();await page.getByText('今天阳光很好,我们一起聊聊天。',{exact:true}).waitFor();
await page.getByRole('button',{name:'加载更多聊天'}).click();await page.locator('.cp-memory-row').nth(1).waitFor();assert.equal(await page.locator('.cp-memory-row').count(),2);
assert.equal(requests.find(r=>r.op==='calls/turns').body.business_session_id,'first');checks.push('new call list and transcript pagination');
await page.screenshot({path:resolve(output,'calls-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,'calls-mobile.png'),fullPage:true});checks.push('mobile call layout');
slow=true;await page.locator('.cp-memory-row').last().click();await page.getByRole('button',{name:'切换长者'}).click();await page.getByRole('heading',{name:'还没有聊天记录'}).waitFor();await page.waitForTimeout(650);assert.equal(await page.getByText('今天我去公园散步了。',{exact:true}).count(),0);checks.push('late responses do not cross elders');
denied=true;await page.getByRole('button',{name:'刷新记录'}).click();await page.getByRole('alert').filter({hasText:'没有查看聊天文字的权限'}).waitFor();checks.push('permission denial is not shown as an empty history');denied=false;
for(const section of ['daily','memory']) {await page.goto('http://127.0.0.1:5322/__records-test?mode=memory&section='+section);await page.locator('.cp-memory-row').waitFor();assert.equal(await page.locator('.cp-memory-row').count(),1);assert.match(await page.locator('.cp-memory-row').innerText(),section==='daily'?/公园/:/淡茶/);}
checks.push('daily and memory separate actual record types');
await page.setViewportSize({width:1440,height:1000});await page.goto('http://127.0.0.1:5322/__records-test?mode=portal&section=devices');await page.getByRole('heading',{name:'设备已连接'}).waitFor();
assert.equal(await page.locator('.cp-connected').evaluate(e=>getComputedStyle(e).color),'rgb(33, 131, 75)');
await page.getByRole('button',{name:'查看设备详情',exact:true}).click();await page.getByRole('slider').fill('56');await page.getByRole('button',{name:'保存音量配置',exact:true}).click();
const form=page.getByRole('form',{name:'家庭业务受控操作'});await form.getByRole('checkbox').check();await form.locator('button[type=submit]').click();
await page.getByText('音量设置已保存,正在等待设备确认',{exact:true}).first().waitFor();await page.evaluate(()=>window.applyReported());await page.getByText('音量已生效,设备实际音量 56%',{exact:true}).first().waitFor();
assert.equal(await page.getByText('音量设置已保存,正在等待设备确认',{exact:true}).count(),0);checks.push('saved volume becomes confirmed after reported revision; connected check is green');await page.screenshot({path:resolve(output,'volume-confirmed.png'),fullPage:true});
await page.goto('http://127.0.0.1:5322/__records-test?mode=portal&section=conversations');await page.locator('[aria-label="设备聊天记录"] .cp-memory-row').first().click();await page.getByText('今天我去公园散步了。',{exact:true}).waitFor();await page.screenshot({path:resolve(output,'portal-calls.png'),fullPage:true});
await page.goto('http://127.0.0.1:5322/__records-test?mode=portal&section=stories');await page.locator('.cp-tabs').getByRole('button',{name:'日常',exact:true}).waitFor();await page.locator('.cp-tabs').getByRole('button',{name:'故事',exact:true}).click();await page.getByText('喜欢在阳台照顾花草。看到花开会觉得心情很好。',{exact:true}).first().waitFor();await page.screenshot({path:resolve(output,'portal-stories.png'),fullPage:true});checks.push('full portal shows current records and daily/story tabs');
assert.deepEqual(problems,[]);await writeFile(resolve(output,'result.json'),JSON.stringify({checks,problems},null,2));console.log(JSON.stringify({checks,problems}));
}finally{await browser.close();await server.close();}