| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152 |
- // Isolated browser fixtures: no production reads/writes or provider calls.
- import {chromium} from 'playwright';
- import {createServer} from 'node:http';
- import {readFileSync,existsSync,mkdirSync,writeFileSync} from 'node:fs';
- import {resolve,extname} from 'node:path';
- import assert from 'node:assert/strict';
- const root=resolve('dist/xiaoshu-mobile/browser'),out=resolve('tmp/word-audio-qa');mkdirSync(out,{recursive:true});
- const wav=Buffer.alloc(44+3200);wav.write('RIFF');wav.writeUInt32LE(wav.length-8,4);wav.write('WAVEfmt ',8);wav.writeUInt32LE(16,16);wav.writeUInt16LE(1,20);wav.writeUInt16LE(1,22);wav.writeUInt32LE(16000,24);wav.writeUInt32LE(32000,28);wav.writeUInt16LE(2,32);wav.writeUInt16LE(16,34);wav.write('data',36);wav.writeUInt32LE(3200,40);
- const server=createServer((req,res)=>{
- let file=resolve(root,'.'+decodeURIComponent(new URL(req.url,'http://local').pathname));
- if(!file.startsWith(root+'/')||!existsSync(file)||!extname(file))file=root+'/index.html';
- res.setHeader('Content-Type',({'.html':'text/html','.js':'text/javascript','.css':'text/css','.svg':'image/svg+xml'})[extname(file)]||'application/octet-stream');res.end(readFileSync(file));
- });
- await new Promise(done=>server.listen(0,'127.0.0.1',done));const origin='http://127.0.0.1:'+server.address().port;
- const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
- const saved=new Map([['101',origin+'/fixtures/existing.wav'],['103',origin+'/fixtures/broken.wav']]),calls=[],errors=[];let fail=false;
- try{
- const context=await browser.newContext({viewport:{width:375,height:812},reducedMotion:'reduce'});
- await context.addInitScript(()=>localStorage.setItem('xiaoshu.angular.session',JSON.stringify({token:'r:word-audio-fixture',user:{userId:100,groupId:1,honeyName:'测试学生'}})));
- await context.route('**/*',async route=>{
- const url=new URL(route.request().url());if(url.origin!==origin)return route.abort();
- if(url.pathname==='/fixtures/broken.wav')return route.fulfill({status:404,body:''});
- if(url.pathname.startsWith('/fixtures/'))return route.fulfill({contentType:'audio/wav',body:wav});
- if(!url.pathname.startsWith('/cloud-functions/'))return route.continue();
- const {params:p}=route.request().postDataJSON();calls.push(p);let result=[];
- if(p.action==='app_session_validate')result={valid:true};
- if(p.action==='app_live_versions')result={version:'1',scopeVersions:{},syncStatus:{state:'current'}};
- if(p.action==='e_record_detail')result=['apple','banana','orange','pear'].map((Title,index)=>({GeneralID:101+index,Title,sy:'测试单词',yp:saved.get(String(101+index))||''}));
- if(p.action==='app_word_audio_ensure'){
- if(fail)return route.fulfill({status:429,json:{retcode:-1,retmsg:'今日自动读音额度已用完,请稍后再试'}});
- await new Promise(done=>setTimeout(done,180));const url=origin+'/fixtures/'+p.wordId+'.wav';saved.set(p.wordId,url);result={state:'ready',url,source:'xfyun'};
- }
- return route.fulfill({json:{retcode:0,result}});
- });
- const page=await context.newPage();page.on('pageerror',error=>errors.push(error.message));
- await page.goto(origin+'/pages/his/his_words?id=201');await page.getByRole('button',{name:'播放单词读音:apple'}).waitFor();
- const generated=()=>calls.filter(p=>p.action==='app_word_audio_ensure');
- await page.getByRole('button',{name:'播放单词读音:apple'}).click();assert.equal(generated().length,0);
- await page.getByRole('button',{name:'播放单词读音:banana'}).click();await page.getByRole('status').filter({hasText:'正在准备读音'}).waitFor();
- await page.getByRole('status').filter({hasText:'正在准备读音'}).waitFor({state:'hidden'});assert.equal(generated().length,1);
- await page.getByRole('button',{name:'播放单词读音:banana'}).click();assert.equal(generated().length,1);
- await page.getByRole('button',{name:'播放单词读音:orange'}).click();await page.waitForFunction(()=>document.body.textContent.includes('正在准备读音'));
- await page.getByRole('status').filter({hasText:'正在准备读音'}).waitFor({state:'hidden'});assert.equal(generated().length,2);assert.equal(generated()[1].playbackFailed,true);
- await page.reload();await page.getByRole('button',{name:'播放单词读音:banana'}).click();assert.equal(generated().length,2);
- fail=true;await page.getByRole('button',{name:'播放单词读音:pear'}).click();await page.getByText('今日自动读音额度已用完,请稍后再试',{exact:true}).waitFor();
- fail=false;await page.getByRole('button',{name:'播放单词读音:pear'}).click();await page.getByRole('status').filter({hasText:'正在准备读音'}).waitFor();await page.getByRole('status').filter({hasText:'正在准备读音'}).waitFor({state:'hidden'});assert.equal(generated().length,4);assert.ok(saved.get('104'));await page.getByText('今日自动读音额度已用完,请稍后再试',{exact:true}).waitFor({state:'hidden'});
- assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),'mobile horizontal overflow');
- await page.screenshot({path:out+'/history-375.png'});assert.deepEqual(errors,[]);
- assert.equal(calls.filter(p=>['e_add_words','content_add','app_lesson_complete'].includes(p.action)).length,0);
- writeFileSync(out+'/browser-result.json',JSON.stringify({passed:true,checks:['existing playback','first synthesis and reuse','404 recovery','reload reuse','quota error and retry','375px layout','no teaching writes'],environment:'local build with isolated API/audio fixtures'},null,2));
- console.log('Word audio browser checks passed');
- }finally{await browser.close();await new Promise(done=>server.close(done));}
|