• DocsDocs
  • PricingPricing
Book a demo
Sign in
Sign in
Book a demo
    • Ready-made features
      • AI Copilots
        AI Copilots

        In-app AI agents that feel human

      • Comments
        Comments

        Contextual commenting

      • Multiplayer Editing
        Multiplayer Editing

        Realtime collaboration

      • Notifications
        Notifications

        Smart alerts for your app

      • Presence
        Presence

        Realtime presence indicators

    • Platform
      • Monitoring Dashboard
        Monitoring Dashboard

        Monitor your product

      • Realtime Infrastructure
        Realtime Infrastructure

        Hosted WebSocket infrastructure

    • Tools
      • Examples

        Gallery of open source examples

      • Next.js Starter Kit

        Kickstart your Next.js collaborative app

      • DevTools

        Browser extension for debugging

      • Tutorial

        Step-by-step interactive tutorial

      • Guides

        How-to guides and tutorial

    • Company
      • Blog

        The latest from Liveblocks

      • Customers

        The teams Liveblocks empowers

      • Changelog

        Weekly product updates

      • Security

        Our approach to security

      • About

        The story and team behind Liveblocks

  • Docs
  • Pricing
  • Ready-made features
    • AI Copilots
    • Comments
    • Multiplayer Editing
    • Notifications
    • Presence
    Platform
    • Monitoring Dashboard
    • Realtime Infrastructure
    Solutions
    • People platforms
    • Sales tools
    • Startups
    Use cases
    • Multiplayer forms
    • Multiplayer text editor
    • Multiplayer creative tools
    • Multiplayer whiteboard
    • Comments
    • Sharing and permissions
    • Document browsing
  • Resources
    • Documentation
    • Examples
    • React components
    • DevTools
    • Next.js Starter Kit
    • Tutorial
    • Guides
    • Release notes
    Technologies
    • Next.js
    • React
    • JavaScript
    • Redux
    • Zustand
    • Yjs
    • Tiptap
    • BlockNote
    • Slate
    • Lexical
    • Quill
    • Monaco
    • CodeMirror
  • Company
    • Pricing
    • Blog
    • Customers
    • Changelog
    • About
    • Contact us
    • Careers
    • Terms of service
    • Privacy policy
    • DPA
    • Security
    • Trust center
    • Subprocessors
  • HomepageSystem status
    • Github
    • Discord
    • X
    • LinkedIn
    • YouTube
    © 2025 Liveblocks Inc.
Blog/Engineering

How to set up end-to-end tests for multiplayer apps using Puppeteer and Jest

Learn how to set up end-to-end tests with multiple browser windows using Puppeteer and Jest.

on February 4th, 2022
How to set up end-to-end tests for multiplayer apps using Puppeteer and Jest
February 4th, 2022·7 min read
Share article

I recently tweeted a demo of one of our e2e tests, and some people were curious to know how it worked. So here we are!

For simplicity’s sake, I will show you how to test a basic realtime todo app, one of our examples.

E2E test of multiplayer todo list

Before we jump into the code, I want to explain why we chose Puppeteer and Jest instead of other popular e2e test solutions. If you only want to see how it works, you can skip the following sections or directly look at the code on Github.

Why not Cypress?

Usually, when I have to write e2e web tests, my go-to tool is Cypress. If you’re not familiar with Cypress, it’s a complete solution to write/record/debug/run e2e tests with a slick API/interface. It’s a great open-source tool overall.

However, Cypress has a significant limitation that I can’t ignore in the context of Liveblocks. Cypress made the permanent trade-off of not supporting multiple tabs/browsers. This trade-off is understandable and fair! Supporting multiple tabs/browsers could negatively impact API surface/developer experience for the common e2e tests.

Because we’re building APIs for collaboration, 99% of the end-to-end tests we do involve multiple browsers. Mocking is not an option because we want to go through all our infrastructure.

Why not Playwright?

I didn’t know it existed before I started writing e2e tests at Liveblocks 🙃 I never tried it, so I don’t have any opinion. Playwright supports a multi-browsers environment, so it would be a high contender for refactoring if one day Puppeteer & Jest are not enough!

Why Puppeteer & Jest?

Puppeteer is a node package that lets you control Chrome (or Firefox). It’s not made to write end-to-end tests, so you have to use it with the test framework of your choice. I decided to go with Jest because we use it for unit/integration tests.

