Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/wppconnect-team/wa-js/llms.txt

Use this file to discover all available pages before exploring further.

Playwright gives you programmatic control of a real Chromium browser from Node.js. Combined with WA-JS, it becomes the foundation for WhatsApp bots, automated test suites, and data-extraction pipelines — all without modifying WhatsApp’s servers or using unofficial APIs at the network level. WA-JS is injected directly into the browser page, so every WPP.* call runs exactly as it does in a regular browser session.

Installation

Install playwright-chromium for the browser runtime and @wppconnect/wa-js for the injection bundle:
npm install playwright-chromium @wppconnect/wa-js

Basic automation script

The following TypeScript example from the WA-JS README shows the complete lifecycle: launch a browser, inject WA-JS, wait for it to be ready, then call WPP functions from Node.js via page.evaluate().
import * as playwright from 'playwright-chromium';

async function start() {
  const browser = await playwright.chromium.launch();
  const page = await browser.newPage();

  await page.goto('https://web.whatsapp.com/');

  await page.addScriptTag({
    path: require.resolve('@wppconnect/wa-js'),
  });

  // Wait WA-JS load
  await page.waitForFunction(() => window.WPP?.isReady);

  // Evaluating code: See https://playwright.dev/docs/evaluating/
  const isAuthenticated: string = await page.evaluate(() =>
    WPP.conn.isAuthenticated()
  );

  // Sending message: See https://playwright.dev/docs/evaluating/
  const sendResult: string = await page.evaluate(
    (to, message) => WPP.chat.sendTextMessage(to, message),
    to,
    message
  );
}

start();
page.evaluate() serialises its return value as JSON across the Node.js/browser boundary. Primitives, plain objects, and arrays work fine. Class instances, functions, and non-serialisable values are not transferred — only their JSON-safe properties come through.

How script injection works

page.addScriptTag({ path: require.resolve('@wppconnect/wa-js') }) loads the bundled wppconnect-wa.js file into the page’s JavaScript context. After that call returns, window.WPP exists in the browser, but WA-JS may still be initialising its internal loader. page.waitForFunction(() => window.WPP?.isReady) blocks until the loader has finished patching WhatsApp’s internals and set the isReady flag to true. Only after that check is it safe to call any WPP.* function.
WhatsApp Web requires the browser tab to remain open and connected while a session is active. Closing or hiding the page context will terminate the WebSocket connection and disconnect the session. For long-running automation, keep the browser process alive and monitor the conn.stream_mode_changed event to detect disconnections.

Passing data between Node.js and the browser

page.evaluate() accepts a serialisable second argument that is passed into the browser-side function:
const to = '5511999999999@c.us';
const message = 'Hello from Playwright!';

await page.evaluate(
  (to, message) => WPP.chat.sendTextMessage(to, message),
  to,
  message
);
The variables to and message are declared in Node.js and transferred to the browser context as serialised values. The browser-side arrow function receives them as its parameters. This is the standard pattern for all page.evaluate() calls that need runtime data.

Session persistence

By default, playwright.chromium.launch() creates a temporary browser profile that is discarded when the process exits. That means the next run shows a fresh QR code. To avoid scanning QR on every run, use a persistent context with a fixed user data directory:
import * as playwright from 'playwright-chromium';
import * as path from 'path';
import * as os from 'os';

const userDataDir = path.join(os.tmpdir(), 'wa-js-session');

async function start() {
  const context = await playwright.chromium.launchPersistentContext(
    userDataDir,
    { headless: false } // show browser so you can scan QR on first run
  );

  const page = context.pages()[0] ?? await context.newPage();

  await page.goto('https://web.whatsapp.com/');

  await page.addScriptTag({
    path: require.resolve('@wppconnect/wa-js'),
  });

  await page.waitForFunction(() => window.WPP?.isReady);

  // Session is now cached in userDataDir and reused on subsequent runs
}

start();
On the first run the browser opens visibly so you can scan the QR code. Subsequent runs load the saved session from userDataDir automatically. You can also export and import session state as JSON using Playwright’s storageState option, which saves cookies and localStorage but not the full browser profile:
// Save state after authenticating
await context.storageState({ path: 'wa-session.json' });

// Restore state in a new context
const context = await playwright.chromium.launchPersistentContext(userDataDir, {
  storageState: 'wa-session.json',
});
storageState captures cookies and localStorage, but WhatsApp Web also stores cryptographic keys in IndexedDB. A persistent context directory (launchPersistentContext) is more reliable for full session reuse.

Running the WA-JS test suite

WA-JS ships a Playwright test suite under tests/. Two entry points are available:
# Smoke tests — no QR scan needed.
# Loads the live wppconnect-wa.js bundle on web.whatsapp.com and verifies
# every public WPP module still exposes the documented functions.
npm test -- tests/smoke.spec.ts

# Full test project (smoke + any other *.spec.ts)
npm test
Tests that need an authenticated session use the loggedPage fixture from tests/wpp-test.ts. To bootstrap that session for the first time (opens a real browser so you can scan the QR code), run:
npm run test:prepare
The session is cached under your OS temp directory (wa-js-test-<browser>) and reused automatically by all subsequent npm test runs.

Listening for events

You can register WA-JS event listeners inside page.evaluate() and bridge them back to Node.js using page.exposeFunction():
// Expose a Node.js function to the browser context
await page.exposeFunction('onNewMessage', (msg: unknown) => {
  console.log('New message:', msg);
});

// Register the WA-JS event listener inside the page
await page.evaluate(() => {
  WPP.on('chat.new_message', (msg) => {
    (window as any).onNewMessage(msg);
  });
});
page.exposeFunction makes a Node.js callback available as a global function inside the browser, so WA-JS events can trigger Node.js-side processing.

Build docs developers (and LLMs) love