shots-mobile.mjs 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. /**
  2. * 移动端逐屏截图(对照 prototypes/fmode-mobile-20261102/shots)
  3. *
  4. * 用法:node scripts/shots-mobile.mjs
  5. * 前置:npm run build:mock(MockApi 数据,无需真实登录)
  6. */
  7. import { chromium, devices } from 'playwright';
  8. import { mkdir } from 'node:fs/promises';
  9. import { createServer } from 'node:http';
  10. import { existsSync } from 'node:fs';
  11. import { readFile } from 'node:fs/promises';
  12. import { extname, join, resolve, dirname } from 'node:path';
  13. import { fileURLToPath } from 'node:url';
  14. const here = dirname(fileURLToPath(import.meta.url));
  15. const distDir = resolve(here, '../dist/fmode-app-v2/browser');
  16. const outDir = resolve(here, '../test-results/mobile-shots');
  17. const MIME = {
  18. '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css',
  19. '.json': 'application/json', '.svg': 'image/svg+xml', '.woff2': 'font/woff2',
  20. '.ico': 'image/x-icon', '.png': 'image/png',
  21. };
  22. const server = createServer(async (req, res) => {
  23. try {
  24. const url = decodeURIComponent((req.url ?? '/').split('?')[0]);
  25. let file = join(distDir, url);
  26. if (!existsSync(file) || url === '/') file = join(distDir, 'index.html');
  27. const body = await readFile(file);
  28. res.writeHead(200, { 'Content-Type': MIME[extname(file)] ?? 'application/octet-stream' });
  29. res.end(body);
  30. } catch {
  31. res.writeHead(404).end('not found');
  32. }
  33. });
  34. await new Promise((r) => server.listen(0, r));
  35. const base = `http://127.0.0.1:${server.address().port}`;
  36. await mkdir(outDir, { recursive: true });
  37. const browser = await chromium.launch({ channel: 'msedge' });
  38. const ctx = await browser.newContext({ ...devices['iPhone 13'], deviceScaleFactor: 2 });
  39. const page = await ctx.newPage();
  40. const errors = [];
  41. page.on('pageerror', (e) => errors.push(String(e)));
  42. await page.goto(base + '/');
  43. await page.evaluate(() => {
  44. localStorage.setItem('fmode_ui_mode_override', 'mobile');
  45. localStorage.setItem('fmode_session_token', 'demo-fmode');
  46. localStorage.setItem('fmode_me', JSON.stringify({ id: 'me-1', name: '林一舟', mobile: '13800000000', companyName: '北美数字科技' }));
  47. });
  48. const shots = [
  49. ['01-messages', '/mobile/messages'],
  50. ['03-home', '/mobile/home'],
  51. ['04-contacts', '/mobile/contacts'],
  52. ['06-projects', '/mobile/projects'],
  53. ['08-tasks', '/mobile/tasks'],
  54. ['10-deliverables', '/mobile/deliverables'],
  55. ['11-events', '/mobile/events'],
  56. ['12-workbench', '/mobile/workbench'],
  57. ['13-mine', '/mobile/mine'],
  58. ];
  59. for (const [name, path] of shots) {
  60. await page.goto(base + path, { waitUntil: 'networkidle' });
  61. await page.waitForTimeout(900);
  62. await page.screenshot({ path: join(outDir, `${name}.png`) });
  63. console.log('shot', name, page.url());
  64. }
  65. // 登录页(清掉登录态)
  66. await page.goto(base + '/login');
  67. await page.evaluate(() => {
  68. localStorage.removeItem('fmode_session_token');
  69. localStorage.removeItem('fmode_me');
  70. });
  71. await page.goto(base + '/login', { waitUntil: 'networkidle' });
  72. await page.waitForTimeout(600);
  73. await page.screenshot({ path: join(outDir, '00-login.png') });
  74. console.log('errors:', errors.length ? errors : 'none');
  75. await browser.close();
  76. server.close();