๐ญ Playwright JavaScript Evaluation
Talking to the Browser Like a Puppeteer Talks to Puppets
๐ The Big Picture
Imagine youโre a puppet master standing behind a stage. The puppets (the webpage) can do amazing things, but sometimes you need to whisper instructions directly to them or ask them to tell you secrets they know.
Thatโs exactly what JavaScript Evaluation in Playwright does!
page.evaluate= You whisper a command, the puppet does it, and tells you the resultpage.evaluateHandle= You ask for a puppet itself, not just what it saysexposeFunction= You give the puppet a special phone to call you anytime
๐ฏ Why Do We Need This?
Sometimes, Playwrightโs regular commands arenโt enough. You need to:
- Read hidden information from the page (like data stored in memory)
- Run custom calculations inside the browser
- Access browser-only features (like localStorage, cookies, or window objects)
- Let the page talk back to your test when something happens
Think of it like this: Playwright is great at clicking buttons and filling forms. But what if you need to peek inside the toy box to see whatโs there? Thatโs when you use evaluate!
๐ Part 1: The page.evaluate Method
๐ช What Is It?
page.evaluate lets you run JavaScript code inside the browser and get the result back.
Simple Analogy:
You write a note, pass it to the puppet through a tiny door, the puppet reads it, does what it says, writes an answer, and passes it back.
โจ Basic Example
// Get the page title
const title = await page.evaluate(() => {
return document.title;
});
console.log(title); // "My Awesome Page"
What happened?
- We sent a tiny function to the browser
- The browser ran it
- The browser sent back the result (
document.title)
๐ Passing Values INTO the Browser
You can send values from your test INTO the browser!
const searchTerm = "puppies";
const result = await page.evaluate((term) => {
// 'term' is now "puppies" inside the browser
return `You searched for: ${term}`;
}, searchTerm);
console.log(result); // "You searched for: puppies"
The rule: Whatever you pass as the second argument becomes the first argument inside the function.
๐ฆ Passing Multiple Values
Use an object to send many values:
const data = { name: "Luna", age: 5 };
const greeting = await page.evaluate((info) => {
return `Hello, ${info.name}! You are ${info.age}.`;
}, data);
console.log(greeting); // "Hello, Luna! You are 5."
๐ช Real-World Example: Reading localStorage
// Check what's stored in localStorage
const savedUser = await page.evaluate(() => {
return localStorage.getItem('currentUser');
});
if (savedUser) {
console.log(`Welcome back, ${savedUser}!`);
}
๐จ Real-World Example: Getting Computed Styles
const buttonColor = await page.evaluate(() => {
const btn = document.querySelector('.buy-button');
const styles = window.getComputedStyle(btn);
return styles.backgroundColor;
});
console.log(`Button color: ${buttonColor}`);
โ ๏ธ Important Rules
- The function runs in the browser, not your test file
- You can only return simple values (strings, numbers, arrays, objects)
- You cannot use variables from your test unless you pass them in
// โ WRONG - myVar doesn't exist in browser
const myVar = "hello";
await page.evaluate(() => {
console.log(myVar); // ERROR!
});
// โ
CORRECT - pass it as argument
const myVar = "hello";
await page.evaluate((v) => {
console.log(v); // "hello"
}, myVar);
๐ Part 2: The page.evaluateHandle Method
๐ช Whatโs Different?
page.evaluate gives you the answer (a value).
page.evaluateHandle gives you a handle to the actual thing in the browser.
Analogy:
With
evaluate, the puppet tells you โthe apple is red.โ WithevaluateHandle, the puppet hands you the actual apple (well, a magic string attached to it).
๐ค When Would You Use This?
When you need to work with something you canโt just copy as a simple value:
- DOM elements
- Functions
- Complex objects with methods
- Window or document objects
โจ Basic Example
// Get a handle to the document body
const bodyHandle = await page.evaluateHandle(() => {
return document.body;
});
// Now you can use this handle with other methods
const bodyText = await bodyHandle.evaluate(
(body) => body.innerText
);
console.log(bodyText);
// Always clean up handles when done!
await bodyHandle.dispose();
๐ฏ Getting a Handle to a Function
// Get a handle to a function defined on the page
const funcHandle = await page.evaluateHandle(() => {
return window.calculateTotal;
});
// Use the function through the handle
const total = await page.evaluate(
(fn) => fn(100, 50),
funcHandle
);
console.log(total); // Result of calculateTotal(100, 50)
await funcHandle.dispose();
๐ณ Working with Complex Objects
// Get handle to the window object
const windowHandle = await page.evaluateHandle(() => {
return window;
});
// Access properties through the handle
const screenWidth = await windowHandle.evaluate(
(win) => win.screen.width
);
console.log(`Screen width: ${screenWidth}`);
await windowHandle.dispose();
๐ Handle vs Evaluate Comparison
| Feature | evaluate |
evaluateHandle |
|---|---|---|
| Returns | Simple values | JSHandle object |
| Best for | Strings, numbers, arrays | DOM, functions, complex objects |
| Cleanup | Not needed | Must call .dispose() |
| Use case | Reading data | Interacting with live objects |
โ ๏ธ Memory Management
Always dispose of handles when done:
const handle = await page.evaluateHandle(() => {
return document.querySelector('#app');
});
// ... use the handle ...
// Clean up to prevent memory leaks!
await handle.dispose();
๐ Part 3: Exposing Functions to the Page
๐ช What Is This?
page.exposeFunction creates a bridge between your test and the webpage. It lets the webpage call a function that runs in your test!
Analogy:
You give the puppet a magic walkie-talkie. Whenever the puppet presses the button, you hear them and can respond!
๐ Why Is This Powerful?
- The webpage can send data TO your test
- You can respond to events happening inside the page
- Perfect for intercepting or logging browser actions
โจ Basic Example
// Expose a function called "reportEvent"
await page.exposeFunction('reportEvent', (eventName) => {
console.log(`Browser event: ${eventName}`);
});
// Now the page can call window.reportEvent()
await page.evaluate(() => {
window.reportEvent('User clicked signup!');
});
// Output: "Browser event: User clicked signup!"
๐ Real Example: Tracking All Clicks
const clicks = [];
// Expose a click tracker
await page.exposeFunction('trackClick', (element) => {
clicks.push(element);
console.log(`Clicked: ${element}`);
});
// Set up tracking in the browser
await page.evaluate(() => {
document.addEventListener('click', (e) => {
window.trackClick(e.target.tagName);
});
});
// Now do some clicking...
await page.click('button');
await page.click('a');
console.log(clicks); // ['BUTTON', 'A']
๐ Auth Example: Intercepting Token
let capturedToken = null;
// Expose function to capture auth token
await page.exposeFunction('captureAuth', (token) => {
capturedToken = token;
console.log('Token captured!');
});
// Inject capture code before page loads auth
await page.evaluate(() => {
const originalSetItem = localStorage.setItem;
localStorage.setItem = function(key, value) {
if (key === 'authToken') {
window.captureAuth(value);
}
originalSetItem.call(this, key, value);
};
});
// Login happens...
// capturedToken now has the value!
๐ฎ Interactive Example: Two-Way Communication
// The page can ASK your test for data!
await page.exposeFunction('getTestData', async (key) => {
const testData = {
username: 'testuser',
apiKey: 'secret123'
};
return testData[key];
});
// Page can now request data
const username = await page.evaluate(async () => {
const name = await window.getTestData('username');
return `Logged in as: ${name}`;
});
console.log(username); // "Logged in as: testuser"
โก Async Functions Work Too!
// Expose an async function
await page.exposeFunction('fetchConfig', async () => {
// Simulate API call
return new Promise((resolve) => {
setTimeout(() => {
resolve({ theme: 'dark', lang: 'en' });
}, 100);
});
});
// Page calls it and awaits
const config = await page.evaluate(async () => {
const cfg = await window.fetchConfig();
return cfg;
});
console.log(config); // { theme: 'dark', lang: 'en' }
๐ Expose Function Rules
- Name must be unique - donโt use names that already exist on window
- Function persists - once exposed, it works for the entire page session
- Arguments are serialized - complex objects get converted
- Async works - the page can await your exposed functions
๐ง Summary Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ YOUR TEST (Node.js) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ page.evaluate(fn, args) โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Run fn in browser, return value โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ page.evaluateHandle(fn) โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Run fn, return HANDLE to object โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ page.exposeFunction(name, fn) โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Browser can CALL fn on window โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
๐ You Did It!
Now you know the three ways to talk between your test and the browser:
| Method | Direction | Returns | Use Case |
|---|---|---|---|
evaluate |
Test โ Browser โ Test | Values | Get data from page |
evaluateHandle |
Test โ Browser โ Test | Handle | Work with DOM/objects |
exposeFunction |
Browser โ Test | Anything | Let page call your code |
Remember the puppet analogy:
- ๐
evaluate= Pass a note, get an answer - ๐ญ
evaluateHandle= Get a string attached to the puppet itself - ๐
exposeFunction= Give the puppet a phone to call you
Happy testing! ๐
