Playwright配置SSL证书登录网站及request模块报错问题咨询
Playwright配置SSL客户端证书登录实现方案
问题背景
- 测试需求:登录目标网站时需通过SSL客户端证书完成身份认证,已生成对应
cert、key格式的pem证书文件,需将证书配置到测试代码中实现自动登录逻辑 - 现状:Playwright官方原生支持客户端证书配置的功能尚在开发阶段,尝试社区提供的两种临时方案(动态添加证书、封装fixture注入测试类调用)均运行失败,抛出
request模块未找到的错误 - 原有问题代码如下:
import { test, expect } from '@playwright/test' test('login test @certTest', async ({browser}) => { const fs = require('fs'); const request = require('request'); const ctx = await browser.newContext(); await ctx.route('**/*', (route, req) => { const options = { uri: req.url(), method: req.method(), headers: req.headers(), body: req.postDataBuffer(), timeout: 10000, followRedirect: false, agentOptions: { ca: fs.readFileSync('./certs/user_cert.pem') }, }; let firstTry = true; const handler = function handler(err, resp, data) { if (err) { if (firstTry) { firstTry = false; return request(options, handler); } console.error(`Unable to call ${options.uri}`, err.code, err); return route.abort(); } else { return route.fulfill({ status: resp.statusCode, headers: resp.headers, body: data, }); } }; return request(options, handler); }); const page = await ctx.newPage(); await page.goto('https://my.page.net') const title = page.locator('title') await expect(title).toHaveText('My Title') })
问题排查与修复步骤
先解决
request模块未找到的报错
你代码中引用的request是第三方npm包,既不是Node.js内置模块,也不属于Playwright的自带依赖,运行报错是因为项目中未安装该依赖。如果要沿用原有逻辑,可在项目根目录执行安装命令:npm install request --save-dev
注意:request包已停止官方维护,不推荐长期使用,下方提供无额外依赖的原生实现方案修正证书配置参数错误
原有代码中agentOptions仅配置了ca(CA根证书)参数,SSL客户端身份认证必须同时传入客户端公钥证书、客户端私钥两个必填参数,对应你持有的两个pem文件,缺省会直接导致认证失败。如果你的私钥设置了访问密码,还需要额外传入passphrase参数。可直接运行的稳定实现代码(无额外第三方依赖)
以下代码使用Node.js原生自带的https模块替代已废弃的request包,不需要额外安装依赖,同时补全了客户端证书的必填配置项:
import { test, expect } from '@playwright/test' const fs = require('fs'); const https = require('https'); const path = require('path'); test('login test @certTest', async ({browser}) => { // 提前读取证书文件,避免每个请求重复IO,建议用绝对路径避免工作目录问题 const clientCertConfig = { ca: fs.readFileSync(path.resolve(__dirname, './certs/ca_root.pem')), // 替换为你的CA根证书路径,如不需要可删除 cert: fs.readFileSync(path.resolve(__dirname, './certs/user_cert.pem')), // 替换为你的客户端公钥证书pem路径 key: fs.readFileSync(path.resolve(__dirname, './certs/user_key.pem')), // 替换为你的客户端私钥pem路径 // passphrase: '你的私钥密码' // 私钥有密码时取消注释填写 }; const ctx = await browser.newContext(); // 如不需要拦截静态资源,可缩小路由匹配范围仅拦截接口请求,提升运行速度 await ctx.route('**/*', (route, req) => { const reqUrl = new URL(req.url()); const options = { hostname: reqUrl.hostname, port: reqUrl.port || 443, path: reqUrl.pathname + reqUrl.search, method: req.method(), headers: req.headers(), body: req.postDataBuffer(), timeout: 10000, rejectUnauthorized: true, // 生产环境保持true,测试自签名证书可临时设为false agent: new https.Agent(clientCertConfig) }; let firstTry = true; const sendRequest = () => { const proxyReq = https.request(options, (resp) => { const chunks = []; resp.on('data', (chunk) => chunks.push(chunk)); resp.on('end', () => { route.fulfill({ status: resp.statusCode, headers: resp.headers, body: Buffer.concat(chunks) }); }); }); proxyReq.on('error', (err) => { // 首次请求偶发连接失败时重试一次 if (firstTry) { firstTry = false; return sendRequest(); } console.error(`请求 ${req.url()} 失败:`, err.code, err.message); route.abort(); }); if (options.body) { proxyReq.write(options.body); } proxyReq.end(); }; sendRequest(); }); const page = await ctx.newPage(); await page.goto('https://my.page.net'); const title = page.locator('title'); await expect(title).toHaveText('My Title'); })
内容的提问来源于stack exchange,提问作者Tester123
相关产品推荐
相关产品推荐

