JavaScript Evaluation

Back

Loading concept...

๐ŸŽญ 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 result
  • page.evaluateHandle = You ask for a puppet itself, not just what it says
  • exposeFunction = 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:

  1. Read hidden information from the page (like data stored in memory)
  2. Run custom calculations inside the browser
  3. Access browser-only features (like localStorage, cookies, or window objects)
  4. 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?

  1. We sent a tiny function to the browser
  2. The browser ran it
  3. 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

  1. The function runs in the browser, not your test file
  2. You can only return simple values (strings, numbers, arrays, objects)
  3. 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.โ€ With evaluateHandle, 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

  1. Name must be unique - donโ€™t use names that already exist on window
  2. Function persists - once exposed, it works for the entire page session
  3. Arguments are serialized - complex objects get converted
  4. 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! ๐Ÿš€

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.