| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 |
- // Isolated browser acceptance against local builds and 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 output=resolve('tmp/support-requests-qa');mkdirSync(output,{recursive:true});
- async function serve(project){
- const root=resolve('dist/'+project+'/browser');
- const server=createServer((req,res)=>{let file=resolve(root,'.'+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','.woff2':'font/woff2'})[extname(file)]||'application/octet-stream');res.end(readFileSync(file));});
- await new Promise(r=>server.listen(0,'127.0.0.1',r));return {server,origin:'http://127.0.0.1:'+server.address().port};
- }
- const admin=await serve('xiaoshu-admin'),mobile=await serve('xiaoshu-mobile');
- const browser=await chromium.launch({headless:true,executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'});
- const errors=[],checks=[],calls=[];let fail=false;
- const records=Array.from({length:25},(_,i)=>({id:'request'+i,userId:String(i%2+1),accountName:i%2?'张老师':'李同学',title:(i%3===0?'【休假申请】':i%3===1?'【意见反馈】':'历史提交 ')+(i%2?'张老师':'李同学'),content:i===0?'10月16日至17日因事申请休假。\n请管理员查看并协调安排。':i===1?'<script>window.injected=true</script>\n希望优化课程提醒。':'希望调整上课时间,感谢老师的帮助。',submittedAt:'2026-10-15T04:30:00Z',kind:['leave','feedback','legacy'][i%3],role:i%2?'teacher':'student'}));
- try{
- const context=await browser.newContext({viewport:{width:1440,height:1000}});
- await context.addInitScript(()=>{const auth={objectId:'fixture-admin',sessionToken:'fixture-only',username:'验收管理员',operationsRole:'ops-manager'};localStorage.setItem('xiaoshu_admin_session_v1',JSON.stringify(auth));});
- await context.route('**/*',async route=>{
- const u=new URL(route.request().url());if(u.origin!==admin.origin)return route.abort();if(!u.pathname.startsWith('/cloud-functions/'))return route.continue();
- const {params:p}=route.request().postDataJSON();calls.push(p);
- if(p.operation!=='ops/support-requests/list')return route.fulfill({json:{success:true,data:{}}});
- if(fail)return route.fulfill({status:500,json:{success:false,message:'模拟读取失败'}});
- const filtered=records.filter(r=>(!p.role||r.role===p.role)&&(!p.kind||r.kind===p.kind)&&(!p.search||(r.title+r.accountName+r.content).includes(p.search)));
- await route.fulfill({json:{success:true,data:{items:filtered.slice((p.page-1)*20,p.page*20),total:filtered.length,page:p.page,pageSize:20,summary:{feedback:filtered.filter(r=>r.kind==='feedback').length,leave:filtered.filter(r=>r.kind==='leave').length,legacy:filtered.filter(r=>r.kind==='legacy').length},refreshedAt:'2026-10-15T04:45:00Z'}}});
- });
- const page=await context.newPage();page.setDefaultTimeout(12000);page.on('pageerror',e=>errors.push(e.message));await page.goto(admin.origin+'/admin/support-requests');await page.locator('.request-row').first().waitFor();
- assert.equal(await page.getByRole('link',{name:'反馈与休假申请',exact:true}).count(),1);
- assert.equal(await page.locator('.request-row').count(),20);assert.match(await page.locator('.summary-grid').innerText(),/25/);assert.equal(await page.evaluate(()=>window.injected),undefined);
- await page.screenshot({path:output+'/admin-desktop.png',fullPage:true});await page.screenshot({path:output+'/admin-overview.png'});
- await page.getByRole('button',{name:'下一页',exact:true}).click();await page.waitForFunction(()=>document.querySelectorAll('.request-row').length===5);checks.push('菜单入口、全部记录、分页及内容按纯文本显示');
- await page.getByLabel('提交身份').selectOption('teacher');await page.getByLabel('类型',{exact:true}).selectOption('leave');await page.getByRole('button',{name:'查询',exact:true}).click();await page.waitForFunction(()=>document.querySelectorAll('.request-row').length===4);
- assert.equal(calls.at(-1).page,1);assert.ok((await page.locator('.request-meta').allTextContents()).every(t=>t.includes('老师')&&t.includes('休假申请')));checks.push('身份与类型组合筛选,查询回到第一页');
- await page.getByLabel('搜索',{exact:true}).fill('不存在的内容');await page.getByRole('button',{name:'查询',exact:true}).click();await page.getByText('暂无符合条件的反馈或申请').waitFor();
- await page.getByRole('button',{name:'重置',exact:true}).click();await page.locator('.request-row').first().waitFor();
- fail=true;await page.getByRole('button',{name:'刷新',exact:true}).click();await page.getByRole('alert').filter({hasText:'模拟读取失败'}).waitFor();assert.equal(await page.locator('.request-row').count(),0);
- fail=false;await page.getByRole('button',{name:'重新加载',exact:true}).click();await page.locator('.request-row').first().waitFor();checks.push('空状态、失败不伪装为空记录、重试恢复');
- for(const width of [768,390]){await page.setViewportSize({width,height:900});assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1));await page.screenshot({path:output+'/admin-'+width+'.png',fullPage:true});}checks.push('1440/768/390宽度布局');
- for(const role of [{id:1,group:1,name:'李同学',type:'feedback'},{id:2,group:3,name:'张老师',type:'leave'}]){
- const c=await browser.newContext({viewport:{width:390,height:844}});await c.addInitScript(role=>localStorage.setItem('xiaoshu.angular.session',JSON.stringify({token:'r:fixture-only',user:{userId:role.id,groupId:role.group,honeyName:role.name}})),role);
- let submitted;
- await c.route('**/*',async route=>{const u=new URL(route.request().url());if(u.origin!==mobile.origin)return route.abort();if(!u.pathname.startsWith('/cloud-functions/'))return route.continue();const {params:p}=route.request().postDataJSON();if(p.action==='guestbook_add'){submitted=JSON.parse(p.model);return route.fulfill({json:{retcode:1,result:{GID:123}}});}return route.fulfill({json:{retcode:1,result:{valid:true}}});});
- const p=await c.newPage();p.setDefaultTimeout(12000);p.on('pageerror',e=>errors.push(e.message));await p.goto(mobile.origin+'/pages/member/feedback');await p.locator('#name').fill(role.name);await p.getByRole('combobox',{name:/提交类型/}).click();await p.getByRole('option',{name:role.type==='leave'?'休假申请':'意见反馈',exact:true}).click();await p.locator('#content').fill('浏览器提交验收');await p.screenshot({path:output+'/mobile-'+role.type+'.png',fullPage:true});await p.getByRole('button',{name:'提交',exact:true}).click();await p.getByText('提交成功',{exact:true}).waitFor();
- assert.equal(submitted.UserID,role.id);assert.equal(submitted.Cateid,22);assert.equal(submitted.Title,`【${role.type==='leave'?'休假申请':'意见反馈'}】${role.name}`);assert.equal(submitted.TContent,'浏览器提交验收');await c.close();
- }
- checks.push('学生反馈、老师休假申请:实际表单提交类型、本人编号和正文');
- assert.deepEqual(errors,[]);writeFileSync(output+'/result.json',JSON.stringify({passed:true,checks,errors},null,2));console.log(JSON.stringify({passed:true,checks,output}));
- }finally{await browser.close();admin.server.close();mobile.server.close();}
|