知识库在线

E2E自动化测试实战:搞定431报错、鉴权与反爬,Playwright vs Cypress探讨

2026-09-01

引言

在端到端(E2E)测试中,我们常常会遭遇这样的窘境:脚本在本地跑得行云流水,一上 CI/CD 环境就频繁“翻车”。

  • 🤯 Request Header Too Large (431) 导致页面直接罢工;
  • 🍪 Cookie 和 Session 状态在测试文件间难以共享,重复登录拖垮效率;
  • 🛡️ 遇到防爬策略,自动化脚本被无情拦截;
  • 🐌 API 响应与 UI 渲染 不同步,断言失败如山倒……

这些问题的根源,往往来自网络层限制状态管理混乱安全策略干扰以及异步时序的疏忽


一、💥 网络层突围:解决 “Request Header Too Large” (HTTP 431)

根本原因与通用策略

当服务器(如 Nginx)检测到请求头总大小超过其限制(默认通常为 8KB)时,会返回 431 Request Header Fields Too Large。在测试中,我们无需修改服务器配置,更灵活的方式是在客户端侧“瘦身”:

  • 精简请求头:删除非必要的大字段(如冗长的追踪 ID、调试信息)。
  • 数据迁移:对于 POST / PUT 请求,将大量数据从请求头移至请求体(Body),这更符合 HTTP 规范。

Playwright 方案:page.route 拦截修改

Playwright 的 page.route 允许在请求发出前“劫持”并修改请求对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 拦截所有 /api/** 的请求
await page.route('/api/**', async route => {
const request = route.request();
const headers = { ...request.headers() };

// 策略1:删除不必要的大字段
delete headers['X-Unnecessary-Large-Header'];

// 策略2:若必须携带大量数据,可将其移至请求体(仅适用于 POST/PUT)
// if (request.method() === 'POST') {
// const originalBody = request.postDataJSON();
// // ... 修改 body 后重新设置 ...
// await route.continue({ headers, postData: JSON.stringify(newBody) });
// } else {
// await route.continue({ headers });
// }

// 继续请求,携带“瘦身”后的请求头
await route.continue({ headers });
});

Cypress 方案:cy.intercept 修改请求对象

Cypress 的 cy.intercept 同样能轻松修改请求头,用法更直观。

1
2
3
4
5
6
7
8
9
cy.intercept('GET', '/api/data', (req) => {
// 直接操作 req.headers 对象
delete req.headers['X-Large-Header'];
// 或设置为更小的测试专用 Token
req.headers['Authorization'] = 'Bearer test-token-small';
}).as('getData');

cy.visit('/my-page');
cy.wait('@getData'); // 等待被修改的请求完成

💡 补充:调整服务器配置(如 Nginx 的 large_client_header_buffers)是后端方案,但在测试环境中,通过框架在客户端侧处理更为灵活和常见。


二、🔐 状态管理:Cookie 与 Session 的高效复用

E2E 测试最耗时的操作之一就是“登录”。每个测试都重新登录会成倍拖慢速度,但共享状态又可能导致测试间相互污染。核心思路是:一次登录,全局复用,测试隔离

Playwright 方案:storageState(官方推荐)

Playwright 的 BrowserContext 相当于独立的浏览器会话,天然隔离 Cookie 和 LocalStorage。我们可以将登录后的状态保存为 JSON 文件,并在其他测试中直接加载,彻底跳过登录步骤。

1️⃣ 登录并保存状态(auth.setup.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { chromium } = require('playwright');

(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();

// 执行登录操作
await page.goto('https://example.com/login');
await page.fill('#username', 'myuser');
await page.fill('#password', 'mypass');
await page.click('#login-btn');
await page.waitForURL('**/dashboard');

// 保存状态到文件
await context.storageState({ path: 'auth_state.json' });
await browser.close();
})();

