diff --git a/docs/src/evaluating.md b/docs/src/evaluating.md index 50851110d0..903aeb8a90 100644 --- a/docs/src/evaluating.md +++ b/docs/src/evaluating.md @@ -68,9 +68,95 @@ int status = await page.EvaluateAsync(@"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 -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 // A primitive value. @@ -86,7 +172,7 @@ await page.evaluate(object => object.foo, { foo: 'bar' }); const button = await page.evaluateHandle('window.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); // Object with multiple handles. @@ -109,7 +195,7 @@ await page.evaluate( ([b1, b2]) => b1.textContent + b2.textContent, [button1, button2]); -// Any non-cyclic mix of serializables and handles works. +// Any mix of serializables and handles works. await page.evaluate( x => x.button1.textContent + x.list[0].textContent + String(x.foo), { button1, list: [button2], foo: null }); @@ -131,7 +217,7 @@ page.evaluate("object => object.foo", obj); ElementHandle button = page.evaluateHandle("window.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); // Object with multiple handles. @@ -156,7 +242,7 @@ page.evaluate( "([b1, b2]) => b1.textContent + b2.textContent", Arrays.asList(button1, button2)); -// Any non-cyclic mix of serializables and handles works. +// Any mix of serializables and handles works. Map arg = new HashMap<>(); arg.put("button1", button1); arg.put("list", Arrays.asList(button2)); @@ -180,7 +266,7 @@ await page.evaluate('object => object.foo', { 'foo': 'bar' }) button = await page.evaluate_handle('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) # Object with multiple handles. @@ -203,7 +289,7 @@ await page.evaluate(""" ([b1, b2]) => b1.textContent + b2.textContent""", [button1, button2]) -# Any non-cyclic mix of serializables and handles works. +# Any mix of serializables and handles works. await page.evaluate(""" x => x.button1.textContent + x.list[0].textContent + String(x.foo)""", { 'button1': button1, 'list': [button2], 'foo': None }) @@ -223,7 +309,7 @@ page.evaluate('object => object.foo', { 'foo': 'bar' }) button = page.evaluate_handle('window.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) # Object with multiple handles. @@ -245,7 +331,7 @@ page.evaluate(""" ([b1, b2]) => b1.textContent + b2.textContent""", [button1, button2]) -# Any non-cyclic mix of serializables and handles works. +# Any mix of serializables and handles works. page.evaluate(""" x => x.button1.textContent + x.list[0].textContent + String(x.foo)""", { 'button1': button1, 'list': [button2], 'foo': None }) @@ -265,7 +351,7 @@ await page.EvaluateAsync("object => object.foo", new { foo = "bar" }); var button = await page.EvaluateHandleAsync("window.button"); await page.EvaluateAsync("button => button.textContent", button); -// Alternative notation using elementHandle.EvaluateAsync. +// Alternative notation using JSHandle.EvaluateAsync. await button.EvaluateAsync("(button, from) => button.textContent.substring(from)", 5); // Object with multiple handles. @@ -282,93 +368,69 @@ await page.EvaluateAsync("({ button1, button2 }) => button1.textContent + button // Note the required parenthesis. 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 }); ``` -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 -const data = { text: 'some data', value: 1 }; -// Pass |data| as a parameter. -const result = await page.evaluate(data => { - window.myApp.use(data); -}, data); -``` +import { test, expect } from '@playwright/test'; +import path from 'path'; -```java -Map data = new HashMap<>(); -data.put("text", "some data"); -data.put("value", 1); -// 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); +test.beforeEach(async ({ page }) => { + // Add script for every test in the beforeEach hook. + // Make sure to correctly resolve the script path. + await page.addInitScript({ path: path.resolve(__dirname, '../mocks/preload.js') }); }); ``` ```java -Map data = new HashMap<>(); -data.put("text", "some data"); -data.put("value", 1); -Object result = page.evaluate("() => {\n" + - " // There is no |data| in the web page.\n" + - " window.myApp.use(data);\n" + - "}"); +// In your test, assuming the "preload.js" file is in the "mocks" directory. +page.addInitScript(Paths.get("mocks/preload.js")); ``` ```python async -data = { 'text': 'some data', 'value': 1 } -result = await page.evaluate("""() => { - // There is no |data| in the web page. - window.myApp.use(data) -}""") +# In your test, assuming the "preload.js" file is in the "mocks" directory. +await page.add_init_script(path="mocks/preload.js") ``` ```python sync -data = { 'text': 'some data', 'value': 1 } -result = page.evaluate("""() => { - // There is no |data| in the web page. - window.myApp.use(data) -}""") +# In your test, assuming the "preload.js" file is in the "mocks" directory. +page.add_init_script(path="mocks/preload.js") ``` ```csharp -var data = new { text = "some data", value = 1}; -// Pass data as a parameter -var result = await page.EvaluateAsync(@"data => { - // There is no |data| in the web page. - window.myApp.use(data); -}"); +// In your test, assuming the "preload.js" file is in the "mocks" directory. +await Page.AddInitScriptAsync(scriptPath: "mocks/preload.js"); +``` + +###### +* 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); +}); ```