// e2e 辅助:根 .env 凭据/环境读取、UI 登录、DB 直种夹具(seed-fixture.cjs)。 import { execSync } from 'node:child_process' import { existsSync, readFileSync } from 'node:fs' import { join } from 'node:path' import { fileURLToPath } from 'node:url' import type { Page } from '@playwright/test' import { expect } from '@playwright/test' const repoRoot = fileURLToPath(new URL('../../..', import.meta.url)) export interface AdminCredentials { account: string password: string } function rootEnv(): Map { const envPath = join(repoRoot, '.env') if (!existsSync(envPath)) { throw new Error(`[admin e2e] 仓库根 .env 不存在(${envPath})`) } const vars = new Map() for (const line of readFileSync(envPath, 'utf-8').split(/\r?\n/)) { const match = /^([A-Z0-9_]+)=(.*)$/.exec(line.trim()) if (match) vars.set(match[1], match[2]) } return vars } export function adminCredentials(): AdminCredentials { const vars = rootEnv() const account = vars.get('ADMIN_ACCOUNT') ?? 'admin' const password = vars.get('ADMIN_PASSWORD') ?? 'admin-secret-change-me' return { account, password } } /** ov 入口基址(公开嵌入 feed 断言;缺省同根 .env.example PORT_OV=8081) */ export function ovBaseUrl(): string { return (rootEnv().get('OV_API_URL') ?? 'http://127.0.0.1:8081').replace(/\/+$/, '') } /** UI 登录(真实后端):填账号密码 → 落到 /albums/manage */ export async function loginAsAdmin(page: Page): Promise { const { account, password } = adminCredentials() await page.goto('/#/auth/login') await page.getByPlaceholder('请输入账号').fill(account) await page.getByPlaceholder('请输入密码', { exact: false }).fill(password) await page.getByRole('button', { name: '登录' }).click() await expect(page).toHaveURL(/\/albums\/manage/, { timeout: 20_000 }) } /** 直种夹具(seed-fixture.cjs):staff 账号 / 相册种子照片 / 治理批量照片 / * 水印旅程真实对象照片 / 水印任务直驱(详见 seed-fixture.cjs 文件头) */ export function seedFixture( mode: 'staff' | 'photos' | 'gov-photos' | 'wm-photos' | 'watermark-run', ...args: string[] ): void { const vars = rootEnv() const databaseUrl = vars.get('DATABASE_URL_CN') if (!databaseUrl) { throw new Error('[admin e2e] 根 .env 缺少 DATABASE_URL_CN') } // @photo/db-cn / @photo/storage 绝对路径:apps/admin 不依赖这些包,借 // services/api 的 node_modules(node 从 e2e 目录解析不到 workspace 链接) const dbCn = join(repoRoot, 'services', 'api', 'node_modules', '@photo', 'db-cn') const storage = join(repoRoot, 'services', 'api', 'node_modules', '@photo', 'storage') // 全量根 .env 注入(S3_* 供水印对象直写与 worker 直驱;process.env 覆盖 // 优先级更高——playwright 进程自身的环境变量优先于文件值) execSync(`node seed-fixture.cjs ${mode} ${args.join(' ')}`, { cwd: fileURLToPath(new URL('.', import.meta.url)), stdio: 'inherit', shell: true, env: { ...Object.fromEntries(vars), ...process.env, PHOTO_DB_CN: dbCn, PHOTO_STORAGE: storage, DATABASE_URL_CN: databaseUrl } }) }