小Cの已经记不起来的博客

用 Playwright 做端到端测试时处理不稳定网络请求的策略

前阵子给公司一个老系统补端到端测试,本地跑得好好的,一上 CI 就开始抽风,十次能红三次。点开报错一看,清一色的 Test timeout of 30000ms exceeded,再往下翻日志,发现全是卡在等接口上。页面本身没任何毛病,纯粹是测试环境那台机器网络不太行,接口时快时慢。折腾了几天,踩了不少坑,把实测有效的几个策略记一下,省得下次又从头排查一遍。

先说说 networkidle 这个大坑

很多人(包括之前的我)喜欢这么写:

await page.goto('/users', { waitUntil: 'networkidle' });

意思是等网络请求都消停了再往下走。听起来很合理,但实际用起来你会发现,只要页面里有个长轮询、WebSocket 或者定时埋点,networkidle 就永远不会触发,然后你就超时报错吧。另外就算触发了,也只能保证“那一刻”没请求,并不能保证你关心的那个接口真的返回了数据。所以我的建议是:别指望它,直接去等具体的接口。

等接口用 waitForResponse,而且要挂在动作前面

test('用户列表能正常加载', async ({ page }) => {
  const listResp = page.waitForResponse(
    resp => resp.url().includes('/api/users') && resp.status() === 200
  );
  await page.goto('/users');
  await listResp;
  await expect(page.getByText('张三')).toBeVisible();
});

注意 waitForResponse 一定要写在 goto 前面,它只是先把监听挂上。不然页面加载快的时候,接口在你挂监听之前就返回了,然后你就一直等着吧,等到超时都不知道发生了什么。另外如果接口是点了按钮才触发,可以自己加个 timeout,别拿默认的 30 秒去赌。

能 mock 的接口就别真调

端到端测试测的是前端页面的逻辑,不是后端接口对不对,所以大部分数据接口都可以直接拦下来:

await page.route('**/api/users', async route => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ list: [{ id: 1, name: '张三' }] }),
  });
});

mock 的好处不只是快和稳,还能模拟后端根本给不出来的场景,比如返回 500,然后断言页面显示了“加载失败”,这种用例用真实接口反而很难构造。通配符写法没准儿和你的路由对不上,发现拦截不到的话,把实际请求的 url 打印出来对照着改一下就行。

第三方请求直接干掉

老系统页面上总有些历史遗留的东西,统计脚本、埋点、某个挂掉多年的 CDN 字体,这些请求慢起来能把整个页面拖死。我的习惯是加一条全局路由把它们 abort 掉:

await page.route('**/*', route => {
  const url = route.request().url();
  if (url.includes('google-analytics') || url.includes('hm.baidu.com')) {
    return route.abort();
  }
  return route.continue();
});

建议直接写进 fixture 或者 beforeEach 里,省得每个用例都抄一遍。

弱网场景给接口人为加点延迟

有时候你确实需要测弱网下的表现,比如加载态有没有正常出来。可以给指定接口人为加个延迟:

await page.route('**/api/search', async route => {
  await new Promise(r => setTimeout(r, 3000)); // 模拟慢接口
  await route.continue();
});

这比去改全局网络节流靠谱,因为只影响这一个接口,别的请求该多快还是多快。

实在测不过的就交给 retries 兜底

上面都做了,还是会有极少数用例因为各种玄学原因偶发失败。全量重试在配置文件里加一行就行:

export default defineConfig({
  retries: process.env.CI ? 2 : 0,
});

本地失败就让它红,CI 上失败自动再跑两次,两次都挂才真的是有问题。这样报告里会把只跑过一次的用例标成 flaky,记得隔段时间回头看看哪些用例老在 flaky,那个大概率是用例本身写法有问题,不是运气问题。

最后

总结下来就三板斧:别等 networkidle,去等具体接口;能 mock 的不真调;没用的请求全部 abort,剩下实在不行的交给 retries。做完这几件事之后,我们那套测试在 CI 上基本没再因为网络红过了,偶尔红一次,回头一查还真是页面本身的 bug,算是意外收获。

评论

还没有评论。

发表评论

提交后评论将经过自动审核,审核通过后公开展示。

未在播放