
在 k6 浏览器自动化测试中,`page.waitfornavigation()` 无法可靠捕获多步重定向后的最终页面状态,导致 `page.title()` 返回中间页标题;根本原因在于导航超时、上下文默认限制及缺乏对目标页面就绪状态的精准断言。
K6 的 k6/experimental/browser 模块基于 Playwright 内核,但其默认行为(如 waitForNavigation 的隐式超时、上下文级超时继承)与真实浏览器交互存在差异,尤其在涉及多跳重定向(如登录流程中的 /login → /auth → /dashboard)时极易出现“假完成”问题——即导航事件被过早触发,而 DOM 尚未渲染或关键资源未加载。
✅ 推荐解决方案:三重保障机制
1. 用 waitForSelector 替代 waitForNavigation(最可靠)
导航完成 ≠ 页面可用。应等待业务语义明确的元素(如仪表盘标题、用户头像、主内容区 ID)出现在 DOM 中:
// ✅ 正确做法:等待最终页核心元素
await Promise.all([
page.waitForSelector('#dashboard-welcome-title', { timeout: 30000 }),
continueButton2.click(),
]);
console.log(await page.title()); // 此时 title 必为最终页⚠️ 注意:#dashboard-welcome-title 需替换为实际页面中仅在目标页存在且稳定渲染的选择器(避免 class 动态生成或 SSR/CSR 混合导致的竞态)。
2. 显式延长所有超时配置
waitForNavigation 默认超时约 30s,但 K6 上下文(Context)和页面(Page)可能继承更短的全局 timeout。需逐层覆盖:
const browser = chromium.launch({
headless: false,
timeout: '120s', // Browser 实例超时
slowMo: '500ms',
});
const context = browser.newContext({
timeout: '120s', // ✅ 关键:Context 级超时(影响 waitForNavigation 等)
});
const page = context.newPage();
// 同时为单次等待指定超时(防御性编程)
await page.waitForSelector('#final-page-element', { timeout: 45000 });3. 禁用隐式 sleep(),改用条件化等待
sleep(10) 是反模式:既无法保证页面就绪(慢网/高负载下仍失败),又拖慢执行效率。所有等待必须基于可观测状态:
// ❌ 错误:固定休眠
sleep(10);
// ✅ 正确:条件化等待 + 超时兜底
await page.waitForFunction(
() => document.querySelector('#app')?.dataset.ready === 'true',
{ timeout: 60000 }
);? 完整修复示例(整合上述原则)
export async function browser() {
const browser = chromium.launch({
headless: false,
timeout: '120s',
slowMo: '500ms',
});
const context = browser.newContext({ timeout: '120s' }); // ← 关键补充
const page = context.newPage();
await page.goto('http://localhost:5000/', { waitUntil: 'networkidle' });
// Cookie banner
await page.locator('//*[@id="cookies-banner"]/div/div[2]/button').click();
// Company input & continue
await page.locator('#company-input').type('Test1');
await Promise.all([
page.waitForSelector('#login-method-selection-password-login-button', { timeout: 30000 }),
page.locator('#company-login-continue-button').click(),
]);
// Password login
await Promise.all([
page.waitForSelector('#user-name-input', { timeout: 30000 }), // 确保登录表单已加载
page.locator('//*[@id="login-method-selection-password-login-button"]/p').click(),
]);
// Final submit → wait for dashboard element
await page.locator('#user-name-input').type('TestUsername');
await page.locator('#password-input').type('TestPassword');
await Promise.all([
page.waitForSelector('#dashboard-main-content', { timeout: 45000 }), // ← 目标页唯一标识
page.locator('//*[@id="password-login-continue-button"]/p').click(),
]);
console.log('Final page title:', await page.title());
console.log('URL:', page.url());
await page.close();
await browser.close();
}? 总结要点
- 永远优先等待具体 UI 元素,而非依赖 waitForNavigation(它只监听导航事件,不验证页面就绪);
- Context 级 timeout 必须显式设置,否则 waitForNavigation 可能受默认 30s 限制;
- 移除所有 sleep(),用 waitForSelector / waitForFunction 实现精准、可预测的等待;
- 在 CI 环境中,建议添加 console.log(page.url()) 辅助调试重定向路径;
- 若页面含动态 SPA 路由(如 React Router),需配合 waitForFunction 检查 window.location.pathname 或应用状态。
通过以上调整,可彻底解决重定向后 page.title() 返回错误标题的问题,并显著提升测试稳定性与可维护性。










