Evaluating JavaScript
Your test code runs in Node.js. The page runs in the browser. They're separate processes — variables don't cross that boundary automatically. page.evaluate() is the bridge: pass a function, execute it in the browser, get the result back in Node.
Two environments — the most important thing to understand
Your test runs in Node.js. The page runs in a browser (V8, Blink). They're separate VMs — JavaScript closures don't cross between them. When you write a function inside page.evaluate(), that function runs in the browser. It has access to window, document, localStorage — but NOT to variables from your test unless you pass them explicitly.
page.evaluate() — read browser state
Use page.evaluate() when you need to read something from the browser that Playwright's locators can't reach — like window.__APP_CONFIG__, localStorage, computed styles, or scroll position.
page.evaluate() — trigger browser-level actions
Sometimes I need to trigger something that only works from inside the browser — dispatch a custom event, call a global app method, or simulate a scroll. evaluate is the right tool.
page.addInitScript() — inject before any page JS runs
page.evaluate() runs after the page loads. If you need code to run before any page JavaScript — to mock a browser API, replace Math.random, or set up a global variable — use page.addInitScript(). It runs at navigation time, before the page's own scripts.
Async evaluate — fetch inside the browser
The function inside evaluate can be async. Playwright waits for the promise to resolve. Use this when you need to make a request from the browser context — with the browser's cookies and session — instead of from Node.js.