library-picturebook-activity/lesingle-creation-frontend/playwright-report/data/5dce8cccaca300faf14a71590b59570c0eeba199.md

185 lines
6.8 KiB
Markdown
Raw Normal View History

# Instructions
- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.
# Test info
- Name: admin\navigation.spec.ts >> 侧边栏导航 >> N-03 菜单点击导航 - 活动管理子菜单
- Location: e2e\admin\navigation.spec.ts:33:3
# Error details
```
TimeoutError: page.waitForSelector: Timeout 15000ms exceeded.
Call log:
- waiting for locator('.layout, .login-container') to be visible
```
# Test source
```ts
513 | status: 200,
514 | contentType: 'application/json',
515 | body: JSON.stringify({ code: 200, message: 'success', data: MOCK_USERS, timestamp: Date.now(), path: '/api/users' }),
516 | })
517 | }
518 | })
519 |
520 | // 租户信息
521 | await page.route('**/api/tenants/my-tenant', async (route) => {
522 | await route.fulfill({
523 | status: 200,
524 | contentType: 'application/json',
525 | body: JSON.stringify({
526 | code: 200,
527 | message: 'success',
528 | data: { id: 2, name: '广东省立中山图书馆', code: TENANT_CODE, tenantType: 'library' },
529 | timestamp: Date.now(),
530 | path: '/api/tenants/my-tenant',
531 | }),
532 | })
533 | })
534 |
535 | // 评审任务列表(评委端)
536 | await page.route('**/api/activities/review**', async (route) => {
537 | await route.fulfill({
538 | status: 200,
539 | contentType: 'application/json',
540 | body: JSON.stringify({
541 | code: 200,
542 | message: 'success',
543 | data: {
544 | list: [
545 | { id: 1, contestName: '少儿绘本创作大赛', totalWorks: 10, reviewedWorks: 5, status: 'in_progress' },
546 | ],
547 | total: 1,
548 | },
549 | timestamp: Date.now(),
550 | }),
551 | })
552 | })
553 |
554 | // 评审规则下拉选项(创建活动页使用)
555 | await page.route('**/api/contests/review-rules/select**', async (route) => {
556 | await route.fulfill({
557 | status: 200,
558 | contentType: 'application/json',
559 | body: JSON.stringify({
560 | code: 200,
561 | message: 'success',
562 | data: [
563 | { id: 1, ruleName: '标准评审规则' },
564 | ],
565 | timestamp: Date.now(),
566 | path: '/api/contests/review-rules/select',
567 | }),
568 | })
569 | })
570 |
571 | // 兜底拦截:防止未 mock 的请求到达真实后端(返回空数据而非 401
572 | await page.route('**/api/**', async (route) => {
573 | const url = route.request().url()
574 | const method = route.request().method()
575 | // 只拦截未被更具体 mock 处理的请求
576 | await route.fulfill({
577 | status: 200,
578 | contentType: 'application/json',
579 | body: JSON.stringify({
580 | code: 200,
581 | message: 'success',
582 | data: method === 'GET' ? { list: [], total: 0, page: 1, pageSize: 10 } : { id: 0 },
583 | timestamp: Date.now(),
584 | path: new URL(url).pathname,
585 | }),
586 | })
587 | })
588 | }
589 |
590 | /**
591 | * 注入登录态到浏览器
592 | * 通过设置 Cookie 模拟已登录状态
593 | */
594 | export async function injectAuthState(page: Page): Promise<void> {
595 | // 先访问页面以便能设置 Cookie
596 | await page.goto('/p/login')
597 |
598 | // 注入 Cookie与 setToken 函数一致path 为 '/'
599 | await page.evaluate((token) => {
600 | document.cookie = `token=${encodeURIComponent(token)}; path=/; max-age=${7 * 24 * 60 * 60}`
601 | }, MOCK_TOKEN)
602 | }
603 |
604 | /**
605 | * 导航到管理端页面(已注入登录态后)
606 | * 等待路由守卫完成和页面渲染
607 | */
608 | export async function navigateToAdmin(page: Page, path: string = ''): Promise<void> {
609 | const targetUrl = `/${TENANT_CODE}${path}`
610 | await page.goto(targetUrl)
611 |
612 | // 等待页面基本加载完成BasicLayout 渲染)
> 613 | await page.waitForSelector('.layout, .login-container', { timeout: 15_000 })
| ^ TimeoutError: page.waitForSelector: Timeout 15000ms exceeded.
614 | }
615 |
616 | /**
617 | * 等待 Ant Design 表格加载完成
618 | */
619 | export async function waitForTable(page: Page): Promise<void> {
620 | await page.waitForSelector('.ant-table', { timeout: 10_000 })
621 | // 等待表格数据加载
622 | await page.waitForSelector('.ant-table-tbody tr', { timeout: 10_000 })
623 | }
624 |
625 | // ==================== 组件预热 ====================
626 |
627 | /** 标记是否已完成组件预热Vite 编译缓存只需触发一次) */
628 | let componentsWarmedUp = false
629 |
630 | /**
631 | * 预热管理端页面组件
632 | * 通过导航到活动列表页触发 Vite 编译,避免首个测试因组件加载慢而失败
633 | */
634 | async function warmupComponents(page: Page): Promise<void> {
635 | if (componentsWarmedUp) return
636 | try {
637 | // 展开活动管理子菜单
638 | const submenu = page.locator('.ant-menu-submenu').filter({ hasText: '活动管理' }).first()
639 | await submenu.click()
640 | await page.waitForTimeout(500)
641 | // 点击活动列表触发组件加载
642 | await submenu.locator('.ant-menu-item').filter({ hasText: '活动列表' }).first().click()
643 | await page.waitForSelector('.contests-page', { timeout: 15_000 })
644 | // 导航回工作台
645 | await page.locator('.ant-menu-item').filter({ hasText: '工作台' }).first().click()
646 | await page.waitForTimeout(500)
647 | componentsWarmedUp = true
648 | } catch {
649 | // 预热失败不影响测试(可能组件已被缓存)
650 | }
651 | }
652 |
653 | // ==================== 扩展 Fixture ====================
654 |
655 | export const test = base.extend<AdminFixtures>({
656 | adminPage: async ({ page }, use) => {
657 | // 设置 API Mock
658 | await setupApiMocks(page)
659 | // 注入登录态
660 | await injectAuthState(page)
661 | // 导航到管理端首页
662 | await navigateToAdmin(page)
663 | // 等待侧边栏加载
664 | await page.waitForSelector('.custom-sider', { timeout: 15_000 })
665 | // 预热组件(首次运行时触发 Vite 编译)
666 | await warmupComponents(page)
667 | await use(page)
668 | },
669 | })
670 |
671 | export { expect }
672 |
```