jest-puppeteer
is a Jest preset that enables end-to-end testing with Puppeteer. It offers a straightforward API for launching new browser instances and interacting with web pages through them.
npm install --save-dev jest-puppeteer puppeteer jest
Add jest-puppeteer as a preset in your Jest configuration file "jest.config.js":
{
"preset": "jest-puppeteer"
}
Note Ensure you remove any existing
testEnvironment
options from your Jest configuration
To write a test, create a new file with a .test.js
extension, and include your test logic using the page
exposed by jest-puppeteer
. Here's a basic example:
import "expect-puppeteer";
describe("Google", () => {
beforeAll(async () => {
await page.goto("https://google.com");
});
it('should display "google" text on page', async () => {
await expect(page).toMatchTextContent("google");
});
});
Argos is a powerful visual testing tool that allows to review visual changes introduced by each pull request. By integrating Argos with jest-puppeteer, you can easily capture and compare screenshots to ensure the visual consistency of your application.
To get started with Argos, follow these steps:
- Install Argos GitHub App
- Install the packages
npm install --save-dev @argos-ci/cli @argos-ci/puppeteer
- Take screenshots during E2E tests with:
await argosScreenshot(page, "/screenshots/myScreenshot.png")
- Include the following command in your CI workflow to upload screenshots to Argos:
npx @argos-ci/cli upload ./screenshots
After installing Argos, learn how to review visual changes in your development workflow.
// jest-puppeteer.config.cjs
/** @type {import('jest-environment-puppeteer').JestPuppeteerConfig} */
module.exports = {
launch: {
dumpio: true,
headless: process.env.HEADLESS !== "false",
},
server: {
command: "node server.js",
port: 4444,
launchTimeout: 10000,
debug: true,
},
};
In this example, an already-running instance of Chrome is used by passing the active WebSocket endpoint to the connect
option. This can be particularly helpful when connecting to a Chrome instance running in the cloud.
// jest-puppeteer.config.cjs
const dockerHost = "http://localhost:9222";
async function getConfig() {
const data = await fetch(`${dockerHost}/json/version`).json();
const browserWSEndpoint = data.webSocketDebuggerUrl;
/** @type {import('jest-environment-puppeteer').JestPuppeteerConfig} */
return {
connect: {
browserWSEndpoint,
},
server: {
command: "node server.js",
port: 3000,
launchTimeout: 10000,
debug: true,
},
};
}
module.exports = getConfig();
It can be challenging to write integration tests with the Puppeteer API, as it is not specifically designed for testing purposes. To simplify the writing tests process, the expect-puppeteer API offers specific matchers when making expectations on a Puppeteer Page.
Here are some examples:
// Assert that the current page contains 'Text in the page'
await expect(page).toMatchTextContent("Text in the page");
// Assert that a button containing text "Home" will be clicked
await expect(page).toClick("button", { text: "Home" });
// Assert that a form will be filled
await expect(page).toFillForm('form[name="myForm"]', {
firstName: "James",
lastName: "Bond",
});
Debugging tests can sometimes be challenging. Jest Puppeteer provides a debug mode that allows you to pause test execution and inspect the browser. To activate debug mode, call jestPuppeteer.debug() in your test:
await jestPuppeteer.debug();
Remember that using jestPuppeteer.debug()
will pause the test indefinitely. To resume, remove or comment out the line and rerun the test. To prevent timeouts during debugging, consider increasing Jest's default timeout:
jest.setTimeout(300000); // Set the timeout to 5 minutes (300000 ms)
Jest Puppeteer allows to start a server before running your tests suite and will close it after the tests end. To automatically start a server, you have to add a server section to your jest-puppeteer.config.cjs
file and specify the command to start server and a port number:
// jest-puppeteer.config.cjs
/** @type {import('jest-environment-puppeteer').JestPuppeteerConfig} */
module.exports = {
server: {
command: "node server.js",
port: 4444,
},
};
Other options are documented in jest-dev-server.
To customize Puppeteer instance, you can update the jest-puppeteer.config.cjs
file.
For example, to launch Firefox browser instead of default chrome, you can set the launch.product
property to "firefox".
You can also update the browser context to use the incognito mode to have isolation between instances. Read jest-puppeteer-environment readme to learn more about the possible options.
Default config values:
// jest-puppeteer.config.cjs
/** @type {import('jest-environment-puppeteer').JestPuppeteerConfig} */
module.exports = {
launch: {
dumpio: true,
headless: process.env.HEADLESS !== "false",
product: "chrome",
},
browserContext: "default",
};
If you are using custom setup files, you must include expect-puppeteer
in your setup to access the matchers it offers. Add the following to your custom setup file:
// setup.js
require("expect-puppeteer");
// Your custom setup
// ...
// jest.config.js
module.exports = {
// ...
setupTestFrameworkScriptFile: "./setup.js",
// or
setupFilesAfterEnv: ["./setup.js"],
};
Be cautious when setting your custom setupFilesAfterEnv and globalSetup, as it may result in undefined globals. Using multiple projects in Jest is one way to mitigate this issue.
module.exports = {
projects: [
{
displayName: "integration",
preset: "jest-puppeteer",
transform: {
"\\.tsx?$": "babel-jest",
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$":
"jest-transform-stub",
},
moduleNameMapper: {
"^.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$":
"jest-transform-stub",
},
modulePathIgnorePatterns: [".next"],
testMatch: [
"<rootDir>/src/**/__integration__/**/*.test.ts",
"<rootDir>/src/**/__integration__/**/*.test.tsx",
],
},
{
displayName: "unit",
transform: {
"\\.tsx?$": "babel-jest",
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$":
"jest-transform-stub",
},
moduleNameMapper: {
"^.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$":
"jest-transform-stub",
},
globalSetup: "<rootDir>/setupEnv.ts",
setupFilesAfterEnv: ["<rootDir>/setupTests.ts"],
modulePathIgnorePatterns: [".next"],
testMatch: [
"<rootDir>/src/**/__tests_/**/*.test.ts",
"<rootDir>/src/**/__tests__/**/*.test.tsx",
],
},
],
};
If you need to use your custom environment, you can extend the PuppeteerEnvironment
.
First, create a JavaScript file for your custom environment:
// custom-environment.js
const PuppeteerEnvironment = require("jest-environment-puppeteer");
class CustomEnvironment extends PuppeteerEnvironment {
async setup() {
await super.setup();
// Your setup
}
async teardown() {
// Your teardown
await super.teardown();
}
}
module.exports = CustomEnvironment;
Next, assign your JavaScript file's path to the testEnvironment
property in your Jest configuration:
{
// ...
"testEnvironment": "./custom-environment.js"
}
Your custom setup
and teardown
will now be executed before and after each test suite, respectively.
You can create custom globalSetup
and globalTeardown
methods. For this purpose, jest-environment-puppeteer exposes the setup and teardown methods, allowing you to integrate them with your custom global setup and teardown methods, as shown in the example below:
// global-setup.js
const setupPuppeteer = require("jest-environment-puppeteer/setup");
module.exports = async function globalSetup(globalConfig) {
await setupPuppeteer(globalConfig);
// Your global setup
};
// global-teardown.js
const teardownPuppeteer = require("jest-environment-puppeteer/teardown");
module.exports = async function globalTeardown(globalConfig) {
// Your global teardown
await teardownPuppeteer(globalConfig);
};
Then assigning your js file paths to the globalSetup
and globalTeardown
property in your Jest configuration.
{
// ...
"globalSetup": "./global-setup.js",
"globalTeardown": "./global-teardown.js"
}
Now, your custom globalSetup
and globalTeardown
will be executed once before and after all test suites, respectively.
Jest Puppeteer employs cosmiconfig for configuration file support, allowing you to configure Jest Puppeteer in various ways (listed in order of precedence):
- A
"jest-puppeteer"
key in yourpackage.json
file. - A
.jest-puppeteerrc
file in either JSON or YAML format. - A
.jest-puppeteerrc.json
,.jest-puppeteerrc.yml
,.jest-puppeteerrc.yaml
, or.jest-puppeteerrc.json5
file. - A
.jest-puppeteerrc.js
,.jest-puppeteerrc.cjs
,jest-puppeteer.config.js
, orjest-puppeteer.config.cjs
file that exports an object usingmodule.exports
. - A
.jest-puppeteerrc.toml
file.
By default, the configuration is searched for at the root of the project. To define a custom path, use the JEST_PUPPETEER_CONFIG
environment variable.
Ensure that the exported configuration is either a config object or a Promise that returns a config object.
interface JestPuppeteerConfig {
/**
* Puppeteer connect options.
* @see https://pptr.dev/api/puppeteer.connectoptions
*/
connect?: ConnectOptions;
/**
* Puppeteer launch options.
* @see https://pptr.dev/api/puppeteer.launchoptions
*/
launch?: PuppeteerLaunchOptions;
/**
* Server config for `jest-dev-server`.
* @see https://www.npmjs.com/package/jest-dev-server
*/
server?: JestDevServerConfig | JestDevServerConfig[];
/**
* Allow to run one browser per worker.
* @default false
*/
browserPerWorker?: boolean;
/**
* Browser context to use.
* @default "default"
*/
browserContext?: "default" | "incognito";
/**
* Exit on page error.
* @default true
*/
exitOnPageError?: boolean;
/**
* Use `runBeforeUnload` in `page.close`.
* @see https://pptr.dev/api/puppeteer.page.close
* @default false
*/
runBeforeUnloadOnClose?: boolean;
}
Provides access to the Puppeteer Browser.
it("should open a new page", async () => {
const page = await browser.newPage();
await page.goto("https://google.com");
});
Provides access to a Puppeteer Page that is opened at the start (most commonly used).
it("should fill an input", async () => {
await page.type("#myinput", "Hello");
});
Provides access to a browser context that is instantiated when the browser is launched. You can control whether each test has its own isolated browser context using the browserContext
option in your configuration file.
A helper for making Puppeteer assertions. For more information, refer to the documentation.
await expect(page).toMatchTextContent("A text in the page");
// ...
Put test in debug mode.
- Jest is suspended (no timeout)
- A
debugger
instruction to Chromium, if Puppeteer has been launched with{ devtools: true }
it will pause
it("should put test in debug mode", async () => {
await jestPuppeteer.debug();
});
To reset global.page
before each test, use the following code:
beforeEach(async () => {
await jestPuppeteer.resetPage();
});
To reset global.browser
, global.context
, and global.page
before each test, use the following code:
beforeEach(async () => {
await jestPuppeteer.resetBrowser();
});
TypeScript is natively supported from v8.0.0, for previous versions, you have to use community-provided types.
Most Continuous Integration (CI) platforms restrict the number of threads you can use. If you run multiple test suites, the tests may timeout due to Jest attempting to run Puppeteer in parallel, and the CI platform being unable to process all parallel jobs in time.
A solution to this issue is to run your tests serially in a CI environment. Users have found that running tests serially in such environments can result in up to 50% performance improvements.
You can achieve this through the CLI by running:
jest --runInBand
Alternatively, you can set Jest to use a maximum number of workers that your CI environment supports:
jest --maxWorkers=2
Jest Puppeteer provides five global variables: browser, page, context, puppeteerConfig, and jestPuppeteer. To prevent errors related to these globals, include them in your ESLint configuration:
// .eslintrc.js
module.exports = {
env: {
jest: true,
},
globals: {
page: true,
browser: true,
context: true,
puppeteerConfig: true,
jestPuppeteer: true,
},
};
Special thanks to Fumihiro Xue for providing an excellent Jest example.