| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 |
- // Local UI acceptance with isolated API and storage fixtures. No online writes.
- 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/lesson-attachments-qa');mkdirSync(out,{recursive:true});
- const png=Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+/wckAAAAASUVORK5CYII=','base64');
- 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(r=>server.listen(0,'127.0.0.1',r));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'});
- let role=3,failUpload=false,sequence=0,feedback={feedbackStatus:'pending',revision:'2026-10-29T00:00:00Z'};
- const calls=[],errors=[];
- try{
- const context=await browser.newContext({viewport:{width:375,height:812},reducedMotion:'reduce'});
- await context.addInitScript(()=>{if(!localStorage.getItem('xiaoshu.angular.session'))localStorage.setItem('xiaoshu.angular.session',JSON.stringify({token:'r:local-fixture',user:{userId:300,groupId:3,honeyName:'测试老师'}}));});
- await context.route('**/*',async route=>{
- const url=new URL(route.request().url());if(url.origin!==origin)return route.abort();
- if(url.pathname.startsWith('/fixtures/'))return route.fulfill({contentType:url.pathname.endsWith('.png')?'image/png':'application/pdf',body:url.pathname.endsWith('.png')?png:Buffer.from('%PDF-1.4\nnotes')});
- if(!url.pathname.startsWith('/cloud-functions/'))return route.continue();
- const {params:p}=route.request().postDataJSON();calls.push(p);let result=[];
- if(p.action==='e_order_detail')result=[{GeneralID:901,szyh:100,pl:300,HoneyName:'测试学员',kcid:0,bindingId:'',courseCategoryKey:'primary_writing',yykcmc:'小学语法写作',dszt:30,updatedAt:'2026-10-29T00:00:00Z',allowedActions:role===3?['editFeedback']:['review'],feedback}];
- if(p.action==='app_live_versions')result={version:'1',scopeVersions:{},syncStatus:{state:'current'}};
- if(p.action==='app_lesson_attachment_upload'){
- if(failUpload)return route.fulfill({status:409,json:{retcode:-1,retmsg:'模拟上传失败,稍后重试'}});
- result={id:'upload-'+ ++sequence,name:p.fileName,contentType:p.contentType,size:Buffer.from(p.base64,'base64').length,url:origin+'/fixtures/'+sequence+(p.contentType.startsWith('image/')?'.png':'.pdf')};
- }
- if(p.action==='app_lesson_feedback_update'){feedback={...JSON.parse(p.feedback),feedbackStatus:'submitted',revision:'2026-10-29T00:01:00Z'};result={saved:true};}
- await route.fulfill({json:{retcode:1,result}});
- });
- const page=await context.newPage();page.on('pageerror',error=>errors.push(error.message));
- await page.goto(origin+'/pages/shop/get_order?id=901&editReport=1');await page.locator('dialog[open]').waitFor();
- await page.getByPlaceholder('例:人教版三年级 Unit 3,重点词汇与句型').fill('阅读短文,整理语法知识点并练习写作');
- await page.getByPlaceholder('说明当前进度与下次起点').fill('完成课堂练习,下次复习错题');await page.getByText('本次无作业',{exact:true}).click();
- const input=page.locator('.attachment-picker input');
- await input.setInputFiles([{name:'课堂笔记.png',mimeType:'image/png',buffer:png},{name:'语法知识点.pdf',mimeType:'application/pdf',buffer:Buffer.from('%PDF-1.4\nnotes')}]);
- await page.locator('.attachment-list article').nth(1).waitFor();
- assert.equal(await page.locator('.attachment-list img').count(),1);
- await page.getByRole('button',{name:'移除附件:语法知识点.pdf'}).click();await page.locator('.attachment-list article').nth(1).waitFor({state:'detached'});assert.equal(await page.locator('.attachment-list article').count(),1);
- await input.setInputFiles({name:'语法知识点.pdf',mimeType:'application/pdf',buffer:Buffer.from('%PDF-1.4\nnotes')});await page.locator('.attachment-list article').nth(1).waitFor();
- failUpload=true;await input.setInputFiles({name:'补充.txt',mimeType:'text/plain',buffer:Buffer.from('课堂笔记')});await page.locator('.feedback-error').waitFor();
- assert.equal(await page.locator('.attachment-list article').count(),2);assert.match(await page.getByPlaceholder('说明当前进度与下次起点').inputValue(),/完成课堂练习/);failUpload=false;
- for(const viewport of [{width:375,height:812},{width:812,height:375}]){
- await page.setViewportSize(viewport);await page.locator('.feedback-attachments').scrollIntoViewIfNeeded();
- assert.ok(await page.locator('dialog').evaluate(element=>element.scrollWidth<=element.clientWidth+1),'dialog must not overflow horizontally');
- await page.screenshot({path:out+'/teacher-'+viewport.width+'.png'});
- }
- await page.setViewportSize({width:375,height:812});await page.getByRole('button',{name:'提交反馈',exact:true}).click();await page.locator('dialog[open]').waitFor({state:'hidden'});
- assert.equal(await page.locator('.saved-attachments a').count(),2);
- await page.getByRole('button',{name:'修改报告',exact:true}).last().click();await page.locator('.attachment-list article').nth(1).waitFor();await page.getByRole('button',{name:'取消修改',exact:true}).click();
- role=1;await page.evaluate(()=>localStorage.setItem('xiaoshu.angular.session',JSON.stringify({token:'r:student-fixture',user:{userId:100,groupId:1,honeyName:'测试学员'}})));
- await page.goto(origin+'/pages/shop/stu_get_order?id=901');await page.locator('.saved-attachments a').nth(1).waitFor();assert.equal(await page.locator('input[type=file]').count(),0);
- await page.locator('.saved-attachments').scrollIntoViewIfNeeded();await page.screenshot({path:out+'/student-375.png'});
- assert.equal(calls.filter(call=>call.action==='app_lesson_feedback_update').length,1);assert.equal(calls.filter(call=>call.action==='app_lesson_complete').length,0);assert.deepEqual(errors,[]);
- writeFileSync(out+'/result.json',JSON.stringify({passed:true,checks:['375px与横屏无横向溢出','多文件上传与图片预览','移除与再次添加','失败保留已上传文件和文字','保存重开仍有附件','学生可看附件且没有上传入口'],environment:'local production build with mocked API and storage; no online writes'},null,2));
- console.log('Lesson attachment browser checks passed');
- }finally{await browser.close();await new Promise(r=>server.close(r));}
|