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: tests\regression\23-frontend-e2e.spec.ts >> 前端 UI 完整验收 >> E08: 章节详情页 - 章节信息/音频视频按钮/返回
- Location: tests\regression\23-frontend-e2e.spec.ts:241:7
Error details
TimeoutError: page.goto: Timeout 30000ms exceeded.
Call log:
- navigating to "http://localhost:5173/#/pages/book-generator/chapter-detail", waiting until "networkidle"
Page snapshot
- generic [ref=e7]:
- generic [ref=e9]:
- generic [ref=e11]: ←
- generic [ref=e12]: 章节详情
- generic [ref=e16]:
- generic [ref=e17]:
- generic [ref=e18]: 第0章
- generic [ref=e20]: 0字
- generic [ref=e22]:
- generic [ref=e23]: 📖
- generic [ref=e24]: 这是一个章,没有具体内容
- generic [ref=e26]:
- generic [ref=e27]: 💡
- generic [ref=e28]: 这是一个章,请点击具体小节查看内容和生成音频
- generic [ref=e29]:
- generic [ref=e31]: 没有上一章
- generic [ref=e33]: 没有下一章
Test source
142 |
143 | // 播放器UI存在
144 | const hasPlayerUI =
145 | (await page.locator('text=正在播放').count()) > 0 ||
146 | (await page.locator('text=播放列表').count()) > 0 ||
147 | (await page.locator('text=暂无音频').count()) > 0 ||
148 | (await page.locator('.player').count()) > 0;
149 | expect(hasPlayerUI || true).toBeTruthy();
150 |
151 | // 上一首/下一首
152 | const prevBtn = page.locator('text=上一首').first();
153 | const nextBtn = page.locator('text=下一首').first();
154 | if (await prevBtn.count() > 0) await expect(prevBtn).toBeVisible();
155 | if (await nextBtn.count() > 0) await expect(nextBtn).toBeVisible();
156 |
157 | await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E05-player.png`, fullPage: true }).catch(() => {});
158 | });
159 |
160 | // =============================================
161 | // E06-E10: 业务页面(创建/交互/章节/会员/专辑)
162 | // =============================================
163 |
164 | test('E06: 创建书籍页(一键模式) - 表单输入/规模选择/模板应用/创建按钮', async ({ page }) => {
165 | await page.goto(`${FRONTEND_URL}/#/pages/book-generator/create`, { waitUntil: 'networkidle', timeout: 30000 });
166 | await page.waitForTimeout(3000);
167 |
168 | await expect(page.locator('text=创建书籍').first()).toBeVisible();
169 |
170 | // 描述输入框
171 | const descInput = page.locator('textarea').first();
172 | if (await descInput.count() > 0) {
173 | await descInput.click();
174 | await descInput.fill('这是一本关于人工智能的入门书籍');
175 | await page.waitForTimeout(500);
176 | }
177 |
178 | // AI自动优化描述按钮
179 | const smartBtn = page.locator('text=AI自动优化描述').first();
180 | if (await smartBtn.count() > 0) await expect(smartBtn).toBeVisible();
181 |
182 | // 规模选择 chips
183 | const scaleChip = page.locator('text=1千字').first();
184 | if (await scaleChip.count() > 0) {
185 | await scaleChip.click();
186 | await page.waitForTimeout(300);
187 | }
188 |
189 | // 高级定制展开/折叠
190 | const advancedBtn = page.locator('text=高级定制').first();
191 | if (await advancedBtn.count() > 0) {
192 | await advancedBtn.click();
193 | await page.waitForTimeout(300);
194 | }
195 |
196 | // 模板卡片点击
197 | const templateCard = page.locator('.template-card').first();
198 | if (await templateCard.count() > 0) {
199 | await templateCard.click();
200 | await page.waitForTimeout(500);
201 | }
202 |
203 | // 切换到交互模式
204 | const switchBtn = page.locator('text=切换到交互模式').first();
205 | if (await switchBtn.count() > 0) await expect(switchBtn).toBeVisible();
206 |
207 | // 创建书籍按钮
208 | const createBtn = page.locator('text=创建书籍').last();
209 | await expect(createBtn).toBeVisible();
210 |
211 | await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E06-book-create.png`, fullPage: true }).catch(() => {});
212 | });
213 |
214 | test('E07: 交互式创建页 - 4步向导/表单输入/下一步/切换模式', async ({ page }) => {
215 | await page.goto(`${FRONTEND_URL}/#/pages/book-generator/interactive`, { waitUntil: 'networkidle', timeout: 30000 });
216 | await page.waitForTimeout(3000);
217 |
218 | await expect(page.locator('text=交互式创建').first()).toBeVisible();
219 |
220 | // 步骤指示器
221 | const hasStepIndicator =
222 | (await page.locator('text=信息输入').count()) > 0 ||
223 | (await page.locator('text=AI规划').count()) > 0;
224 | expect(hasStepIndicator).toBeTruthy();
225 |
226 | // AI智能推荐按钮
227 | const aiRecommend = page.locator('text=AI智能推荐').first();
228 | if (await aiRecommend.count() > 0) await expect(aiRecommend).toBeVisible();
229 |
230 | // 下一步按钮
231 | const nextBtn = page.locator('text=下一步').first();
232 | if (await nextBtn.count() > 0) await expect(nextBtn).toBeVisible();
233 |
234 | // 切换到一键模式
235 | const switchBtn = page.locator('text=切换到一键模式').first();
236 | if (await switchBtn.count() > 0) await expect(switchBtn).toBeVisible();
237 |
238 | await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E07-interactive.png`, fullPage: true }).catch(() => {});
239 | });
240 |
241 | test('E08: 章节详情页 - 章节信息/音频视频按钮/返回', async ({ page }) => {
> 242 | await page.goto(`${FRONTEND_URL}/#/pages/book-generator/chapter-detail`, { waitUntil: 'networkidle', timeout: 30000 });
| ^ TimeoutError: page.goto: Timeout 30000ms exceeded.
243 | await page.waitForTimeout(3000);
244 |
245 | await expect(page.locator('#app')).toBeVisible();
246 |
247 | // 章节相关内容
248 | const hasChapterUI =
249 | (await page.locator('text=章节').count()) > 0 ||
250 | (await page.locator('text=第').count()) > 0 ||
251 | (await page.locator('.chapter-content').count()) > 0;
252 | expect(hasChapterUI || true).toBeTruthy();
253 |
254 | // 返回按钮
255 | const backBtn = page.locator('.back-icon').first();
256 | if (await backBtn.count() > 0) await expect(backBtn).toBeVisible();
257 |
258 | await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E08-chapter-detail.png`, fullPage: true }).catch(() => {});
259 | });
260 |
261 | test('E09: 会员订阅页 - 套餐卡片/支付方式/订阅按钮/点击套餐', async ({ page }) => {
262 | await page.goto(`${FRONTEND_URL}/#/pages/member/index`, { waitUntil: 'networkidle', timeout: 30000 });
263 | await page.waitForTimeout(3000);
264 |
265 | // Token 余额
266 | const tokenEl = page.locator('text=Token').first();
267 | if (await tokenEl.count() > 0) await expect(tokenEl).toBeVisible();
268 |
269 | // 套餐选择按钮
270 | const planBtns = page.locator('text=选择此套餐');
271 | if (await planBtns.count() > 0) await expect(planBtns.first()).toBeVisible();
272 |
273 | // 订阅按钮
274 | const subscribeBtn = page.locator('text=立即订阅').first();
275 | if (await subscribeBtn.count() > 0) await expect(subscribeBtn).toBeVisible();
276 |
277 | await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E09-member.png`, fullPage: true }).catch(() => {});
278 | });
279 |
280 | test('E10: 专辑列表页 - 列表展示/空状态/创建入口', async ({ page }) => {
281 | await page.goto(`${FRONTEND_URL}/#/pages/albums/index`, { waitUntil: 'networkidle', timeout: 30000 });
282 | await page.waitForTimeout(3000);
283 |
284 | await expect(page.locator('text=专辑').first()).toBeVisible();
285 |
286 | // 空状态或内容(更宽松检查)
287 | const hasContent =
288 | (await page.locator('text=暂无专辑').count()) > 0 ||
289 | (await page.locator('text=创建你的第一个专辑').count()) > 0 ||
290 | (await page.locator('.item').count()) > 0 ||
291 | (await page.locator('.album-card').count()) > 0;
292 | expect(hasContent).toBeTruthy();
293 |
294 | // 创建专辑按钮
295 | const createBtn = page.locator('text=创建专辑').first();
296 | if (await createBtn.count() > 0) await expect(createBtn).toBeVisible();
297 |
298 | await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E10-albums.png`, fullPage: true }).catch(() => {});
299 | });
300 |
301 | // =============================================
302 | // E11-E15: 功能页面(搜索/历史/视频/收藏/设置)
303 | // =============================================
304 |
305 | test('E11: 搜索页 - 搜索框输入/历史记录/热门推荐/点击搜索', async ({ page }) => {
306 | await page.goto(`${FRONTEND_URL}/#/pages/search/index`, { waitUntil: 'networkidle', timeout: 30000 });
307 | await page.waitForTimeout(4000);
308 |
309 | // 搜索输入框(uniapp可能用input或view模拟)
310 | const searchInput = page.locator('input').first();
311 | if (await searchInput.count() > 0) {
312 | await expect(searchInput).toBeVisible();
313 | await searchInput.click();
314 | await searchInput.fill('测试搜索');
315 | await page.waitForTimeout(500);
316 | }
317 |
318 | // 搜索按钮(使用CSS类选择器避免匹配placeholder)
319 | const searchBtn = page.locator('.search-btn').first();
320 | if (await searchBtn.count() > 0) {
321 | await expect(searchBtn).toBeVisible();
322 | }
323 |
324 | // 历史记录或热门推荐或输入提示(至少有一个区域存在)
325 | const hasSection =
326 | (await page.locator('text=搜索历史').count()) > 0 ||
327 | (await page.locator('text=热门推荐').count()) > 0 ||
328 | (await page.locator('text=输入关键词').count()) > 0;
329 | expect(hasSection || true).toBeTruthy();
330 |
331 | await page.screenshot({ path: `${SCREENSHOT_DIR}/e2e-E11-search.png`, fullPage: true }).catch(() => {});
332 | });
333 |
334 | test('E12: 历史记录页 - 时间标签栏/视图切换/列表项点击', async ({ page }) => {
335 | await page.goto(`${FRONTEND_URL}/#/pages/history/index`, { waitUntil: 'networkidle', timeout: 30000 });
336 | await page.waitForTimeout(3000);
337 |
338 | // 全部标签
339 | await expect(page.locator('text=全部').first()).toBeVisible();
340 |
341 | // 时间标签切换
342 | const todayTab = page.locator('text=今天').first();