Skip to main content

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:

Prerequisites

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:

  1. In Chrome, click More options(vertical ellipses) and select Developer Tools from the More Tools option
  2. In Developer Tools, click the More options(vertical ellipses) icon and select Recorder from the More Tools option. Enable Recorder

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:

  1. In Recorder, click Start new recording. Start new recording
  2. In RECORDING NAME, enter the name for the saved recording.
  3. Click Start a new recording and perform the actions as per the test case.Start recording
  4. Click End recording to complete recording.
  5. Click the Export icon and select the Export as a Puppeteer script option. Export Puppeteer script
  6. 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))}`,
});
Note: Check out the list of supported Puppeteer capabilities to run your tests on BrowserStack.

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');
    }
})();
Important: Currently, the DevTools Chrome Recorder scripts do not support assertions.

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






Thank you for your valuable feedback

Is this page helping you?

Yes
No

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!

Talk to an Expert
Download Copy