Run Puppeteer tests recorded with Chrome DevTools
Learn how to integrate your Puppeteer test scripts recorded using Chrome devtools recorder with BrowserStack Automate.
Chrome DevTools is a set of web developer tools built directly into the Google Chrome browser. Recorder is once such tool that allows you to record and replay the actions as performed in the Chrome browser.
In this guide you will learn how to:
- Record browser actions and generate scripts
- Modify the script to integrate with BrowserStack
- Run your test
- View your results
Prerequisites
- Chrome version 89 or higher to enable Recorder
- BrowserStack Username and Access key, which you can find in your account settings. If you have not created an account yet, you can sign up for a Free Trial or purchase a plan.
- Node version 14 or later installed.
Record browser actions and generate Puppeteer scripts
You need to enable the Recorder in your Chrome browser to record your tests. Once enabled, you can record your test case, and export the Puppeteer scripts that you can integrate with BrowserStack.
Enable Recorder
To enable recorder:
- In Chrome, click More options(vertical ellipses) and select Developer Tools from the More Tools option
- In Developer Tools, click the More options(vertical ellipses) icon and select Recorder from the More Tools option.
After enabling the Recorder, you can start recording your actions performed in the browser window.
Record and export script
In this example, we will perform the following actions and export the recording as a Puppeteer script:
- Open a Google search page
- Search the term BrowserStack
- Open the BrowserStack home page from the search results
To record and export the test script, perform the following steps:
- In Recorder, click Start new recording.
- In RECORDING NAME, enter the name for the saved recording.
- Click Start a new recording and perform the actions as per the test case.
- Click End recording to complete recording.
- Click the Export icon and select the Export as a Puppeteer script option.
- Save the puppeteer script on your local machine.
The following code snippet shows the exported script:
//my-test.js
const puppeteer = require('puppeteer'); // v13.0.0 or later
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
const timeout = 5000;
page.setDefaultTimeout(timeout);
{
const targetPage = page;
await targetPage.setViewport({"width":885,"height":794})
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await targetPage.goto("chrome://new-tab-page/");
await Promise.all(promises);
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await targetPage.goto("https://www.google.com/");
await Promise.all(promises);
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Search"],["body > div > div.o3j99.ikrT4e.om7nvf > form > div > div > div.RNNXgb > div > div.a4bIc > input"]], targetPage, { timeout, visible: true });
await scrollIntoViewIfNeeded(element, timeout);
const type = await element.evaluate(el => el.type);
if (["select-one"].includes(type)) {
await element.select("browserstack");
} else if (["textarea","text","url","tel","search","password","number","email"].includes(type)) {
await element.type("browserstack");
} else {
await element.focus();
await element.evaluate((el, value) => {
el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}, "browserstack");
}
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await targetPage.keyboard.down("Enter");
await Promise.all(promises);
}
{
const targetPage = page;
await targetPage.keyboard.up("Enter");
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
const element = await waitForSelectors([["aria/BrowserStack - Test Anytime, From Anywhere","aria/[role=\"generic\"]"],["#tads > div:nth-child(3) > div > div > div > div.v5yQqb > a > div.CCgQ5.vCa9Yd.QfkTvb.MUxGbd.v0nnCb > span"]], targetPage, { timeout, visible: true });
await scrollIntoViewIfNeeded(element, timeout);
await element.click({
offset: {
x: 76,
y: 10.8828125,
},
});
await Promise.all(promises);
}
await browser.close();
async function waitForSelectors(selectors, frame, options) {
for (const selector of selectors) {
try {
return await waitForSelector(selector, frame, options);
} catch (err) {
console.error(err);
}
}
throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors));
}
async function scrollIntoViewIfNeeded(element, timeout) {
await waitForConnected(element, timeout);
const isInViewport = await element.isIntersectingViewport({threshold: 0});
if (isInViewport) {
return;
}
await element.evaluate(element => {
element.scrollIntoView({
block: 'center',
inline: 'center',
behavior: 'auto',
});
});
await waitForInViewport(element, timeout);
}
async function waitForConnected(element, timeout) {
await waitForFunction(async () => {
return await element.getProperty('isConnected');
}, timeout);
}
async function waitForInViewport(element, timeout) {
await waitForFunction(async () => {
return await element.isIntersectingViewport({threshold: 0});
}, timeout);
}
async function waitForSelector(selector, frame, options) {
if (!Array.isArray(selector)) {
selector = [selector];
}
if (!selector.length) {
throw new Error('Empty selector provided to waitForSelector');
}
let element = null;
for (let i = 0; i < selector.length; i++) {
const part = selector[i];
if (element) {
element = await element.waitForSelector(part, options);
} else {
element = await frame.waitForSelector(part, options);
}
if (!element) {
throw new Error('Could not find element: ' + selector.join('>>'));
}
if (i < selector.length - 1) {
element = (await element.evaluateHandle(el => el.shadowRoot ? el.shadowRoot : el)).asElement();
}
}
if (!element) {
throw new Error('Could not find element: ' + selector.join('|'));
}
return element;
}
async function waitForElement(step, frame, timeout) {
const count = step.count || 1;
const operator = step.operator || '>=';
const comp = {
'==': (a, b) => a === b,
'>=': (a, b) => a >= b,
'<=': (a, b) => a <= b,
};
const compFn = comp[operator];
await waitForFunction(async () => {
const elements = await querySelectorsAll(step.selectors, frame);
return compFn(elements.length, count);
}, timeout);
}
async function querySelectorsAll(selectors, frame) {
for (const selector of selectors) {
const result = await querySelectorAll(selector, frame);
if (result.length) {
return result;
}
}
return [];
}
async function querySelectorAll(selector, frame) {
if (!Array.isArray(selector)) {
selector = [selector];
}
if (!selector.length) {
throw new Error('Empty selector provided to querySelectorAll');
}
let elements = [];
for (let i = 0; i < selector.length; i++) {
const part = selector[i];
if (i === 0) {
elements = await frame.$$(part);
} else {
const tmpElements = elements;
elements = [];
for (const el of tmpElements) {
elements.push(...(await el.$$(part)));
}
}
if (elements.length === 0) {
return [];
}
if (i < selector.length - 1) {
const tmpElements = [];
for (const el of elements) {
const newEl = (await el.evaluateHandle(el => el.shadowRoot ? el.shadowRoot : el)).asElement();
if (newEl) {
tmpElements.push(newEl);
}
}
elements = tmpElements;
}
}
return elements;
}
async function waitForFunction(fn, timeout) {
let isActive = true;
setTimeout(() => {
isActive = false;
}, timeout);
while (isActive) {
const result = await fn();
if (result) {
return;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error('Timed out');
}
})();
Integrate script with BrowserStack
For running the script succesfully on BrowserStack, add required parameters , such as Browser and OS, and BrowserStack credentials.
In the exported script, puppeteer.launch
is used to start a browser instance, whereas, puppeteer.connect
is used to start a browser instance on BrowserStack along with other capabilities set in the script.
As you can see in the snippet, you need to use puppeteer.connect
to connect to the CDP endpoint at BrowserStack. The caps
variable is used to send the additional capabilities to BrowserStack so that the specific browser/OS combination can be assigned to your test.
Add the following capabilities and BrowserStack hub URL to your script:
const caps = {
'browser': 'chrome',
'browser_version': 'latest',
'os': 'os x',
'os_version': 'mojave',
'browserstack.username': process.env.BROWSERSTACK_USERNAME || '<YOUR_USERNAME>',
'browserstack.accessKey': process.env.BROWSERSTACK_ACCESS_KEY || '<YOUR_ACCESS_KEY>'
};
const browser = await puppeteer.connect({
browserWSEndpoint:
`ws://cdp.browserstack.com/puppeteer?caps=${encodeURIComponent(JSON.stringify(caps))}`,
});
Using the imported script from the previous step, we have added parallels to it so as to check if the same test would run on different operating systems.
The following sample scripts includes all necessary changes to be made for the script to work on BrowserStack:
'use strict';
const { strict } = require('once');
const puppeteer = require('puppeteer');
const expect = require('chai').expect;
(async () => {
const caps = {
'browser': 'chrome', // You can choose `chrome`, `edge` or `firefox` in this capability
'browser_version': 'latest', // We support v83 and above. You can choose `latest`, `latest-beta`, `latest-1`, `latest-2` and so on, in this capability
'os': 'os x',
'os_version': 'big sur',
'build': 'DevTools recorder',
'name': 'DevTools 2', // The name of your test and build. See browserstack.com/docs/automate/puppeteer/organize tests for more details
'browserstack.username': process.env.BROWSERSTACK_USERNAME || '<YOUR_USERNAME>',
'browserstack.accessKey': process.env.BROWSERSTACK_ACCESS_KEY || '<YOUR_ACCESS_KEY>'
};
const browser = await puppeteer.connect({
browserWSEndpoint:
`wss://cdp.browserstack.com/puppeteer?caps=${encodeURIComponent(JSON.stringify(caps))}`, // The BrowserStack CDP endpoint gives you a `browser` instance based on the `caps` that you specified
});
/*
* The BrowserStack specific code ends here. Following this line is your test script.
* Here, we have a simple script that opens duckduckgo.com, searches for the word BrowserStack and asserts the result.
*/
const page = await browser.newPage();
const timeout = 5000;
page.setDefaultTimeout(timeout);
{
const targetPage = page;
await targetPage.setViewport({"width":885,"height":794})
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await targetPage.goto("chrome://new-tab-page/");
await Promise.all(promises);
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await targetPage.goto("https://www.google.com/");
await Promise.all(promises);
}
{
const targetPage = page;
const element = await waitForSelectors([["aria/Search"],["body > div > div.o3j99.ikrT4e.om7nvf > form > div > div > div.RNNXgb > div > div.a4bIc > input"]], targetPage, { timeout, visible: true });
await scrollIntoViewIfNeeded(element, timeout);
const type = await element.evaluate(el => el.type);
if (["select-one"].includes(type)) {
await element.select("browserstack");
} else if (["textarea","text","url","tel","search","password","number","email"].includes(type)) {
await element.type("browserstack");
} else {
await element.focus();
await element.evaluate((el, value) => {
el.value = value;
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
}, "browserstack");
}
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
await targetPage.keyboard.down("Enter");
await Promise.all(promises);
}
{
const targetPage = page;
await targetPage.keyboard.up("Enter");
}
{
const targetPage = page;
const promises = [];
promises.push(targetPage.waitForNavigation());
const element = await waitForSelectors([["aria/BrowserStack - Test Anytime, From Anywhere","aria/[role=\"generic\"]"],["#tads > div:nth-child(3) > div > div > div > div.v5yQqb > a > div.CCgQ5.vCa9Yd.QfkTvb.MUxGbd.v0nnCb > span"]], targetPage, { timeout, visible: true });
await scrollIntoViewIfNeeded(element, timeout);
await element.click({
offset: {
x: 76,
y: 10.8828125,
},
});
await Promise.all(promises);
}
await browser.close();
async function waitForSelectors(selectors, frame, options) {
for (const selector of selectors) {
try {
return await waitForSelector(selector, frame, options);
} catch (err) {
console.error(err);
}
}
throw new Error('Could not find element for selectors: ' + JSON.stringify(selectors));
}
async function scrollIntoViewIfNeeded(element, timeout) {
await waitForConnected(element, timeout);
const isInViewport = await element.isIntersectingViewport({threshold: 0});
if (isInViewport) {
return;
}
await element.evaluate(element => {
element.scrollIntoView({
block: 'center',
inline: 'center',
behavior: 'auto',
});
});
await waitForInViewport(element, timeout);
}
async function waitForConnected(element, timeout) {
await waitForFunction(async () => {
return await element.getProperty('isConnected');
}, timeout);
}
async function waitForInViewport(element, timeout) {
await waitForFunction(async () => {
return await element.isIntersectingViewport({threshold: 0});
}, timeout);
}
async function waitForSelector(selector, frame, options) {
if (!Array.isArray(selector)) {
selector = [selector];
}
if (!selector.length) {
throw new Error('Empty selector provided to waitForSelector');
}
let element = null;
for (let i = 0; i < selector.length; i++) {
const part = selector[i];
if (element) {
element = await element.waitForSelector(part, options);
} else {
element = await frame.waitForSelector(part, options);
}
if (!element) {
throw new Error('Could not find element: ' + selector.join('>>'));
}
if (i < selector.length - 1) {
element = (await element.evaluateHandle(el => el.shadowRoot ? el.shadowRoot : el)).asElement();
}
}
if (!element) {
throw new Error('Could not find element: ' + selector.join('|'));
}
return element;
}
async function waitForElement(step, frame, timeout) {
const count = step.count || 1;
const operator = step.operator || '>=';
const comp = {
'==': (a, b) => a === b,
'>=': (a, b) => a >= b,
'<=': (a, b) => a <= b,
};
const compFn = comp[operator];
await waitForFunction(async () => {
const elements = await querySelectorsAll(step.selectors, frame);
return compFn(elements.length, count);
}, timeout);
}
async function querySelectorsAll(selectors, frame) {
for (const selector of selectors) {
const result = await querySelectorAll(selector, frame);
if (result.length) {
return result;
}
}
return [];
}
async function querySelectorAll(selector, frame) {
if (!Array.isArray(selector)) {
selector = [selector];
}
if (!selector.length) {
throw new Error('Empty selector provided to querySelectorAll');
}
let elements = [];
for (let i = 0; i < selector.length; i++) {
const part = selector[i];
if (i === 0) {
elements = await frame.$$(part);
} else {
const tmpElements = elements;
elements = [];
for (const el of tmpElements) {
elements.push(...(await el.$$(part)));
}
}
if (elements.length === 0) {
return [];
}
if (i < selector.length - 1) {
const tmpElements = [];
for (const el of elements) {
const newEl = (await el.evaluateHandle(el => el.shadowRoot ? el.shadowRoot : el)).asElement();
if (newEl) {
tmpElements.push(newEl);
}
}
elements = tmpElements;
}
}
return elements;
}
async function waitForFunction(fn, timeout) {
let isActive = true;
setTimeout(() => {
isActive = false;
}, timeout);
while (isActive) {
const result = await fn();
if (result) {
return;
}
await new Promise(resolve => setTimeout(resolve, 100));
}
throw new Error('Timed out');
}
})();
Run your test
Save your test as mytest.js
and use the following command to run your integrated test:
node mytest.js
View test results
After running your test on BrowserStack, you can view results on the Automate Dashboard.
We're sorry to hear that. Please share your feedback so we can do better
Contact our Support team for immediate help while we work on improving our docs.
We're continuously improving our docs. We'd love to know what you liked
We're sorry to hear that. Please share your feedback so we can do better
Contact our Support team for immediate help while we work on improving our docs.
We're continuously improving our docs. We'd love to know what you liked
Thank you for your valuable feedback!