import fs from 'node:fs' import path from 'node:path' import type { APIRequestContext, Locator, Page } from '@playwright/test' import { expect } from './fixtures' export interface AdminCredentials { username: string; password: string } export function readAdminCredentials(): AdminCredentials { const configured = process.env.E2E_ACCOUNT_FILE?.trim() const filename = path.resolve(configured || '../账号.MD') if (!fs.existsSync(filename)) throw new Error(`测试账号文件不存在:${filename}`) const values = new Map() for (const line of fs.readFileSync(filename, 'utf8').split(/\r?\n/)) { const match = line.match(/^\s*([^::]+)\s*[::]\s*(.*?)\s*$/) if (match?.[1] && match[2]) values.set(match[1].trim(), match[2].trim()) } const username = values.get('账号') || values.get('用户名') || '' const password = values.get('密码') || '' if (!username || !password) throw new Error('账号.MD 必须包含“账号”和“密码”两项') return { username, password } } type RecordValue = Record const asRecord = (value: unknown): RecordValue => value && typeof value === 'object' ? value as RecordValue : {} export const envelopeData = (value: unknown): T => { const body = asRecord(value) return ('data' in body ? body.data : value) as T } export async function adminAccessToken(request: APIRequestContext) { const credentials = readAdminCredentials() const response = await request.post('/api/auth/v1/auth/login', { data: { roleCode: 'admin', username: credentials.username, password: credentials.password, rememberMe: false }, }) expect(response.ok(), `管理员 API 登录失败:HTTP ${response.status()}`).toBeTruthy() const data = asRecord(envelopeData(await response.json())) const tokens = asRecord(data.tokens) const accessToken = String(data.accessToken ?? tokens.accessToken ?? '') if (!accessToken) throw new Error('管理员登录响应缺少 accessToken') return accessToken } export async function authHeaders(request: APIRequestContext) { const accessToken = await adminAccessToken(request) return { Authorization: `Bearer ${accessToken}` } } export async function loginAsAdmin(page: Page) { const credentials = readAdminCredentials() await page.goto('/login') await page.locator('[data-role-code="admin"]').click() const username = page.locator('input[name="username"]') const password = page.locator('input[name="password"]') await username.fill(credentials.username) await password.fill(credentials.password) await page.getByRole('button', { name: /登录系统|正在验证身份/ }).click() await expect(page).not.toHaveURL(/\/login(?:\?|$)/) await expect(page.locator('.app-shell, .main-layout, .platform-shell').first()).toBeVisible() } export const visibleDialog = (page: Page) => page.locator('.el-dialog:visible').last() export const formItem = (scope: Locator, label: string | RegExp) => scope.locator('.el-form-item').filter({ hasText: label }).first() export async function fillFormItem(scope: Locator, label: string | RegExp, value: string) { const control = formItem(scope, label).locator('input:not([type="hidden"]), textarea').first() await expect(control).toBeVisible() await control.fill(value) } export async function chooseSelectOption(page: Page, scope: Locator, label: string | RegExp, option: string | RegExp) { const item = formItem(scope, label) await item.locator('.el-select').click() const dropdown = page.locator('.el-select-dropdown:visible').last() await expect(dropdown).toBeVisible() await dropdown.locator('.el-select-dropdown__item').filter({ hasText: option }).first().click() } export async function confirmMessageBox(page: Page, buttonName: string | RegExp) { const box = page.locator('.el-message-box:visible').last() await expect(box).toBeVisible() await box.getByRole('button', { name: buttonName }).click() } export const tableRowByText = (page: Page, text: string) => page .locator('.el-table__body-wrapper .el-table__row, tbody tr') .filter({ hasText: text }) .first()