capture_system_screenshots.mjs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { dirname, join } from 'node:path';
  2. import { fileURLToPath } from 'node:url';
  3. import { createRequire } from 'node:module';
  4. import { mkdirSync, writeFileSync } from 'node:fs';
  5. const here = dirname(fileURLToPath(import.meta.url));
  6. const root = join(here, '../..');
  7. const require = createRequire(join(root, 'Web/package.json'));
  8. const { chromium } = require('@playwright/test');
  9. const { sm2 } = require('sm-crypto-v2');
  10. const FRONTEND = process.env.AIDOP_PPT_FRONTEND ?? 'http://127.0.0.1:8888';
  11. const API = process.env.AIDOP_PPT_API ?? 'http://127.0.0.1:5007';
  12. const ACCOUNT = process.env.AIDOP_PPT_ACCOUNT ?? 'AIDOPDemo';
  13. const PASSWORD = process.env.AIDOP_PPT_PASSWORD ?? '1234567890dop';
  14. const TENANT = Number(process.env.AIDOP_PPT_TENANT ?? 797403760988229);
  15. const PUBLIC_KEY =
  16. '0484C7466D950E120E5ECE5DD85D0C90EAA85081A3A2BD7C57AE6DC822EFCCBD66620C67B0103FC8DD280E36C3B282977B722AAEC3C56518EDCEBAFB72C5A05312';
  17. const OUTPUT = join(here, 'system-screenshots');
  18. const allPages = [
  19. ['decision-grid', '/#/aidop/smart-ops/grid'],
  20. ['decision-workbench', '/#/dashboard/home'],
  21. ['decision-diagnosis', '/#/aidop/smart-diagnosis'],
  22. ['decision-improvement-ledger', '/#/aidop/smart-ops/improvement-plans'],
  23. ['collaboration-exception-dashboard', '/#/aidop/smart-ops/s8'],
  24. ['collaboration-exception-list', '/#/aidop/s8/exceptions'],
  25. ['data-platform-overview', '/#/aidop/data-platform/overview'],
  26. ['data-platform-map', '/#/aidop/data-platform/data-map'],
  27. ['data-platform-sources', '/#/aidop/data-platform/sources'],
  28. ['data-platform-sync-tasks', '/#/aidop/data-platform/sync-tasks'],
  29. ['data-platform-sync-logs', '/#/aidop/data-platform/sync-logs'],
  30. ['data-kpi-master', '/#/aidop/smart-ops/kpi-master'],
  31. ];
  32. const requested = new Set(
  33. String(process.env.AIDOP_PPT_PAGES ?? '')
  34. .split(',')
  35. .map((item) => item.trim())
  36. .filter(Boolean),
  37. );
  38. const pages = requested.size ? allPages.filter(([name]) => requested.has(name)) : allPages;
  39. async function login() {
  40. const response = await fetch(`${API}/api/sysAuth/login`, {
  41. method: 'POST',
  42. headers: { 'Content-Type': 'application/json' },
  43. body: JSON.stringify({
  44. account: ACCOUNT,
  45. password: sm2.doEncrypt(PASSWORD, PUBLIC_KEY, 1),
  46. tenantId: TENANT,
  47. }),
  48. });
  49. const body = await response.json();
  50. const token = body.result?.accessToken;
  51. if (!response.ok || !token) {
  52. throw new Error(`login failed: HTTP ${response.status} ${JSON.stringify(body)}`);
  53. }
  54. return token;
  55. }
  56. async function openPage(browser, token) {
  57. const context = await browser.newContext({
  58. viewport: { width: 1600, height: 1000 },
  59. deviceScaleFactor: 1,
  60. });
  61. await context.addCookies([{ name: 'token', value: token, url: `${FRONTEND}/` }]);
  62. await context.addInitScript(
  63. ({ accessToken, api }) => {
  64. localStorage.setItem('admin.net:access-token', JSON.stringify(accessToken));
  65. window.__env__ = { ...(window.__env__ ?? {}), VITE_API_URL: api };
  66. },
  67. { accessToken: token, api: API },
  68. );
  69. await context.route('**/config.js*', async (route) => {
  70. await route.fulfill({
  71. status: 200,
  72. contentType: 'application/javascript',
  73. body: `window.__env__ = { VITE_API_URL: ${JSON.stringify(API)} };`,
  74. });
  75. });
  76. await context.route('**/api/sysMenu/loginMenuTree*', async (route) => {
  77. const response = await route.fetch();
  78. const body = await response.json();
  79. const routes = body?.result;
  80. if (Array.isArray(routes) && !routes.some((item) => item?.path === '/dashboard/home')) {
  81. routes.unshift({
  82. path: '/dashboard/home',
  83. name: 'home',
  84. component: '/home/index',
  85. type: 2,
  86. meta: {
  87. title: '我的工作台',
  88. isHide: false,
  89. isKeepAlive: true,
  90. isAffix: true,
  91. icon: 'ele-HomeFilled',
  92. },
  93. children: [],
  94. });
  95. }
  96. await route.fulfill({
  97. response,
  98. contentType: 'application/json',
  99. body: JSON.stringify(body),
  100. });
  101. });
  102. const page = await context.newPage();
  103. page.setDefaultTimeout(45000);
  104. return { context, page };
  105. }
  106. mkdirSync(OUTPUT, { recursive: true });
  107. const token = await login();
  108. const browser = await chromium.launch({ channel: 'msedge', headless: true });
  109. const report = { generatedAt: new Date().toISOString(), frontend: FRONTEND, api: API, pages: [] };
  110. try {
  111. for (const [name, path] of pages) {
  112. const { context, page } = await openPage(browser, token);
  113. const errors = [];
  114. page.on('pageerror', (error) => errors.push(error.message));
  115. page.on('console', (message) => {
  116. if (message.type() === 'error') errors.push(message.text());
  117. });
  118. const initialPath = name === 'decision-diagnosis' ? '/#/aidop/smart-ops/s1' : path;
  119. await page.goto(`${FRONTEND}${initialPath}`, { waitUntil: 'domcontentloaded' });
  120. await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
  121. await page.locator('.el-loading-mask').waitFor({ state: 'hidden', timeout: 20000 }).catch(() => {});
  122. if (name === 'decision-diagnosis') {
  123. await page.getByRole('button', { name: '智慧诊断' }).first().click();
  124. await page.waitForURL('**/#/aidop/smart-diagnosis**', { timeout: 30000 });
  125. await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {});
  126. await page.locator('.el-loading-mask').waitFor({ state: 'hidden', timeout: 20000 }).catch(() => {});
  127. }
  128. await page.waitForTimeout(2500);
  129. const file = `${name}.png`;
  130. await page.screenshot({ path: join(OUTPUT, file), fullPage: true });
  131. report.pages.push({
  132. name,
  133. path,
  134. file,
  135. finalUrl: page.url(),
  136. title: await page.title(),
  137. errors: errors.slice(0, 10),
  138. });
  139. console.log(`captured ${name}: ${page.url()}`);
  140. await context.close();
  141. }
  142. } finally {
  143. await browser.close();
  144. }
  145. writeFileSync(join(OUTPUT, 'capture-report.json'), JSON.stringify(report, null, 2));