首页 / Playwright 入门教程 / 在页面中执行 JavaScript(evaluate)

Playwright 入门教程

在页面中执行 JavaScript(evaluate)

本教程共 59 篇 · 第 54 篇 · 更新于 2026-08-04 · 约 7 分钟阅读

PlaywrightevaluateevaluateHandle执行JSJSHandle初始化脚本环境隔离

本节目标:学完你能把一段 JS 丢进浏览器里执行,把结果或元素带回 Node 测试环境,绕开 Playwright API 够不着的地方。

Playwright 的 API 很全,但总有些事它没直接封装——比如读 document.location.href、调页面里已有的某个函数。这时候用 page.evaluate(),把 JS 函数送进浏览器跑,再把结果带回来。

两套环境不互通

你的测试跑在 Node.js 里,页面跑在浏览器里。它们是两台不同的「虚拟机」,互相看不见。所以 evaluate 里的函数能直接用 windowdocument 这些浏览器全局变量。

const href = await page.evaluate(() => document.location.href);

如果返回的是 Promise,或函数是 async 的,evaluate 会自动等它 resolve:

const status = await page.evaluate(async () => {
  const response = await fetch(location.href);
  return response.status;
});

不能直接用外面的变量

最常见的新手坑:把测试里的变量直接写进 evaluate 函数体。它运行在浏览器里,根本不认识这个变量。

// ❌ 错:浏览器里没有 data 这个变量
const data = 'some data';
const result = await page.evaluate(() => {
  window.myApp.use(data);
});

正确做法是把它作为参数显式传进去:

// ✅ 对:把 data 当参数传
const data = 'some data';
const result = await page.evaluate(data => {
  window.myApp.use(data);
}, data);
Warning

记住铁律:测试环境的变量,必须当参数传进 evaluate,绝不能当闭包直接用。

参数能传什么

evaluate 只接受一个可选参数,它可以是「可序列化值」和「JSHandle(句柄)」的混合:

// 原始值
await page.evaluate(num => num, 42);

// 数组
await page.evaluate(array => array.length, [1, 2, 3]);

// 对象
await page.evaluate(object => object.foo, { foo: 'bar' });

拿到元素要用 JSHandle

想操作页面里的某个对象(而不是它的文本),先 evaluateHandle 拿句柄,再当参数传:

const button = await page.evaluateHandle('window.button');
await page.evaluate(button => button.textContent, button);

句柄也能用更顺手的 JSHandle.evaluate

await button.evaluate((button, from) => button.textContent.substring(from), 5);

多个句柄可以塞进一个对象或数组传:

const button1 = await page.evaluateHandle('window.button1');
const button2 = await page.evaluateHandle('window.button2');

await page.evaluate(
  ({ button1, button2 }) => button1.textContent + button2.textContent,
  { button1, button2 }
);
Note

解构时属性名要对得上,且整体要加括号——这是 JS 的语法要求,不是 Playwright 的规定。

在页面加载前埋脚本

有时你想在页面一开始加载就改点东西,比如把随机数固定住,方便测试可复现。用 page.addInitScript()

// mocks/preload.js
Math.random = () => 42;
import { test, expect } from '@playwright/test';
import path from 'path';

test.beforeEach(async ({ page }) => {
  await page.addInitScript({ path: path.resolve(__dirname, '../mocks/preload.js') });
});

短脚本不想单建文件,直接传函数也行,还能带参数:

test.beforeEach(async ({ page }) => {
  const value = 42;
  await page.addInitScript(value => {
    Math.random = () => value;
  }, value);
});
Tip

addInitScript 在每个页面导航前都会执行,适合放全局 mock 或测试数据。它和 evaluate 的区别是:一个在加载前埋,一个在加载后跑。

什么时候用它

别一上来就 evaluate。能用 Locator 和断言搞定,就用它们——更稳、更抗页面变动。只有这几种情况才值得掏出 evaluate

  • 读 Playwright 没直接暴露的页面内部状态。
  • 调页面里已有的工具函数。
  • 页面加载前就要改全局行为(配合 addInitScript)。

一句话:evaluate 是「翻窗户」的备用手段,能走正门(Locator/断言)就别翻窗。

小结

这章学了 page.evaluate() 把 JS 送进浏览器跑、再把结果带回来。核心铁律:Node 和浏览器是两套环境,测试里的变量必须当参数传进去,不能当闭包直接用。拿到元素要用 evaluateHandle 拿句柄。addInitScript 可以在页面加载前埋脚本,适合全局 mock。能用 Locator 搞定的别掏 evaluate,它是兜底手段,不是常规武器。