2️⃣ 在其他测试中复用(test_dashboard.js

1
2
3
4
5
6
7
8
9
10
11
12
const { chromium } = require('playwright');

(async () => {
const browser = await chromium.launch();
// 直接加载已保存的状态,跳过登录
const context = await browser.newContext({ storageState: 'auth_state.json' });
const page = await context.newPage();

await page.goto('https://example.com/dashboard');
// ... 执行测试断言 ...
await browser.close();
})();

Cypress 方案:cy.session()(现代标准)

Cypress 12+ 推出的 cy.session() 是管理会话的黄金标准。它能自动缓存、恢复和清理会话状态,并与 Cypress 的测试隔离机制完美协同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// cypress/e2e/dashboard.cy.js
describe('用户功能测试', () => {
beforeEach(() => {
// 如果 'admin-session' 已缓存,则直接恢复;否则执行登录并缓存
cy.session('admin-session', () => {
cy.visit('/login');
cy.get('#username').type('admin');
cy.get('#password').type('123456');
cy.get('#login-btn').click();
cy.url().should('include', '/dashboard');
});
});

it('测试个人资料页', () => {
cy.visit('/profile');
cy.get('h1').should('contain', '我的个人资料');
});

it('测试设置页', () => {
cy.visit('/settings');
cy.get('h1').should('contain', '账户设置');
});
});

💡 补充:Cypress 默认在每个测试前清理 Cookie/Web Storage(testIsolation 机制),而 cy.session() 会在测试开始时自动恢复状态,测试结束后再次清理,确保测试间互不影响。切勿在 afterEach 中手动登出,以免因测试失败导致清理未执行,污染后续测试。


三、🕵️‍♂️ 应对防爬策略:隐藏自动化特征

在测试或采集公开数据时,许多网站会检测 navigator.webdriver 等特征来拦截自动化脚本。我们需要从“伪装”和“行为”两个维度突围。

Playwright:底层隐蔽 + Stealth 插件

Playwright 通过 WebSocket 连接浏览器,比 Selenium 更底层,天生更难被检测。对于高防护网站,可结合社区插件 playwright-stealth 抹除自动化痕迹。

启用 Stealth 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const { chromium } = require('playwright');
const { stealth } = require('playwright-stealth');

(async () => {
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
// 在创建 page 之前应用 stealth 补丁
await stealth(context);
const page = await context.newPage();

await page.goto('https://target-site.com');
// ... 执行操作 ...
await browser.close();
})();

其他实战技巧

  • 模拟真人行为:在点击、滚动等操作间加入随机延迟(如 1~3 秒),或使用 mouse.wheel 模拟平滑滚动,避免机械化的瞬时操作。
  • User-Agent 轮换:在 browser.newContext 中随机传入真实 UA 列表。
  • 请求头补充:通过 page.route 统一修改 Accept-LanguageSec-Fetch-* 等头部,使其更接近真实浏览器。

Cypress:同域策略的天然优势

Cypress 运行在浏览器内部,与待测应用同源,不暴露 navigator.webdriver 等常见自动化特征,对基础防爬有天然免疫力。

拦截第三方追踪:在 cypress.config.js 中使用 blockHosts 屏蔽 Google Analytics 等第三方脚本,减少被指纹追踪的风险。

1
2
3
4
5
6
7
// cypress.config.js
const { defineConfig } = require('cypress');
module.exports = defineConfig({
e2e: {
blockHosts: ['*.google-analytics.com', '*.googletagmanager.com'],
},
});

Mock 数据:对于极度复杂的防爬验证(如验证码),最佳实践不是“破解”,而是使用 cy.intercept 直接 Mock 验证成功的接口响应,从源头绕过。


四、⚡ 性能与稳定性:API 与 UI 的协同优化

E2E 测试慢且不稳定的根源,往往是异步数据加载与 UI 渲染之间的时序问题。解决方案是 智能等待 + API 驱动 + AI 视觉

智能等待:拒绝 sleep,拥抱“条件等待”

错误做法page.waitForTimeout(3000)cy.wait(3000) —— 不仅慢,网络波动时依然会超时报错。

Playwright 最佳实践:Web-First 断言
Playwright 的断言自带自动重试机制,无需显式等待。

1
2
// 自动等待直到元素可见且文本匹配,默认超时 5s
await expect(page.locator('.status')).toHaveText('提交成功');

Cypress 最佳实践:内置重试机制
Cypress 命令和断言均内置重试逻辑。

1
2
// 不断重试获取 '.btn',直到其变为 'enabled' 状态
cy.get('.btn').should('be.enabled');

数据准备:API 驱动 UI 验证(BFF 模式)

不要完全依赖 UI 来准备测试数据(如“通过 UI 注册10个用户”),这极其缓慢。

  • 策略:在 beforeEach 或测试脚本中,直接调用后端 API 创建所需数据(用户、订单等),然后仅在 UI 层验证最终业务流程。
  • 优势:将测试执行时间从分钟级缩短至秒级,且数据可控。

示例(Playwright)

1
2
3
4
5
// 直接调用 API 创建测试用户
const newUser = await request.post('/api/users', { data: { name: 'test' } });
// 再用 UI 登录并验证
await page.goto('/dashboard');
await expect(page.locator('.user-name')).toHaveText('test');

Cypress 中可用 cy.request 实现相同目的。

AI 视觉验证:终结 UI 元素不稳定的假告警

在 AI 测试时代,传统 DOM 选择器(CSS/XPath)常因前端代码重构(如更改 class 名)而失效。

  • AI 视觉验证:利用多模态大模型“看”截图,像人类一样识别页面上的“订单总价”或“提交按钮”,不依赖脆弱的 DOM 结构。
  • 语义匹配:将 API 返回的数据(如价格 99.9)与 UI 截图中的视觉文本(如 ¥99.90)进行语义对齐,允许格式差异,彻底解决格式化导致的断言失败。

框架选择:在实现 AI 视觉验证时,Playwright 是更优的底座。因为 Playwright 原生支持 async/await,可轻松将截图转为 Buffer 并传给 AI 接口;而 Cypress 的异步链式调用和上下文割裂(浏览器 vs Node),会让调用外部 AI 接口变得非常复杂(需借助 cy.task 桥接,代码割裂)。


五、📊 总结与决策(两大框架对比表)

挑战维度 Playwright 最佳实践 Cypress 最佳实践
Header 过大 (431) page.route 拦截并修改请求头 cy.intercept 修改请求对象
鉴权与 Session storageState 保存/加载 JSON 状态文件 cy.session() 缓存与恢复会话
防爬与隐蔽性 playwright-stealth + 行为模拟 + UA 轮换 同域天然隐蔽 + blockHosts 屏蔽第三方
API 与 UI 协同 async/await 编排 API 造数与 UI 验证 cy.request 配合 UI 命令链
AI 视觉验证集成 ⭐⭐⭐⭐⭐(原生异步,极易集成 Node 逻辑) ⭐⭐(需 cy.task 桥接,代码割裂)
等待机制 Web-First 断言(自动重试) 内置重试 + 别名等待(@alias

结语

E2E 测试的本质是用机器模拟真人,但我们要比真人更“聪明”。

  • 通过拦截网络层解决协议报错,
  • 通过复用状态提升执行效率,
  • 通过 API 驱动保证数据稳定性,
  • 并适时引入 AI 视觉 应对 UI 变动,
使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