docs(evaluate): improve the guide (#32222)

This commit is contained in:
Dmitry Gozman 2024-08-21 01:31:41 -07:00 committed by GitHub
parent 109cab66f1
commit b66cb6caaa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -68,9 +68,95 @@ int status = await page.EvaluateAsync<int>(@"async () => {
}"); }");
``` ```
## Different environments
Evaluated scripts run in the browser environment, while your test runs in a testing environments. This means you cannot use variables from your test in the page and vice versa. Instead, you should pass them explicitly as an argument.
The following snippet is **WRONG** because it uses the variable directly:
```js
const data = 'some data';
const result = await page.evaluate(() => {
// WRONG: there is no "data" in the web page.
window.myApp.use(data);
});
```
```java
String data = "some data";
Object result = page.evaluate("() => {\n" +
" // WRONG: there is no 'data' in the web page.\n" +
" window.myApp.use(data);\n" +
"}");
```
```python async
data = "some data"
result = await page.evaluate("""() => {
// WRONG: there is no "data" in the web page.
window.myApp.use(data)
}""")
```
```python sync
data = "some data"
result = page.evaluate("""() => {
// WRONG: there is no "data" in the web page.
window.myApp.use(data)
}""")
```
```csharp
var data = "some data";
var result = await page.EvaluateAsync(@"() => {
// WRONG: there is no 'data' in the web page.
window.myApp.use(data);
}");
```
The following snippet is **CORRECT** because it passes the value explicitly as an argument:
```js
const data = 'some data';
// Pass |data| as a parameter.
const result = await page.evaluate(data => {
window.myApp.use(data);
}, data);
```
```java
String data = "some data";
// Pass |data| as a parameter.
Object result = page.evaluate("data => {\n" +
" window.myApp.use(data);\n" +
"}", data);
```
```python async
data = "some data"
# Pass |data| as a parameter.
result = await page.evaluate("""data => {
window.myApp.use(data)
}""", data)
```
```python sync
data = "some data"
# Pass |data| as a parameter.
result = page.evaluate("""data => {
window.myApp.use(data)
}""", data)
```
```csharp
var data = "some data";
// Pass |data| as a parameter.
var result = await page.EvaluateAsync("data => { window.myApp.use(data); }", data);
```
## Evaluation Argument ## Evaluation Argument
Playwright evaluation methods like [`method: Page.evaluate`] take a single optional argument. This argument can be a mix of [Serializable] values and [JSHandle] or [ElementHandle] instances. Handles are automatically converted to the value they represent. Playwright evaluation methods like [`method: Page.evaluate`] take a single optional argument. This argument can be a mix of [Serializable] values and [JSHandle] instances. Handles are automatically converted to the value they represent.
```js ```js
// A primitive value. // A primitive value.
@ -86,7 +172,7 @@ await page.evaluate(object => object.foo, { foo: 'bar' });
const button = await page.evaluateHandle('window.button'); const button = await page.evaluateHandle('window.button');
await page.evaluate(button => button.textContent, button); await page.evaluate(button => button.textContent, button);
// Alternative notation using elementHandle.evaluate. // Alternative notation using JSHandle.evaluate.
await button.evaluate((button, from) => button.textContent.substring(from), 5); await button.evaluate((button, from) => button.textContent.substring(from), 5);
// Object with multiple handles. // Object with multiple handles.
@ -109,7 +195,7 @@ await page.evaluate(
([b1, b2]) => b1.textContent + b2.textContent, ([b1, b2]) => b1.textContent + b2.textContent,
[button1, button2]); [button1, button2]);
// Any non-cyclic mix of serializables and handles works. // Any mix of serializables and handles works.
await page.evaluate( await page.evaluate(
x => x.button1.textContent + x.list[0].textContent + String(x.foo), x => x.button1.textContent + x.list[0].textContent + String(x.foo),
{ button1, list: [button2], foo: null }); { button1, list: [button2], foo: null });
@ -131,7 +217,7 @@ page.evaluate("object => object.foo", obj);
ElementHandle button = page.evaluateHandle("window.button"); ElementHandle button = page.evaluateHandle("window.button");
page.evaluate("button => button.textContent", button); page.evaluate("button => button.textContent", button);
// Alternative notation using elementHandle.evaluate. // Alternative notation using JSHandle.evaluate.
button.evaluate("(button, from) => button.textContent.substring(from)", 5); button.evaluate("(button, from) => button.textContent.substring(from)", 5);
// Object with multiple handles. // Object with multiple handles.
@ -156,7 +242,7 @@ page.evaluate(
"([b1, b2]) => b1.textContent + b2.textContent", "([b1, b2]) => b1.textContent + b2.textContent",
Arrays.asList(button1, button2)); Arrays.asList(button1, button2));
// Any non-cyclic mix of serializables and handles works. // Any mix of serializables and handles works.
Map<String, Object> arg = new HashMap<>(); Map<String, Object> arg = new HashMap<>();
arg.put("button1", button1); arg.put("button1", button1);
arg.put("list", Arrays.asList(button2)); arg.put("list", Arrays.asList(button2));
@ -180,7 +266,7 @@ await page.evaluate('object => object.foo', { 'foo': 'bar' })
button = await page.evaluate_handle('button') button = await page.evaluate_handle('button')
await page.evaluate('button => button.textContent', button) await page.evaluate('button => button.textContent', button)
# Alternative notation using elementHandle.evaluate. # Alternative notation using JSHandle.evaluate.
await button.evaluate('(button, from) => button.textContent.substring(from)', 5) await button.evaluate('(button, from) => button.textContent.substring(from)', 5)
# Object with multiple handles. # Object with multiple handles.
@ -203,7 +289,7 @@ await page.evaluate("""
([b1, b2]) => b1.textContent + b2.textContent""", ([b1, b2]) => b1.textContent + b2.textContent""",
[button1, button2]) [button1, button2])
# Any non-cyclic mix of serializables and handles works. # Any mix of serializables and handles works.
await page.evaluate(""" await page.evaluate("""
x => x.button1.textContent + x.list[0].textContent + String(x.foo)""", x => x.button1.textContent + x.list[0].textContent + String(x.foo)""",
{ 'button1': button1, 'list': [button2], 'foo': None }) { 'button1': button1, 'list': [button2], 'foo': None })
@ -223,7 +309,7 @@ page.evaluate('object => object.foo', { 'foo': 'bar' })
button = page.evaluate_handle('window.button') button = page.evaluate_handle('window.button')
page.evaluate('button => button.textContent', button) page.evaluate('button => button.textContent', button)
# Alternative notation using elementHandle.evaluate. # Alternative notation using JSHandle.evaluate.
button.evaluate('(button, from) => button.textContent.substring(from)', 5) button.evaluate('(button, from) => button.textContent.substring(from)', 5)
# Object with multiple handles. # Object with multiple handles.
@ -245,7 +331,7 @@ page.evaluate("""
([b1, b2]) => b1.textContent + b2.textContent""", ([b1, b2]) => b1.textContent + b2.textContent""",
[button1, button2]) [button1, button2])
# Any non-cyclic mix of serializables and handles works. # Any mix of serializables and handles works.
page.evaluate(""" page.evaluate("""
x => x.button1.textContent + x.list[0].textContent + String(x.foo)""", x => x.button1.textContent + x.list[0].textContent + String(x.foo)""",
{ 'button1': button1, 'list': [button2], 'foo': None }) { 'button1': button1, 'list': [button2], 'foo': None })
@ -265,7 +351,7 @@ await page.EvaluateAsync<object>("object => object.foo", new { foo = "bar" });
var button = await page.EvaluateHandleAsync("window.button"); var button = await page.EvaluateHandleAsync("window.button");
await page.EvaluateAsync<IJSHandle>("button => button.textContent", button); await page.EvaluateAsync<IJSHandle>("button => button.textContent", button);
// Alternative notation using elementHandle.EvaluateAsync. // Alternative notation using JSHandle.EvaluateAsync.
await button.EvaluateAsync<string>("(button, from) => button.textContent.substring(from)", 5); await button.EvaluateAsync<string>("(button, from) => button.textContent.substring(from)", 5);
// Object with multiple handles. // Object with multiple handles.
@ -282,93 +368,69 @@ await page.EvaluateAsync("({ button1, button2 }) => button1.textContent + button
// Note the required parenthesis. // Note the required parenthesis.
await page.EvaluateAsync("([b1, b2]) => b1.textContent + b2.textContent", new[] { button1, button2 }); await page.EvaluateAsync("([b1, b2]) => b1.textContent + b2.textContent", new[] { button1, button2 });
// Any non-cyclic mix of serializables and handles works. // Any mix of serializables and handles works.
await page.EvaluateAsync("x => x.button1.textContent + x.list[0].textContent + String(x.foo)", new { button1, list = new[] { button2 }, foo = null as object }); await page.EvaluateAsync("x => x.button1.textContent + x.list[0].textContent + String(x.foo)", new { button1, list = new[] { button2 }, foo = null as object });
``` ```
Right: ## Init scripts
Sometimes it is convenient to evaluate something in the page before it starts loading. For example, you might want to setup some mocks or test data.
In this case, use [`method: Page.addInitScript`] or [`method: BrowserContext.addInitScript`]. In the example below, we will replace `Math.random()` with a constant value.
First, create a `preload.js` file that contains the mock.
```js browser
// preload.js
Math.random = () => 42;
```
Next, add init script to the page.
```js ```js
const data = { text: 'some data', value: 1 }; import { test, expect } from '@playwright/test';
// Pass |data| as a parameter. import path from 'path';
const result = await page.evaluate(data => {
window.myApp.use(data);
}, data);
```
```java test.beforeEach(async ({ page }) => {
Map<String, Object> data = new HashMap<>(); // Add script for every test in the beforeEach hook.
data.put("text", "some data"); // Make sure to correctly resolve the script path.
data.put("value", 1); await page.addInitScript({ path: path.resolve(__dirname, '../mocks/preload.js') });
// Pass |data| as a parameter.
Object result = page.evaluate("data => {\n" +
" window.myApp.use(data);\n" +
"}", data);
```
```python async
data = { 'text': 'some data', 'value': 1 }
# Pass |data| as a parameter.
result = await page.evaluate("""data => {
window.myApp.use(data)
}""", data)
```
```python sync
data = { 'text': 'some data', 'value': 1 }
# Pass |data| as a parameter.
result = page.evaluate("""data => {
window.myApp.use(data)
}""", data)
```
```csharp
var data = new { text = "some data", value = 1};
// Pass data as a parameter
var result = await page.EvaluateAsync("data => { window.myApp.use(data); }", data);
```
Wrong:
```js
const data = { text: 'some data', value: 1 };
const result = await page.evaluate(() => {
// There is no |data| in the web page.
window.myApp.use(data);
}); });
``` ```
```java ```java
Map<String, Object> data = new HashMap<>(); // In your test, assuming the "preload.js" file is in the "mocks" directory.
data.put("text", "some data"); page.addInitScript(Paths.get("mocks/preload.js"));
data.put("value", 1);
Object result = page.evaluate("() => {\n" +
" // There is no |data| in the web page.\n" +
" window.myApp.use(data);\n" +
"}");
``` ```
```python async ```python async
data = { 'text': 'some data', 'value': 1 } # In your test, assuming the "preload.js" file is in the "mocks" directory.
result = await page.evaluate("""() => { await page.add_init_script(path="mocks/preload.js")
// There is no |data| in the web page.
window.myApp.use(data)
}""")
``` ```
```python sync ```python sync
data = { 'text': 'some data', 'value': 1 } # In your test, assuming the "preload.js" file is in the "mocks" directory.
result = page.evaluate("""() => { page.add_init_script(path="mocks/preload.js")
// There is no |data| in the web page.
window.myApp.use(data)
}""")
``` ```
```csharp ```csharp
var data = new { text = "some data", value = 1}; // In your test, assuming the "preload.js" file is in the "mocks" directory.
// Pass data as a parameter await Page.AddInitScriptAsync(scriptPath: "mocks/preload.js");
var result = await page.EvaluateAsync(@"data => { ```
// There is no |data| in the web page.
window.myApp.use(data); ######
}"); * langs: js
Alternatively, you can pass a function instead of creating a preload script file. This is more convenient for short or one-off scripts. You can also pass an argument this way.
```js
import { test, expect } from '@playwright/test';
// Add script for every test in the beforeEach hook.
test.beforeEach(async ({ page }) => {
const value = 42;
await page.addInitScript(value => {
Math.random = () => value;
}, value);
});
``` ```