Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Implementation:Microsoft Playwright Page AddInitScript

From Leeroopedia
Knowledge Sources
Domains Network_Testing, Mocking, Browser_API_Injection
Last Updated 2026-02-11 00:00 GMT

Overview

Concrete tool for injecting JavaScript into the browser context to mock or override browser APIs before page scripts execute, provided by the Playwright library.

Description

Playwright provides three complementary methods for injecting test-controlled code into the browser environment:

page.addInitScript() (packages/playwright-core/src/client/page.ts:L501-504) registers a script that runs in every new document context created within the page. The script executes before any page-authored JavaScript, making it ideal for overriding browser APIs. The method accepts a function, a string of JavaScript code, or an object with path or content properties. An optional arg parameter can be passed and is serialized into the script context. Internally, the script is converted to a source string via evaluationScript() from clientHelper and sent to the browser through the channel protocol.

page.exposeFunction() (packages/playwright-core/src/client/page.ts:L327-331) adds a function to the browser's window object that, when called from page scripts, bridges to a Node.js callback in the test process. The function name is registered via the channel's exposeBinding command, and the callback is stored in the page's _bindings map. When the browser-side function is called, arguments are serialized, sent to Node.js, the callback executes, and the result is returned to the browser.

page.exposeBinding() (packages/playwright-core/src/client/page.ts:L333-336) is similar to exposeFunction() but provides additional context to the callback. The first argument received by the callback is a source object containing { context, page, frame }, allowing the handler to know which frame triggered the call. The options.handle parameter, when set to true, passes a JSHandle instead of serialized values, enabling manipulation of browser-side objects directly.

The BindingCall class (packages/playwright-core/src/client/page.ts:L864-891) manages the cross-boundary function invocation, handling argument deserialization via parseResult(), result serialization via serializeArgument(), and error propagation via serializeError().

Usage

Use these methods when:

  • You need to mock navigator.geolocation, navigator.permissions, or other non-HTTP browser APIs.
  • You want to freeze or control time with Date.now() overrides.
  • You need to inject feature flags or configuration before application code reads them.
  • You want to set up window.fetch or XMLHttpRequest interceptors at the JavaScript level.
  • You need to bridge browser events to the test process for assertion (via exposeFunction).
  • You want to inject a mock service worker registration script.

Code Reference

Source Location

  • Repository: playwright
  • addInitScript: packages/playwright-core/src/client/page.ts:L501-504
  • exposeFunction: packages/playwright-core/src/client/page.ts:L327-331
  • exposeBinding: packages/playwright-core/src/client/page.ts:L333-336
  • BindingCall: packages/playwright-core/src/client/page.ts:L864-891

Signature

// Add an initialization script
page.addInitScript(
  script: Function | string | { path?: string; content?: string },
  arg?: any
): Promise<void>;

// Expose a function to the browser window object
page.exposeFunction(
  name: string,
  callback: Function
): Promise<void>;

// Expose a binding with source context
page.exposeBinding(
  name: string,
  callback: (source: { context: BrowserContext; page: Page; frame: Frame }, ...args: any[]) => any,
  options?: { handle?: boolean }
): Promise<void>;

Import

// Playwright Test (recommended)
import { test, expect } from '@playwright/test';

// Library mode
import { chromium } from 'playwright';

I/O Contract

Inputs

Name Type Required Description
script (addInitScript) string | { path?, content? } Yes JavaScript to evaluate in every new document. Functions are serialized; strings are evaluated directly; objects specify a file path or inline content.
arg (addInitScript) any No Argument passed to the script function. Must be JSON-serializable.
name (exposeFunction) string Yes The name of the function to add to window. Must not conflict with existing properties.
callback (exposeFunction) Function Yes Node.js function to invoke when the browser-side function is called. Arguments are deserialized from the browser context.
name (exposeBinding) string Yes The name of the function to add to window.
callback (exposeBinding) (source, ...args) => any Yes Node.js function receiving a source object ({ context, page, frame }) as the first argument, followed by deserialized browser arguments.
options.handle (exposeBinding) boolean No When true, the callback receives a JSHandle instead of serialized argument values. Useful for complex or non-serializable browser objects.

