argos-ci / jest-puppeteer

Run tests using Jest & Puppeteer 🎪✨
MIT License
3.54k stars 289 forks source link
chrome chromeless integration-testing jest jest-environment puppeteer

Let's find a balance between detailed explanations and clarity. Here’s a more comprehensive version that retains structure but elaborates more where needed:


🎪 jest-puppeteer

npm version npm downloads

jest-puppeteer is a Jest preset designed for seamless integration with Puppeteer, enabling end-to-end testing in a browser environment. With a simple API, it allows you to launch browsers and interact with web pages, making it perfect for testing UI interactions in web applications.

Table of Contents

  1. Getting Started
  2. Recipes
  3. Jest-Puppeteer Configuration
  4. API Reference
  5. Troubleshooting
  6. Acknowledgements

Getting Started

Installation

To start using jest-puppeteer, you’ll need to install the following packages:

npm install --save-dev jest-puppeteer puppeteer jest

This will install Jest (the testing framework), Puppeteer (the headless browser tool), and jest-puppeteer (the integration between the two).

Basic Setup

In your Jest configuration file (jest.config.js), add jest-puppeteer as the preset:

{
  "preset": "jest-puppeteer"
}

This will configure Jest to use Puppeteer for running your tests. Make sure to remove any conflicting testEnvironment settings that might be present in your existing Jest configuration, as jest-puppeteer manages the environment for you.

Writing Your First Test

Once you’ve configured Jest, you can start writing tests using Puppeteer’s page object, which is automatically provided by jest-puppeteer.

Create a test file (e.g., google.test.js):

import "expect-puppeteer";

describe("Google Homepage", () => {
  beforeAll(async () => {
    await page.goto("https://google.com");
  });

  it('should display "google" text on page', async () => {
    await expect(page).toMatchTextContent(/Google/);
  });
});

This example test navigates to Google’s homepage and checks if the page contains the word "Google". jest-puppeteer simplifies working with Puppeteer by exposing the page object, allowing you to write tests using a familiar syntax.

TypeScript Setup

If you’re using TypeScript, jest-puppeteer natively supports it from version 8.0.0. To get started with TypeScript, follow these steps:

  1. Make sure your project is using the correct type definitions. If you’ve upgraded to version 10.1.2 or above, uninstall old types:
npm uninstall --save-dev @types/jest-environment-puppeteer @types/expect-puppeteer
  1. Install @types/jest (jest-puppeteer does not support @jest/globals) :
npm install --save-dev @types/jest
  1. Jest will automatically pick up type definitions from @types/jest. Once you’ve set up the environment, you can start writing tests in TypeScript just like in JavaScript:
import "jest-puppeteer";
import "expect-puppeteer";

describe("Google Homepage", (): void => {
  beforeAll(async (): Promise<void> => {
    await page.goto("https://google.com");
  });

  it('should display "google" text on page', async (): Promise<void> => {
    await expect(page).toMatchTextContent(/Google/);
  });
});

Visual Testing with Argos

Argos is a powerful tool for visual testing, allowing you to track visual changes introduced by each pull request. By integrating Argos with jest-puppeteer, you can easily capture and compare screenshots to maintain the visual consistency of your application.

To get started, check out the Puppeteer Quickstart Guide.

Recipes

Using expect-puppeteer

Writing tests with Puppeteer’s core API can be verbose. The expect-puppeteer library simplifies this by adding custom matchers, such as checking for text content or interacting with elements. Some examples:

await expect(page).toMatchTextContent("Expected text");
await expect(page).toClick("button", { text: "Submit" });
await expect(page).toFillForm('form[name="login"]', {
  username: "testuser",
  password: "password",
});

Debugging Tests

Debugging can sometimes be tricky in headless browser environments. jest-puppeteer provides a helpful debug() function, which pauses test execution and opens the browser for manual inspection:

await jestPuppeteer.debug();

To prevent the test from timing out, increase Jest’s timeout:

jest.setTimeout(300000); // 5 minutes

This can be particularly useful when you need to step through interactions or inspect the state of the page during test execution.

Automatic Server Management

If your tests depend on a running server (e.g., an Express app), you can configure jest-puppeteer to automatically start and stop the server before and after tests:

