import { test, expect } from '@playwright/test'; /** * 人才测评系统 — 端到端测试 * * Generator → 录制操作 * Planner → 编排测试结构 (describe + test + expect) * Healer → 自动重试 + Trace 快照 (通过 playwright.config.ts 配置) */ const ADMIN = { username: 'admin', password: 'admin123' }; // ── 辅助函数 ── /** 通过 native setter 触发 React onChange */ async function fillReactInput(page: any, selector: string, text: string) { await page.waitForSelector(selector, { timeout: 10000 }); await page.evaluate(({ sel, txt }: { sel: string; txt: string }) => { const el = document.querySelector(sel); if (!el) return; const tag = el.tagName.toLowerCase(); const setter = Object.getOwnPropertyDescriptor( tag === 'textarea' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype, 'value' )?.set; setter?.call(el, txt); el.dispatchEvent(new Event('input', { bubbles: true })); }, { sel: selector, txt: text }); } /** 等待 spinner 消失 */ async function waitForIdle(page: any) { await page.waitForFunction(() => !document.querySelector('.animate-spin'), { timeout: 90000 }).catch(() => {}); await page.waitForTimeout(1500); } /** 关闭可能存在的确认弹窗 */ async function dismissModal(page: any) { await page.evaluate(() => { document.querySelectorAll('[class*="fixed"][class*="inset-0"]').forEach(el => { const btn = Array.from(el.querySelectorAll('button')).find(b => b.textContent?.includes('继续答题') ); if (btn) btn.click(); }); }); } // ══════════════════════════════════════════ // 测试套件 // ══════════════════════════════════════════ test.describe('人才测评系统 — 端到端验证', () => { // ── 1. 登录 ── test.describe('1. 登录', () => { test('admin 登录成功,页面跳转到工作台', async ({ page }) => { await page.goto('/login'); await page.waitForTimeout(1000); await expect(page.locator('input[type="text"]').first()).toBeVisible(); await page.locator('input[type="text"]').first().fill(ADMIN.username); await page.locator('input[type="password"]').first().fill(ADMIN.password); await page.locator('button[type="submit"]').click(); // 登录成功后 URL 不再是 /login await page.waitForURL('**/'); expect(page.url()).not.toContain('/login'); }); test('错误密码显示错误提示', async ({ page }) => { await page.goto('/login'); await page.waitForTimeout(500); await page.locator('input[type="text"]').first().fill('admin'); await page.locator('input[type="password"]').first().fill('wrongpass'); await page.locator('button[type="submit"]').click(); await page.waitForTimeout(2000); // 应该还停留在登录页且有错误提示 expect(page.url()).toContain('/login'); }); }); // ── 2. 考核模板 ── test.describe('2. 考核模板', () => { test.beforeEach(async ({ page }) => { await page.goto('/login'); await page.waitForTimeout(500); await page.locator('input[type="text"]').first().fill(ADMIN.username); await page.locator('input[type="password"]').first().fill(ADMIN.password); await page.locator('button[type="submit"]').click(); await page.waitForURL('**/'); }); test('两个考核模板均可见', async ({ page }) => { await page.goto('/assessment'); await page.waitForTimeout(3000); const techTpl = page.locator('button:has-text("AI协作技巧-对话测评")'); const nonTechTpl = page.locator('button:has-text("AI协作-非技术人员测评")'); await expect(techTpl.first()).toBeVisible({ timeout: 10000 }); await expect(nonTechTpl.first()).toBeVisible({ timeout: 10000 }); }); test('选择模板后显示开始评估按钮', async ({ page }) => { await page.goto('/assessment'); await page.waitForTimeout(3000); await page.locator('button:has-text("AI协作技巧-对话测评")').first().click(); await page.waitForTimeout(500); const startBtn = page.locator('button:has-text("开始专业评估")'); await expect(startBtn).toBeVisible({ timeout: 5000 }); }); }); // ── 3. 选择题答题 ── test.describe('3. 选择题答题', () => { test('点击选择后确认答案按钮可用', async ({ page }) => { // 登录 await page.goto('/login'); await page.waitForTimeout(500); await page.locator('input[type="text"]').first().fill(ADMIN.username); await page.locator('input[type="password"]').first().fill(ADMIN.password); await page.locator('button[type="submit"]').click(); await page.waitForURL('**/'); // 开始考核 await page.goto('/assessment'); await page.waitForTimeout(2000); await page.locator('button:has-text("AI协作技巧-对话测评")').first().click(); await page.waitForTimeout(500); await page.locator('button:has-text("开始专业评估")').first().click(); // 等题目 for (let i = 0; i < 60; i++) { const text = await page.textContent('body').catch(() => ''); if (text.includes('问题 ') || text.includes('Question ')) break; await new Promise(r => setTimeout(r, 2000)); } await waitForIdle(page); // 检查是否是选择题 const hasChoice = await page.evaluate(() => { return document.querySelectorAll('button.w-full.text-left.px-5.py-4').length > 0; }); if (hasChoice) { await dismissModal(page); // 用 evaluate 点击选项 await page.evaluate(() => { const opts = Array.from(document.querySelectorAll('button.w-full.text-left.px-5.py-4')) .filter(b => /^[A-D]/.test(b.textContent || '')); if (opts.length > 0) (opts[0] as HTMLButtonElement).click(); }); await page.waitForTimeout(500); const confirmBtn = page.locator('button:has-text("确认答案")'); await expect(confirmBtn).toBeEnabled({ timeout: 5000 }); } else { // 如果是简答题,测试 textarea 可见 const ta = page.locator('textarea'); await expect(ta.first()).toBeVisible({ timeout: 5000 }); } }); }); // ── 4. 简答题输入 ── test.describe('4. 简答题输入', () => { test('简答题 textarea 可输入并发送', async ({ page }) => { // 登录 + 开始考核 await page.goto('/login'); await page.waitForTimeout(500); await page.locator('input[type="text"]').first().fill(ADMIN.username); await page.locator('input[type="password"]').first().fill(ADMIN.password); await page.locator('button[type="submit"]').click(); await page.waitForURL('**/'); await page.goto('/assessment'); await page.waitForTimeout(2000); await page.locator('button:has-text("AI协作技巧-对话测评")').first().click(); await page.waitForTimeout(500); await page.locator('button:has-text("开始专业评估")').first().click(); for (let i = 0; i < 60; i++) { const text = await page.textContent('body').catch(() => ''); if (text.includes('问题 ') || text.includes('Question ')) break; await new Promise(r => setTimeout(r, 2000)); } await waitForIdle(page); await dismissModal(page); // 检查如果是简答题 const hasSA = await page.evaluate(() => { const ta = document.querySelector('textarea'); return ta !== null && ta.offsetParent !== null; }); if (hasSA) { await fillReactInput(page, 'textarea', '端到端测试回答 — 验证Playwright Planner编排功能'); await page.waitForTimeout(500); // 发送按钮应可用 const sendBtn = page.locator('button:has(svg.lucide-send)'); await expect(sendBtn.last()).toBeEnabled({ timeout: 5000 }); } else { // 选择题——选一个 await page.evaluate(() => { const opts = Array.from(document.querySelectorAll('button.w-full.text-left.px-5.py-4')) .filter(b => /^[A-D]/.test(b.textContent || '')); if (opts.length > 0) (opts[0] as HTMLButtonElement).click(); }); await page.waitForTimeout(500); const confirmBtn = page.locator('button:has-text("确认答案")'); await expect(confirmBtn).toBeEnabled({ timeout: 5000 }); } }); }); // ── 5. 设置页验证 ── test.describe('5. 设置页 — 测评模板', () => { test('设置页有测评模板 Tab', async ({ page }) => { await page.goto('/login'); await page.waitForTimeout(500); await page.locator('input[type="text"]').first().fill(ADMIN.username); await page.locator('input[type="password"]').first().fill(ADMIN.password); await page.locator('button[type="submit"]').click(); await page.waitForURL('**/'); await page.goto('/settings'); await page.waitForTimeout(3000); const templateTab = page.locator('button:has-text("测评模板")'); await expect(templateTab).toBeVisible({ timeout: 5000 }); }); test('测评模板 Tab 可点击', async ({ page }) => { await page.goto('/login'); await page.waitForTimeout(500); await page.locator('input[type="text"]').first().fill(ADMIN.username); await page.locator('input[type="password"]').first().fill(ADMIN.password); await page.locator('button[type="submit"]').click(); await page.waitForURL('**/'); await page.goto('/settings'); await page.waitForTimeout(3000); const templateTab = page.locator('button:has-text("测评模板")'); if (await templateTab.isVisible().catch(() => false)) { await templateTab.click(); await page.waitForTimeout(2000); // 点开模板列表应该能看到模板 const tplList = page.locator('text=AI协作技巧-对话测评'); await expect(tplList.first()).toBeVisible({ timeout: 5000 }); } }); }); });