error-context.md 11 KB

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: regression\23-frontend-e2e.spec.ts >> 前端 UI 完整验收 >> E03: 书籍生成列表页 - 空状态/创建按钮/点击创建
  • Location: regression\23-frontend-e2e.spec.ts:95:7

Error details

Error: expect(locator).toBeVisible() failed

Locator: locator('text=书籍生成').first()
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
  - Expect "toBeVisible" with timeout 15000ms
  - waiting for locator('text=书籍生成').first()

Test source

  1   | /**
  2   |  * L2 回归测试:前端 UI 功能验收 - 完整版
  3   |  *
  4   |  * 测试范围:
  5   |  * 1. 所有页面加载与渲染
  6   |  * 2. 所有可点击元素的交互
  7   |  * 3. TabBar 导航切换
  8   |  *
  9   |  * 风格:Navigate → Locate → Click/Assert → Screenshot
  10  |  */
  11  | import { test, expect } from '@playwright/test';
  12  | 
  13  | const FRONTEND_URL = process.env.FRONTEND_URL || 'http://localhost:5173';
  14  | const SCREENSHOT_DIR = 'test-results/screenshots';
  15  | 
  16  | test.describe('前端 UI 完整验收', () => {
  17  |   // =============================================
  18  |   // E01-E05: TabBar 主页面(首页/生成/书籍/我的/播放)
  19  |   // =============================================
  20  | 
  21  |   test('E01: 首页 - 搜索栏/通知铃铛/最近收听/点击搜索', async ({ page }) => {
  22  |     await page.goto(`${FRONTEND_URL}/#/pages/index/index`, { waitUntil: 'networkidle', timeout: 30000 });
  23  |     await page.waitForTimeout(4000);
  24  | 
  25  |     // 搜索栏(uniapp用text模拟placeholder)
  26  |     const searchBar = page.locator('.search-bar').first();
  27  |     await expect(searchBar).toBeVisible();
  28  | 
  29  |     // 搜索图标
  30  |     const searchIcon = page.locator('.search-icon').first();
  31  |     if (await searchIcon.count() > 0) await expect(searchIcon).toBeVisible();
  32  | 
  33  |     // 搜索占位文字
  34  |     const searchPlaceholder = page.locator('.search-placeholder').first();
  35  |     if (await searchPlaceholder.count() > 0) await expect(searchPlaceholder).toBeVisible();
  36  | 
  37  |     // 通知铃铛
  38  |     const bell = page.locator('.bell-icon').first();
  39  |     if (await bell.count() > 0) await expect(bell).toBeVisible();
  40  | 
  41  |     // 页面主体存在(骨架屏/内容/空状态至少有一个)
  42  |     const hasContent =
  43  |       (await page.locator('.recent-section').count()) > 0 ||
  44  |       (await page.locator('.onboarding').count()) > 0 ||
  45  |       (await page.locator('.empty').count()) > 0 ||
  46  |       (await page.locator('.skeleton-grid').count()) > 0 ||
  47  |       (await page.locator('.album-grid').count()) > 0;
  48  |     expect(hasContent).toBeTruthy();
  49  | 
  50  |     // 点击搜索栏区域
  51  |     await searchBar.click();
  52  |     await page.waitForTimeout(1000);
  53  | 
  54  |     // 验证导航到搜索页
  55  |     await expect(page.locator('#app')).toBeVisible();
  56  | 
  57  |     await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E01-index.png`, fullPage: true }).catch(() => {});
  58  |   });
  59  | 
  60  |   test('E02: 生成音频页 - 文本输入/音色选择/生成按钮/点击交互', async ({ page }) => {
  61  |     await page.goto(`${FRONTEND_URL}/#/pages/create/index`, { waitUntil: 'networkidle', timeout: 30000 });
  62  |     await page.waitForTimeout(3000);
  63  | 
  64  |     // 页面标题
  65  |     await expect(page.locator('text=开始生成').first()).toBeVisible();
  66  | 
  67  |     // 文本输入框(uniapp textarea)
  68  |     const textarea = page.locator('textarea').first();
  69  |     if (await textarea.count() > 0) {
  70  |       await expect(textarea).toBeVisible();
  71  |       await textarea.click();
  72  |       await textarea.fill('测试文本');
  73  |       await page.waitForTimeout(300);
  74  |     }
  75  | 
  76  |     // AI 生成内容按钮
  77  |     const aiBtn = page.locator('text=AI 生成内容').first();
  78  |     if (await aiBtn.count() > 0) await expect(aiBtn).toBeVisible();
  79  | 
  80  |     // 音色选择区卡片
  81  |     const voiceSection = page.locator('text=选择音色').first();
  82  |     if (await voiceSection.count() > 0) await expect(voiceSection).toBeVisible();
  83  | 
  84  |     // 参数调节区卡片
  85  |     const paramsSection = page.locator('text=参数调节').first();
  86  |     if (await paramsSection.count() > 0) await expect(paramsSection).toBeVisible();
  87  | 
  88  |     // 生成音频按钮
  89  |     const generateBtn = page.locator('text=开始生成').last();
  90  |     await expect(generateBtn).toBeVisible();
  91  | 
  92  |     await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E02-create.png`, fullPage: true }).catch(() => {});
  93  |   });
  94  | 
  95  |   test('E03: 书籍生成列表页 - 空状态/创建按钮/点击创建', async ({ page }) => {
  96  |     await page.goto(`${FRONTEND_URL}/#/pages/book-generator/index`, { waitUntil: 'networkidle', timeout: 30000 });
  97  |     await page.waitForTimeout(3000);
  98  | 
> 99  |     await expect(page.locator('text=书籍生成').first()).toBeVisible();
      |                                                     ^ Error: expect(locator).toBeVisible() failed
  100 | 
  101 |     // 创建新书籍按钮
  102 |     const createBtn = page.locator('text=创建新书籍').first();
  103 |     if (await createBtn.count() > 0) await expect(createBtn).toBeVisible();
  104 | 
  105 |     // 空状态或列表(更宽松的检查)
  106 |     const hasContent =
  107 |       (await page.locator('text=暂无书籍').count()) > 0 ||
  108 |       (await page.locator('text=创建第一本').count()) > 0 ||
  109 |       (await page.locator('.item').count()) > 0 ||
  110 |       (await page.locator('.album-card').count()) > 0 ||
  111 |       (await page.locator('.book-card').count()) > 0;
  112 |     expect(hasContent).toBeTruthy();
  113 | 
  114 |     await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E03-book-list.png`, fullPage: true }).catch(() => {});
  115 |   });
  116 | 
  117 |   test('E04: 我的页面 - 用户卡片/菜单项点击/退出登录', async ({ page }) => {
  118 |     await page.goto(`${FRONTEND_URL}/#/pages/mine/index`, { waitUntil: 'networkidle', timeout: 30000 });
  119 |     await page.waitForTimeout(3000);
  120 | 
  121 |     await expect(page.locator('#app')).toBeVisible();
  122 | 
  123 |     // 点击"书籍生成"按钮,验证跳转到书籍生成页
  124 |     const bookGenBtn = page.locator('text=书籍生成').first();
  125 |     if (await bookGenBtn.count() > 0) {
  126 |       await bookGenBtn.click();
  127 |       await page.waitForTimeout(2000);
  128 |       await expect(page.locator('#app')).toBeVisible();
  129 |       const currentUrl = page.url();
  130 |       console.log('书籍生成跳转后URL:', currentUrl);
  131 |       // 验证跳转到书籍生成相关页面
  132 |       expect(currentUrl.includes('book-generator')).toBeTruthy();
  133 |       // 截图
  134 |       await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E04-bookgenerator.png`, fullPage: true }).catch(() => {});
  135 |     }
  136 | 
  137 |     // 点击"历史记录"按钮
  138 |     await page.goto(`${FRONTEND_URL}/#/pages/mine/index`, { waitUntil: 'networkidle', timeout: 30000 });
  139 |     await page.waitForTimeout(2000);
  140 |     const historyBtn = page.locator('text=历史记录').first();
  141 |     if (await historyBtn.count() > 0) {
  142 |       await historyBtn.click();
  143 |       await page.waitForTimeout(2000);
  144 |       await expect(page.locator('#app')).toBeVisible();
  145 |       console.log('历史记录跳转后URL:', page.url());
  146 |       await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E04-history.png`, fullPage: true }).catch(() => {});
  147 |     }
  148 | 
  149 |     // 点击"会员中心"按钮
  150 |     await page.goto(`${FRONTEND_URL}/#/pages/mine/index`, { waitUntil: 'networkidle', timeout: 30000 });
  151 |     await page.waitForTimeout(2000);
  152 |     const memberBtn = page.locator('text=会员中心').first();
  153 |     if (await memberBtn.count() > 0) {
  154 |       await memberBtn.click();
  155 |       await page.waitForTimeout(2000);
  156 |       await expect(page.locator('#app')).toBeVisible();
  157 |       console.log('会员中心跳转后URL:', page.url());
  158 |       await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E04-member.png`, fullPage: true }).catch(() => {});
  159 |     }
  160 | 
  161 |     // 点击"设置"按钮
  162 |     await page.goto(`${FRONTEND_URL}/#/pages/mine/index`, { waitUntil: 'networkidle', timeout: 30000 });
  163 |     await page.waitForTimeout(2000);
  164 |     const settingsBtn = page.locator('text=设置').first();
  165 |     if (await settingsBtn.count() > 0) {
  166 |       await settingsBtn.click();
  167 |       await page.waitForTimeout(2000);
  168 |       await expect(page.locator('#app')).toBeVisible();
  169 |       console.log('设置跳转后URL:', page.url());
  170 |       await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E04-settings.png`, fullPage: true }).catch(() => {});
  171 |     }
  172 | 
  173 |     // 退出登录按钮
  174 |     await page.goto(`${FRONTEND_URL}/#/pages/mine/index`, { waitUntil: 'networkidle', timeout: 30000 });
  175 |     await page.waitForTimeout(2000);
  176 |     const logoutBtn = page.locator('text=退出登录').first();
  177 |     if (await logoutBtn.count() > 0) {
  178 |       await logoutBtn.click();
  179 |       await page.waitForTimeout(1000);
  180 |       console.log('退出登录后URL:', page.url());
  181 |     }
  182 | 
  183 |     await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E04-mine.png`, fullPage: true }).catch(() => {});
  184 |   });
  185 | 
  186 |   test('E05: 播放器页面 - 播放控件/播放列表/音频操作', async ({ page }) => {
  187 |     await page.goto(`${FRONTEND_URL}/#/pages/player/index`, { waitUntil: 'networkidle', timeout: 30000 });
  188 |     await page.waitForTimeout(3000);
  189 | 
  190 |     await expect(page.locator('#app')).toBeVisible();
  191 | 
  192 |     // 播放器UI存在
  193 |     const hasPlayerUI =
  194 |       (await page.locator('text=正在播放').count()) > 0 ||
  195 |       (await page.locator('text=播放列表').count()) > 0 ||
  196 |       (await page.locator('text=暂无音频').count()) > 0 ||
  197 |       (await page.locator('.player').count()) > 0;
  198 |     expect(hasPlayerUI || true).toBeTruthy();
  199 |