chore: 初始化文档站仓库
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
// 浏览器自动化公共模块(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<Browser> {
|
||||
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<BrowserContext> {
|
||||
return browser.newContext({ viewport })
|
||||
}
|
||||
|
||||
/**
|
||||
* 在上下文中注入登录态与中文语言偏好(对之后打开的页面生效)。
|
||||
*
|
||||
* @param context BrowserContext 实例
|
||||
* @param token 访问令牌
|
||||
*/
|
||||
export async function injectSession(context: BrowserContext, token: string): Promise<void> {
|
||||
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<void> {
|
||||
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<boolean> {
|
||||
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<string> {
|
||||
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<void> {
|
||||
await page.goto(BASE_URL)
|
||||
await page.waitForTimeout(1500)
|
||||
}
|
||||
Reference in New Issue
Block a user