在Playwright测试中使用nock模拟Node应用API请求失效求助
我正在为Node应用编写首个Playwright截图测试,应用会发起服务端API请求,希望用nock模拟API响应,让测试稳定、可预测且能离线运行。之前在mocha+supertest的旧测试套件里成功用过nock,但新的Playwright测试里nock完全不起作用,推测原因是Node应用是独立进程启动的。
旧的supertest成功测试代码
const supertest = require("supertest"); const app = setupMyEntireNodeExpressApp(); nock("https://my-api.com").get("/").reply(200, { fakeData:true }); supertest(app).get("/").expect(200).end((err, response) => { doSomething(); }) //让supertest启动Node应用并发起请求
nock不生效的Playwright测试代码(myTest.spec.js)
const { test, expect } = require("@playwright/test"); const http = require("http"); const app = setupMyEntireNodeExpressApp(); test("My test", async ({ page, context }) => { nock("https://my-api.com").get("/").reply(200, { fakeData:true }); http.createServer(app).listen(); //启动Node应用 await page.goto("/"); //通过Playwright发起请求 await expect(page).toHaveScreenshot("myTest.png", { fullPage: true }); //通过Playwright截图 });
运行测试时,nock完全不生效,应用还是会调用真实的https://my-api.com接口。我推测问题出在nock运行在myTest.spec.js进程里,但createServer启动的应用是独立进程,二者不共享nock的拦截规则。
补充说明:我已经在playwright.config.js里设置了webServer.reuseExistingServer: false,因为是从测试文件启动Node应用;也试过移除http.createServer(app).listen(),改用webServer.reuseExistingServer: true和webServer.command: "node app.js"让Playwright启动服务器,但问题依旧——显然是myTest.spec.js和node app.js分属不同进程,nock的拦截规则没法跨进程生效。
旧测试里nock能生效,是因为把Node应用对象传给了supertest(app).request,应用和测试代码运行在同一个进程里。我希望能类似地把应用对象传给Playwright,但查了文档没找到相关选项。想问问有没有简便的解决办法?比如用createServer的其他变体启动应用?
方案1:让Node应用和测试代码运行在同一进程
核心思路是不启动独立的Node进程,直接在测试进程内启动HTTP服务器,让Playwright访问本地服务器地址。这样nock的拦截规则会直接作用于同一进程内的应用请求。
修改后的测试代码:
const { test, expect } = require("@playwright/test"); const http = require("http"); const app = setupMyEntireNodeExpressApp(); test("My test", async ({ page }) => { // 在测试进程内启动服务器,监听系统分配的随机端口 const server = http.createServer(app); await new Promise(resolve => server.listen(0, resolve)); const baseUrl = `http://localhost:${server.address().port}`; // 此时nock和应用同进程,拦截规则生效 nock("https://my-api.com") .get("/") .reply(200, { fakeData: true }); try { await page.goto(baseUrl); await expect(page).toHaveScreenshot("myTest.png", { fullPage: true }); } finally { // 测试结束后关闭服务器 await new Promise(resolve => server.close(resolve)); } });
方案2:在应用的测试模式中加载nock规则
如果必须让应用运行在独立进程,可以在应用代码中添加测试模式判断,直接在应用进程内启动nock拦截。
步骤1:修改应用代码
// app.js if (process.env.NODE_ENV === "test") { const nock = require("nock"); // 加载测试所需的拦截规则,也可以从外部文件导入 nock("https://my-api.com") .get("/") .reply(200, { fakeData: true }); }
步骤2:配置Playwright启动命令
// playwright.config.js module.exports = { webServer: { command: "NODE_ENV=test node app.js", reuseExistingServer: false, }, };
方案3:用Playwright自带的网络拦截替代nock
Playwright内置了网络拦截功能,无需依赖nock,直接拦截应用的API请求(包括前端和服务端发起的请求,服务端请求需配置代理)。
示例代码:
const { test, expect } = require("@playwright/test"); test("My test", async ({ page, context }) => { // 拦截所有指向目标API的请求,返回模拟数据 await context.route("https://my-api.com/*", async (route) => { await route.fulfill({ status: 200, json: { fakeData: true }, }); }); await page.goto("http://localhost:3000"); // 替换为你的应用本地地址 await expect(page).toHaveScreenshot("myTest.png", { fullPage: true }); });
注:如果是服务端发起的API请求,需要让应用的HTTP请求走Playwright代理,可通过设置应用的HTTP_PROXY环境变量实现。
内容的提问来源于stack exchange,提问作者Drkawashima

