Overview
Puppeteer is a Node.js library developed by Google for controlling headless Chrome and Chromium over the DevTools Protocol. It allows you to automate UI testing, scraping, screenshot testing, and more.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('<https://example.com>');
await page.screenshot({path: 'example.png'});
await browser.close();
})();
Launching Browser
Launch a headless browser instance:
const browser = await puppeteer.launch();
Launch a full version of Chrome:
const browser = await puppeteer.launch({
headless: false
});
Launch browser with custom args:
const browser = await puppeteer.launch({
args: ['--start-maximized']
});
Custom launch options:
codepuppeteer.launch({
executablePath: '/path/to/Chrome',// Custom Chrome binary
product: 'firefox'// Launch Firefox instead
});
Creating Pages
Create a new page:
const page = await browser.newPage();
Create incognito page:
const context = await browser.createIncogniteBrowserContext();
const page = await context.newPage();
Access an existing page:
const pages = await browser.pages();
const page = pages[0];
Tabs
Switch between tabs/bring them into focus:
await page1.bringToFront();
await page2.bringToFront();
Actions
Navigate to URL:
await page.goto('<https://example.com>');
Click on element:
await page.click('#element');
Type into input:
await page.type('#input', 'Text');
Press keyboard keys:
await page.keyboard.press('Shift');
Upload files:
await page.setInputFiles('#upload', ['/path/to/file1', '/path/to/file2']);
Execute Javascript code on page:
const result = await page.evaluate(() => {
return document.querySelector('#result').textContent;
});
Hover over element:
await page.hover('#element');
Capture screenshot:
await page.screenshot({path: 'screenshot.png'});
Emulate mobile device:
await page.emulate(puppeteer.devices['iPhone 6']);
Scroll into view:
await page.evaluate(el => el.scrollIntoView(), await page.$('.item'));
Type in iframe:
const frame = page.frames().find(f => f.name() === 'frame');
await frame.$eval('#input', el => el.value = 'Text');
Tap element on mobile:
await page.touchscreen.tap(200, 75);
Trigger drag and drop:
await page.mouse.down();
await page.mouse.move(0, 100);
await page.mouse.up();
Selectors
Get element by CSS Selector:
const nav = await page.$('nav');
Get multiple elements:
const items = await page.$$('.item');
Use XPath selectors:
const button = await page.$x('//*[@id="button"]');
Get text content:
const text = await page.textContent('.results');
Advanced Selectors
Use text selector:
const link = await page.$('a:text("Next")');
Visibility selector:
const hidden = await page.$('.element:hidden');
Attribute selector:
const checkbox = await page.$('input[type="checkbox"]');
XPath selector:
const submit = await page.$x('//button[@type="submit"]');
Get by text content:
const p = await page.$eval('p', el => el.innerText === 'Hello');
Query shadow DOM:
const shadow = await page.$(.element/shadow-root');
const text = await shadow.$eval('.text', el => el.textContent);
Accessibility Testing
Audit for issues:
const issues = await page.accessibility.audit({
runA11yChecks: true
});
expect(issues).toHaveLength(0);
Check colors contrast:
const contrastratio = await page.$eval('.button', button => {
const bgColor = window.getComputedStyle(button).backgroundColor;
// compute contrast ratio
});
expect(contrastratio).toBeGreaterThan(4.5);
Tab focus order:
await page.keyboard.press('Tab');
const active = await page.evaluate(() => document.activeElement.id);
expect(active).toBe('username');
Debugging & Reporting
Trace console errors:
page.on('console', msg => {
if (msg.type() === 'error') {
console.error(msg.text());
}
});
HTML report generation:
const html = '<h1>Test Report</h1>';
fs.writeFileSync('report.html', html);
Track test coverage:
const coverage = await page.coverage.startJSCoverage();
// run tests
const results = await coverage.stopJSCoverage();
Waiting
Wait for navigation:
await page.waitForNavigation();
Wait for selector:
await page.waitForSelector('div.loaded');
Wait fixed time:
await page.waitFor(1000); // Wait for 1 second
For function result:
await page.waitForFunction(() => window.fetchDone);
For XHR request:
await page.waitForRequest(request => request.url() === 'data.json');
Navigation with timeout:
await page.waitForNavigation({timeout: 60000});
Element for 30 seconds:
await page.waitForSelector('.item', {timeout: 30000});
Frames
Get page frames:
const frames = page.mainFrame().childFrames();
Set current frame:
const frame = page.frames().find(f => f.name() === 'frame');
await frame.evaluate(() => {
// Run code inside frame
});
Inputs
Get HTML/text/attributes of element:
const html = await page.$eval('.item', el => el.outerHTML);
const text = await page.$eval('.item', el => el.innerText);
const class = await page.$eval('.item', el => el.getAttribute('class'));
Fill out and submit form:
await page.type('#input', 'Text');
await page.click('#submitButton');
Samples
Take screenshot of element:
const el = await page.$('.element');
await el.screenshot({path: 'element.png'});
Emulate device and viewport:
const devices = puppeteer.devices;
const iPhone = devices['iPhone 6'];
await page.emulate(iPhone);
await page.setViewport(iPhone.viewport);
Fetch resource timing data:
const metrics = await page.metrics();
const requests = metrics.requestfinished;
PDFs
Generate PDF report:
await page.pdf({
path: 'page.pdf',
format: 'A4'
});
Landscape oriented PDF:
await page.pdf({
path: 'page.pdf',
landscape: true
});
Events
Page load event:
page.once('load', () => {
// Page loaded completely
});
Network request failed event:
page.on('requestfailed', request => {
console.log(request.url + ' ' + request.failure().errorText);
});
Console message event:
page.on('console', msg => {
console.log(`${msg.type()} ${msg.text()}`);
});
Authentication
Set user agent:
await page.setUserAgent('CustomAgent');
Set custom headers:
await page.setExtraHTTPHeaders({
'Accept-Language': 'en-US'
});
Set cookies:
await page.setCookie({name: 'session', value: '1234'});
Set credentials:
await page.authenticate({
username: 'user',
password: 'pass'
});
Set bypass CSP:
await browser.launch({ignoreHTTPSErrors: true});
Use proxy server:
await page.authenticate({username: 'user', password: 'pass'});
Network
Set cache disabled:
await page.setCacheEnabled(false);
Set throttling rate:
await page.setRequestInterception(true);
page.on('request', request => {
request.continue({
throttling: 0.5 // 50% slower
});
});
Mock response:
page.on('request', interceptedRequest => {
interceptedRequest.respond({
contentType: 'text/html',
body: '<html>Mock page</html>'
});
});
Mock redirect response:
await page.route('**/*', route => {
route.continue({ url: '/mock-page.html' });
});
Mock 404 status:
page.on('request', route => {
route.abort('notfound');
});
Advanced Usage
Wait for more complex conditions:
// Wait for text content to change
await page.waitForFunction(selector => {
return document.querySelector(selector).textContent === 'Updated';
}, {}, selector);
// Wait until no network requests for 500ms
await page.waitForTimeout(500);
Handle popups and new tabs:
page.on('dialog', dialog => {
dialog.accept(); // or dismiss()
});
const [popup] = await Promise.all([
new Promise(resolve => browser.once('targetcreated', target => resolve(target.page()))),
page.click('#open-popup'), // clicks a button that opens a popup
]);
await popup.waitForSelector('h1'); // wait for popup content
Stabilize flaky tests using automatic retries:
// Automatically retry failed steps up to 4 times
const autoRetry = require('puppeteer-autoretry');
autoRetry.setDefaults({ retries: 4 });
await autoRetry(page).type('#input', 'Text');
Touch Interactions
Tap on element:
await page.tap('button');
Scroll on mobile:
await page.touchscreen.scroll(50, 100);
Drag and drop:
await page.touchscreen.down();
await page.touchscreen.move(50, 100);
await page.touchscreen.up();
Geolocation and Permissions
Set geolocation:
await page.setGeolocation({latitude: 0, longitude: 0});
Grant camera access:
await page.grantPermissions(['camera']);
Advanced Use Cases
Submit forms and upload files:
// Submit form
await page.type('#email', '[email protected]');
await page.click('#submit');
// Upload file
const input = await page.$('input#file');
input.uploadFile('/path/to/file.txt');
Scrape content from site:
// Extract text from all p elements
const texts = await page.$$eval('p', elements => {
return elements.map(el => el.textContent);
});
Cross-browser visual testing:
const devices = puppeteer.devices;
for (const browserType of ['chromium', 'firefox', 'webkit']) {
const browser = await puppeteer.launch({browserType});
// Emulate devices and test
}
Visual Regression Testing
Compare screenshots:
const screenshot = await page.screenshot();
const diff = await visualDiff.compare(screenshot, 'baseline.png');
expect(diff.misMatchPercentage).toBeLessThan(0.01);
Parallel Testing
Test in parallel:
const browser = await puppeteer.launch();
const pagePromises = [
browser.newPage(),
browser.newPage()
];
const pages = await Promise.all(pagePromises);
// Run tests in parallel
await Promise.all([
pages[0].goto('url1'),
pages[1].goto('url2')
])
Tips and Tricks
Speed up executing by persisting context:
// Persist browser context
const browserContext = await browser.createIncogniteBrowserContext();
await browserContext.close();
await browserContext.waitForTarget(page => page.url() === 'about:blank');
// Restore context
const page = await browserContext.newPage();
Profile CPU usage during test:
await page.profiling.start({path: 'trace.json'});
// Run CPU intensive tasks
await page.profiling.stop();
Use incognito context:
const context = await browser.createIncogniteBrowserContext();
const page = await context.newPage();
Transfer cookies between contexts:
const context1 = await browser.createIncogniteBrowserContext();
const context2 = await browser.createIncogniteBrowserContext();
await context1.addCookies([cookieObj1, cookieObj2]);
const transferred = await context2.transferCookies(context1);
Work with persistent context:
const context = await browser.createPersistentContext();
await context.close();
// Restore later
const page = await context.newPage();
Test Automation Strategies
Reusable Page Object:
class LoginPage {
constructor(page) {
this.page = page;
}
async login(username, password) {
await this.page.type('#username', username);
await this.page.type('#password', password);
await this.page.click('#submit');
}
}
// Usage:
const loginPage = new LoginPage(page);
await loginPage.login('user1', '123456');
Synchronizing test sequence:
const [response] = await Promise.all([
page.click('#submit'), // returns after request sent
page.waitForNavigation() // resolves after page load
]);
// Assert response after navigation
expect(response.status()).toBe(200);
Retry failed test cases:
for (let retry = 0; retry < 3; retry++) {
try {
await loginPage.login('invalid', 'password');
break; // Test passed, so we break
} catch (error) {
if (retry === 2) {
throw error; // Fail after 3 retries
}
// retry test otherwise
}
}