Enough about the “Why”, show me the code!

First of all, we install a few dependencies.

$npm install jest puppeteer jest-environment-node

The testing process is pretty simple. Every step has its own file that we reference in the jest.config.js.

  1. setup.js - Start a few browsers before launching any tests.
  2. puppeteer_environment.js - Create a jest testing environment that will expose the browsers to a test suite. This is where we use jest-environment-node.
  3. test/todo-list.test.js - Navigate to the URL you want to test and validate that everything works as expected.
  4. teardown.js - Close the browsers once all the tests are over.

In setup.js, we’re starting multiple browsers before executing any test suite. To keep it simple, the code below launches two instances of Chrome side to side. It should be relatively easy to update the code to change the number of browsers and their positions.

Puppeteer controls Chrome and Firefox via a websocket. Once the browsers are ready, we write these websocket endpoints in a temporary file that will be read from the test environment.

Then we create a custom test environment to connect to the browsers we just started. And we expose the browsers to the test suite with a global variable.

Now, the actual test. We use Puppeteers APIs to interact with the browsers/pages and then we use Jest to do the assertions.

const TIMEOUT = 60 * 1000;
const URL = "https://liveblocks.io/examples/todo-list?room=e2e-" + Date.now();
describe("liveblocks.io / todo-list example", () => { test( "creating a todo on page A should appear on page B", async () => { /** * By default, puppeteer.launch opens a window * with a single tab so we use it here. * It's also possible to open a new page and close it * for each test with beforeEach / afterEach. */ const pageA = await getFirstTab(browsers[0]); const pageB = await getFirstTab(browsers[1]);
for (const page of [pageA, pageB]) { // Navigate to the todo list on all browsers. await page.goto(URL); // Close an overlay that we have on all our examples await page.click("#close-example-info"); }
// The input appears once the todo list is loaded const input = await pageA.waitForSelector("input");
// Types some text and press enter to create a todo await input.type("Do a blog post about puppeteer & jest\\n", { delay: 50, // This slows down the typing });
// Validate text of the first todo expect(await getTextContent(pageB, "#todo-0")).toBe( "Do a blog post about puppeteer & jest" );
// waiting for the sake of the demo await wait(1000);
// cleanup await pageA.click("#delete-todo-0");
// waiting for the sake of the demo await wait(1000); }, TIMEOUT );});
async function getTextContent(page, selector) { const element = await page.waitForSelector(selector); return await element.evaluate((el) => el.textContent);}
async function getFirstTab(browser) { const [firstTab] = await browser.pages(); return firstTab;}
function wait(ms) { return new Promise((resolve) => setTimeout(resolve, ms));}

Finally, we close all the browsers after the tests are over.

Now, you only need to run jest to execute your test. Add the following script in your package.json.

{  "scripts": {    "test": "jest"  }}

And execute it with:

$npm test

And voilà!

E2E test of multiplayer todo list

Conclusion

At this point, you probably have a good idea about how Puppeteer & Jest can work together to test multi-player apps. Puppeteer is a powerful tool; it’s also possible to launch Firefox, simulate slow network/CPU, take screenshots (for UI testing), and much more.

At Liveblocks, we’re running e2e tests before every deployment/release to ensure that all our components work well together. We also do e2e fuzz testing to find edge cases in various environments.

If you’re interested in knowing more about our engineering practices, please let us know on Github or Twitter, or even better, apply to one of our open positions.

How to guides

Ready to get started?

Join thousands of companies using Liveblocks ready‑made collaborative features to drive growth in their products.

Book a demo

Related blog posts

  • Why we built our AI agents on WebSockets instead of HTTP

    Why we built our AI agents on WebSockets instead of HTTP

    Picture of Jonathan Rowny
    Picture of Nimesh Nayaju
    September 29th
    Engineering
  • What's the best vector database for building AI products?

    What's the best vector database for building AI products?

    Picture of Jonathan Rowny
    September 15th
    Engineering
  • We’ve open-sourced our customizable React emoji picker

    We’ve open-sourced our customizable React emoji picker

    Picture of Chris Nicholas
    Picture of Marc Bouchenoire
    Picture of Pierre Le Vaillant
    March 20th
    Engineering