// 浏览器自动化公共模块(Playwright + Chromium)与 HTTP 公共方法(axios)。 import axios from 'axios' import { chromium, type Browser, type BrowserContext, type Page } from 'playwright-core' import { API_URL, BASE_URL, CHROMIUM_BIN, PASSWORD, USERNAME } from './config.js' /** 默认视口尺寸 */ export const DEFAULT_VIEWPORT = { width: 1440, height: 1000 } /** * 启动 headless Chromium(复用 Playwright 缓存的浏览器)。 * * @returns Browser 实例 */ export async function launchBrowser(): Promise { return chromium.launch({ executablePath: CHROMIUM_BIN, headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'], }) } /** * 创建浏览器上下文。 * * @param browser Browser 实例 * @param viewport 视口尺寸 * @returns BrowserContext 实例 */ export async function newContext( browser: Browser, viewport: { width: number; height: number } = DEFAULT_VIEWPORT, ): Promise { const context = await browser.newContext({ viewport }) // 首次访问需等待 Vite 按需编译,放宽默认导航超时,避免冷启动误判失败 context.setDefaultNavigationTimeout(120000) return context } /** * 在上下文中注入登录态与中文语言偏好(对之后打开的页面生效)。 * * @param context BrowserContext 实例 * @param token 访问令牌 */ export async function injectSession(context: BrowserContext, token: string): Promise { await context.addInitScript( ([t]) => { localStorage.setItem('token', t as string) localStorage.setItem('locale', 'zh-CN') }, [token], ) } /** * 等待页面渲染完成:先等文档就绪,再等关键元素可见,最后做短暂稳定等待。 * * @param page Page 实例 * @param options.selector 关键元素选择器(可选) * @param options.settle 元素出现后的稳定等待毫秒数,默认 600ms */ export async function waitPage( page: Page, options: { selector?: string; settle?: number } = {}, ): Promise { await page.waitForLoadState('domcontentloaded').catch(() => undefined) if (options.selector) { await page .locator(options.selector) .first() .waitFor({ state: 'visible', timeout: 20000 }) .catch(() => undefined) } // 等待异步数据请求基本完成后再截图,避免表格/图表空白; // SSE 页面(运行日志)不会进入空闲,超时忽略即可。 await page.waitForLoadState('networkidle', { timeout: 8000 }).catch(() => undefined) await page.waitForTimeout(options.settle ?? 900) } /** * 按可见文本点击按钮。 * * @param page Page 实例 * @param text 按钮文本 * @returns 是否点击成功 */ export async function clickText(page: Page, text: string): Promise { const button = page.locator('button', { hasText: text }).first() try { // 按钮可能随异步配置(如 AI 启用状态)延迟渲染,等待其出现 await button.waitFor({ state: 'visible', timeout: 15000 }) } catch { return false } // 部分 Naive UI 按钮对 Playwright 合成鼠标事件不响应,改用 DOM 原生 click await button.evaluate((el) => (el as HTMLElement).click()) return true } /** * 登录测试账号并返回 JWT。 * * @returns 访问令牌 */ export async function login(): Promise { const res = await axios.post(`${API_URL}/user/login`, { username: USERNAME, password: PASSWORD }) return res.data.data.token } /** 打开首页并等待(用于非截图场景的简单访问) */ export async function openHome(page: Page): Promise { await page.goto(BASE_URL) await page.waitForTimeout(1500) }