跳到主要内容

执行 JavaScript 代码

简介

Playwright 脚本运行在 Playwright 环境中,而页面脚本运行在浏览器页面环境中。这两个环境互不相交,它们运行在不同的虚拟机、不同进程甚至可能在不同的计算机上。

page.evaluate() API 可以在网页上下文中执行 JavaScript 函数,并将结果带回 Playwright 环境。在 evaluate 中可以使用浏览器全局对象如 windowdocument

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

如果结果是 Promise 或者函数是异步的,evaluate 会自动等待其解析完成:

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

不同环境

被执行的脚本运行在浏览器环境中,而测试代码运行在测试环境中。这意味着你不能在页面中使用测试中的变量,反之亦然。正确的做法是显式地将它们作为参数传递。

以下代码片段是错误的,因为它直接使用了变量:

const data = 'some data';
const result = await page.evaluate(() => {
// 错误:网页中不存在 "data" 变量
window.myApp.use(data);
});

以下代码片段是正确的,因为它显式地将值作为参数传递:

const data = 'some data';
// 将 |data| 作为参数传递
const result = await page.evaluate(data => {
window.myApp.use(data);
}, data);

评估参数

Playwright 的评估方法如 page.evaluate() 接受一个可选参数。这个参数可以是 Serializable 值和 JSHandle 实例的混合。句柄会自动转换为它们所代表的值。

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

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

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

// 单个句柄
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(
o => o.button1.textContent + o.button2.textContent,
{ button1, button2 });

// 对象解构有效。注意解构对象的属性名必须与参数匹配
// 同时注意必须使用括号
await page.evaluate(
({ button1, button2 }) => button1.textContent + button2.textContent,
{ button1, button2 });

// 数组同样有效。解构可以使用任意名称
// 注意必须使用括号
await page.evaluate(
([b1, b2]) => b1.textContent + b2.textContent,
[button1, button2]);

// 任何可序列化值和句柄的混合都有效
await page.evaluate(
x => x.button1.textContent + x.list[0].textContent + String(x.foo),
{ button1, list: [button2], foo: null });

初始化脚本

有时在页面开始加载前执行某些操作会很方便。例如,你可能需要设置一些模拟数据或测试数据。

这种情况下,可以使用 page.addInitScript()browserContext.addInitScript()。在下面的示例中,我们将用固定值替换 Math.random() 函数。

首先,创建一个包含模拟逻辑的 preload.js 文件。

// preload.js
Math.random = () => 42;

然后,将初始化脚本添加到页面中。

import { test, expect } from '@playwright/test';
import path from 'path';

test.beforeEach(async ({ page }) => {
// 在 beforeEach 钩子中为每个测试添加脚本
// 确保正确解析脚本路径
await page.addInitScript({ path: path.resolve(__dirname, '../mocks/preload.js') });
});

或者,你也可以直接传递函数而不需要创建预加载脚本文件。这种方式对于简短或一次性脚本更加方便。你还可以通过这种方式传递参数。

import { test, expect } from '@playwright/test';

// 在 beforeEach 钩子中为每个测试添加脚本
test.beforeEach(async ({ page }) => {
const value = 42;
await page.addInitScript(value => {
Math.random = () => value;
}, value);
});