Outputs

Name Type Description
addInitScript return Promise<void> Resolves when the init script is registered. The script will execute in every subsequent document context.
exposeFunction return Promise<void> Resolves when the function is exposed. window[name] becomes callable from browser code and bridges to the Node.js callback.
exposeBinding return Promise<void> Resolves when the binding is exposed. Same as exposeFunction but callback receives source context.
Browser-side function return Promise<any> When the exposed function is called from browser code, it returns a Promise that resolves with the serialized return value of the Node.js callback.

Usage Examples

Basic Example: Mock Geolocation

import { test, expect } from '@playwright/test';

test('mock geolocation API', async ({ page }) => {
  // Override navigator.geolocation before page scripts run
  await page.addInitScript(() => {
    const mockPosition = {
      coords: {
        latitude: 48.8566,
        longitude: 2.3522,
        accuracy: 100,
        altitude: null,
        altitudeAccuracy: null,
        heading: null,
        speed: null,
      },
      timestamp: Date.now(),
    };

    navigator.geolocation.getCurrentPosition = (success) => {
      success(mockPosition);
    };

    navigator.geolocation.watchPosition = (success) => {
      success(mockPosition);
      return 1;
    };
  });

  await page.goto('https://example.com/map');
  // Page will see Paris coordinates
});

Mock with Parameters

import { test, expect } from '@playwright/test';

test('inject feature flags via init script', async ({ page }) => {
  const featureFlags = {
    newDashboard: true,
    darkMode: false,
    betaFeatures: true,
  };

  await page.addInitScript((flags) => {
    window.__FEATURE_FLAGS__ = flags;
  }, featureFlags);

  await page.goto('https://example.com');
  // Application code can read window.__FEATURE_FLAGS__
});

Expose Function: Bridge Browser to Node.js

import { test, expect } from '@playwright/test';

test('bridge browser analytics to test', async ({ page }) => {
  const analyticsEvents: Array<{ name: string; data: any }> = [];

  // Expose a function that page code can call
  await page.exposeFunction('__trackEvent', (name: string, data: any) => {
    analyticsEvents.push({ name, data });
  });

  // Override the analytics library to use our bridge
  await page.addInitScript(() => {
    window.analytics = {
      track: (name: string, data: any) => {
        (window as any).__trackEvent(name, data);
      },
    };
  });

  await page.goto('https://example.com');
  await page.click('#purchase-button');

  // Assert analytics events were fired
  expect(analyticsEvents).toContainEqual(
    expect.objectContaining({ name: 'purchase_clicked' })
  );
});

Expose Binding with Source Context

import { test, expect } from '@playwright/test';

test('expose binding with frame context', async ({ page }) => {
  await page.exposeBinding('getFrameUrl', (source) => {
    // source.frame tells us which frame called this function
    return source.frame.url();
  });

  await page.addInitScript(() => {
    // Page scripts can call this to get their frame URL from Node.js
    (window as any).getFrameUrl().then((url: string) => {
      document.title = `Frame: ${url}`;
    });
  });

  await page.goto('https://example.com');
});

Mock Date.now for Deterministic Tests

import { test, expect } from '@playwright/test';

test('freeze time for deterministic testing', async ({ page }) => {
  const frozenTime = new Date('2025-01-15T10:00:00Z').getTime();

  await page.addInitScript((timestamp) => {
    const OriginalDate = Date;
    const MockDate = class extends OriginalDate {
      constructor(...args: any[]) {
        if (args.length === 0) {
          super(timestamp);
        } else {
          // @ts-ignore
          super(...args);
        }
      }
      static now() { return timestamp; }
    };
    (window as any).Date = MockDate;
  }, frozenTime);

  await page.goto('https://example.com');
  const displayedDate = await page.textContent('.current-date');
  expect(displayedDate).toContain('January 15, 2025');
});

Related Pages

Implements Principle

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment