import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { createRequire } from 'node:module'; import { mkdirSync, writeFileSync } from 'node:fs'; const here = dirname(fileURLToPath(import.meta.url)); const root = join(here, '../..'); const require = createRequire(join(root, 'Web/package.json')); const { chromium } = require('@playwright/test'); const { sm2 } = require('sm-crypto-v2'); const FRONTEND = process.env.AIDOP_PPT_FRONTEND ?? 'http://127.0.0.1:8888'; const API = process.env.AIDOP_PPT_API ?? 'http://127.0.0.1:5007'; const ACCOUNT = process.env.AIDOP_PPT_ACCOUNT ?? 'AIDOPDemo'; const PASSWORD = process.env.AIDOP_PPT_PASSWORD ?? '1234567890dop'; const TENANT = Number(process.env.AIDOP_PPT_TENANT ?? 797403760988229); const PUBLIC_KEY = '0484C7466D950E120E5ECE5DD85D0C90EAA85081A3A2BD7C57AE6DC822EFCCBD66620C67B0103FC8DD280E36C3B282977B722AAEC3C56518EDCEBAFB72C5A05312'; const OUTPUT = join(here, 'system-screenshots'); const allPages = [ ['decision-grid', '/#/aidop/smart-ops/grid'], ['decision-workbench', '/#/dashboard/home'], ['decision-diagnosis', '/#/aidop/smart-diagnosis'], ['decision-improvement-ledger', '/#/aidop/smart-ops/improvement-plans'], ['collaboration-exception-dashboard', '/#/aidop/smart-ops/s8'], ['collaboration-exception-list', '/#/aidop/s8/exceptions'], ['data-platform-overview', '/#/aidop/data-platform/overview'], ['data-platform-map', '/#/aidop/data-platform/data-map'], ['data-platform-sources', '/#/aidop/data-platform/sources'], ['data-platform-sync-tasks', '/#/aidop/data-platform/sync-tasks'], ['data-platform-sync-logs', '/#/aidop/data-platform/sync-logs'], ['data-kpi-master', '/#/aidop/smart-ops/kpi-master'], ]; const requested = new Set( String(process.env.AIDOP_PPT_PAGES ?? '') .split(',') .map((item) => item.trim()) .filter(Boolean), ); const pages = requested.size ? allPages.filter(([name]) => requested.has(name)) : allPages; async function login() { const response = await fetch(`${API}/api/sysAuth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ account: ACCOUNT, password: sm2.doEncrypt(PASSWORD, PUBLIC_KEY, 1), tenantId: TENANT, }), }); const body = await response.json(); const token = body.result?.accessToken; if (!response.ok || !token) { throw new Error(`login failed: HTTP ${response.status} ${JSON.stringify(body)}`); } return token; } async function openPage(browser, token) { const context = await browser.newContext({ viewport: { width: 1600, height: 1000 }, deviceScaleFactor: 1, }); await context.addCookies([{ name: 'token', value: token, url: `${FRONTEND}/` }]); await context.addInitScript( ({ accessToken, api }) => { localStorage.setItem('admin.net:access-token', JSON.stringify(accessToken)); window.__env__ = { ...(window.__env__ ?? {}), VITE_API_URL: api }; }, { accessToken: token, api: API }, ); await context.route('**/config.js*', async (route) => { await route.fulfill({ status: 200, contentType: 'application/javascript', body: `window.__env__ = { VITE_API_URL: ${JSON.stringify(API)} };`, }); }); await context.route('**/api/sysMenu/loginMenuTree*', async (route) => { const response = await route.fetch(); const body = await response.json(); const routes = body?.result; if (Array.isArray(routes) && !routes.some((item) => item?.path === '/dashboard/home')) { routes.unshift({ path: '/dashboard/home', name: 'home', component: '/home/index', type: 2, meta: { title: '我的工作台', isHide: false, isKeepAlive: true, isAffix: true, icon: 'ele-HomeFilled', }, children: [], }); } await route.fulfill({ response, contentType: 'application/json', body: JSON.stringify(body), }); }); const page = await context.newPage(); page.setDefaultTimeout(45000); return { context, page }; } mkdirSync(OUTPUT, { recursive: true }); const token = await login(); const browser = await chromium.launch({ channel: 'msedge', headless: true }); const report = { generatedAt: new Date().toISOString(), frontend: FRONTEND, api: API, pages: [] }; try { for (const [name, path] of pages) { const { context, page } = await openPage(browser, token); const errors = []; page.on('pageerror', (error) => errors.push(error.message)); page.on('console', (message) => { if (message.type() === 'error') errors.push(message.text()); }); const initialPath = name === 'decision-diagnosis' ? '/#/aidop/smart-ops/s1' : path; await page.goto(`${FRONTEND}${initialPath}`, { waitUntil: 'domcontentloaded' }); await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {}); await page.locator('.el-loading-mask').waitFor({ state: 'hidden', timeout: 20000 }).catch(() => {}); if (name === 'decision-diagnosis') { await page.getByRole('button', { name: '智慧诊断' }).first().click(); await page.waitForURL('**/#/aidop/smart-diagnosis**', { timeout: 30000 }); await page.waitForLoadState('networkidle', { timeout: 30000 }).catch(() => {}); await page.locator('.el-loading-mask').waitFor({ state: 'hidden', timeout: 20000 }).catch(() => {}); } await page.waitForTimeout(2500); const file = `${name}.png`; await page.screenshot({ path: join(OUTPUT, file), fullPage: true }); report.pages.push({ name, path, file, finalUrl: page.url(), title: await page.title(), errors: errors.slice(0, 10), }); console.log(`captured ${name}: ${page.url()}`); await context.close(); } } finally { await browser.close(); } writeFileSync(join(OUTPUT, 'capture-report.json'), JSON.stringify(report, null, 2));