module.exports = {
  server: {
    command: "node server.js",
    port: 4444,
  },
};

This eliminates the need to manually manage your server during testing.

Customizing the Puppeteer Instance

You can easily customize the Puppeteer instance used in your tests by modifying the jest-puppeteer.config.js file. For example, if you want to launch Firefox instead of Chrome:

module.exports = {
  launch: {
    product: "firefox",
    headless: process.env.HEADLESS !== "false",
  },
};

This file allows you to configure browser options, set up browser contexts, and more.

Custom Test Setup

If you have custom setup requirements, you can define setup files to initialize your environment before each test. For instance, you may want to import expect-puppeteer globally:

// setup.js
require("expect-puppeteer");

Then, in your Jest config:

module.exports = {
  setupFilesAfterEnv: ["./setup.js"],
};

Extending PuppeteerEnvironment

For advanced use cases, you can extend the default PuppeteerEnvironment class to add custom functionality:

const PuppeteerEnvironment = require("jest-environment-puppeteer");

class CustomEnvironment extends PuppeteerEnvironment {
  async setup() {
    await super.setup();
    // Custom setup logic
  }

  async teardown() {
    // Custom teardown logic
    await super.teardown();
  }
}

module.exports = CustomEnvironment;

Global Setup and Teardown

Sometimes, tests may require a global setup or teardown step that only runs once per test suite. You can define custom globalSetup and globalTeardown scripts:

// global-setup.js
const setupPuppeteer = require("jest-environment-puppeteer/setup");

module.exports = async function globalSetup(globalConfig) {
  await setupPuppeteer(globalConfig);
  // Additional setup logic
};

In your Jest configuration, reference these files:

{
  "globalSetup": "./global-setup.js",
  "globalTeardown": "./global-teardown.js"
}

Jest-Puppeteer Configuration

Jest-Puppeteer supports various configuration formats through cosmiconfig, allowing flexible ways to define your setup. By default, the configuration is looked for at the root of your project, but you can also define a custom path using the JEST_PUPPETEER_CONFIG environment variable.

Possible configuration formats:

Example of a basic configuration file (jest-puppeteer.config.js):

module.exports = {
  launch: {
    headless: process.env.HEADLESS !== "false",
    dumpio: true, // Show browser console logs
  },
  browserContext: "default", // Use "incognito" if you want isolated sessions per test
  server: {
    command: "node server.js",
    port: 4444,
    launchTimeout: 10000,
    debug: true,
  },
};

You can further extend this configuration to connect to a remote instance of Chrome or customize the environment for your test runs.

API Reference

Jest-Puppeteer exposes several global objects and methods to facilitate test writing:

These methods simplify the setup and teardown process for tests, making it easier to work with Puppeteer in a Jest environment.

Troubleshooting

CI Timeout Issues

In CI environments, tests may occasionally time out due to limited resources. Jest-Puppeteer allows you to control the number of workers used to run tests. Running tests serially can help avoid these timeouts:

Run tests in a single process:

jest --runInBand

Alternatively, you can limit the number of parallel workers:

jest --maxWorkers=2

This ensures that your CI environment doesn’t get overloaded by too many concurrent processes, which can improve the reliability of your tests.

Debugging CI Failures

Sometimes, failures happen only in CI environments and not locally. In such cases, use the debug() method to open a browser during CI runs and inspect the page manually:

await jestPuppeteer.debug();

To avoid test timeouts in CI, set a larger timeout during the debugging process:

jest.setTimeout(600000); // 10 minutes

Preventing ESLint Errors with Global Variables

Jest-Puppeteer introduces global variables like page, browser, context, etc., which ESLint may flag as undefined. You can prevent this by adding these globals to your ESLint configuration:

// .eslintrc.js
module.exports = {
  env: {
    jest: true,
  },
  globals: {
    page: true,
    browser: true,
    context: true,
    puppeteerConfig: true,
    jestPuppeteer: true,
  },
};

This configuration will prevent ESLint from throwing errors about undefined globals.

Acknowledgements

Special thanks to Fumihiro Xue for providing an excellent Jest Puppeteer example, which served as an inspiration for this package.