Frontend Development 13 min read

Using Chrome DevTools Recorder to Boost Development Efficiency with Puppeteer

This article introduces Chrome DevTools' experimental Recorder feature, explains what Puppeteer is, provides a step‑by‑step guide to record, edit, import, and export automation scripts, and demonstrates how the generated Puppeteer code can streamline front‑end testing and reduce manual effort.

TAL Education Technology
TAL Education Technology
TAL Education Technology
Using Chrome DevTools Recorder to Boost Development Efficiency with Puppeteer

Chrome has become the de‑facto standard browser and its DevTools are essential for front‑end developers; the new experimental Recorder tab can capture user actions and export them as Puppeteer scripts.

Puppeteer is a Node.js library that controls Chrome/Chromium for automation, works on headless servers, and can replace repetitive manual clicks.

The guide shows how to enable Recorder (Chrome 97+), record a navigation to https://www.thethinkacademy.com, click elements such as "Courses" and "Load More", and end the recording; it also explains editing the recorded steps to add a missing navigation when a new tab is opened.

Recorder offers import and export functions: JSON can be re‑imported, while other formats (including a Puppeteer script) are for one‑time use. The article presents the exported script and explains its structure.

The generated script launches a browser, sets the viewport, navigates, waits for elements, scrolls into view, clicks, and finally closes the browser.

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": 1596,
            "height": 1094
        })
    }
    {
        const targetPage = page;
        const promises = [];
        promises.push(targetPage.waitForNavigation());
        await targetPage.goto("https://www.thethinkacademy.com/");
        await Promise.all(promises);
    }
    {
        const targetPage = page;
        await waitForElement({
            "type": "waitForElement",
            "selectors": ["body"]
        }, targetPage, timeout);
    }
    {
        const targetPage = page;
        const promises = [];
        promises.push(targetPage.waitForNavigation());
        await scrollIntoViewIfNeeded([["aria/Courses[role=\"link\"]"], ["#topHeader > div.navigation > div > div.right > div > div:nth-child(1) > a"], ["xpath///*[@id=\"topHeader\"]/div[2]/div/div[2]/div/div[1]/a"]], targetPage, timeout);
        const element = await waitForSelectors([["aria/Courses[role=\"link\"]"], ["#topHeader > div.navigation > div > div.right > div > div:nth-child(1) > a"], ["xpath///*[@id=\"topHeader\"]/div[2]/div/div[2]/div/div[1]/a"]], targetPage, { timeout, visible: true });
        await element.click({ offset: { x: 26.40625, y: 22 } });
        await Promise.all(promises);
    }
    {
        const targetPage = page;
        await targetPage.goto("https://www.thethinkacademy.com/courses/list");
    }
    {
        const targetPage = page;
        await targetPage.keyboard.down("Shift");
    }
    {
        const targetPage = page;
        await targetPage.keyboard.down("Meta");
    }
    {
        const targetPage = page;
        await scrollIntoViewIfNeeded([["aria/Load More", "aria/[role=\"generic\"]"], ["#locale__us > div.courses-wrapper > div.wrapper.courses-results-wrapper > div.load-more > button > span"], ["xpath///*[@id=\"locale__us\"]/div[2]/div[4]/div[2]/button/span"], ["text/Load More"]], targetPage, timeout);
        const element = await waitForSelectors([["aria/Load More", "aria/[role=\"generic\"]"], ["#locale__us > div.courses-wrapper > div.wrapper.courses-results-wrapper > div.load-more > button > span"], ["xpath///*[@id=\"locale__us\"]/div[2]/div[4]/div[2]/button/span"], ["text/Load More"]], targetPage, { timeout, visible: true });
        await element.click({ offset: { x: 43.21875, y: 8 } });
    }
    {
        const targetPage = page;
        await targetPage.keyboard.down("Meta");
    }
    {
        const targetPage = page;
        await targetPage.keyboard.down("Shift");
    }
    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(selectors, frame, timeout) {
        const element = await waitForSelectors(selectors, frame, { visible: false, timeout });
        if (!element) {
            throw new Error('The element could not be found.');
        }
        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;
        const timeoutId = setTimeout(()=>{ isActive = false; }, timeout);
        while (isActive) {
            const result = await fn();
            if (result) {
                clearTimeout(timeoutId);
                return;
            }
            await new Promise(resolve=>setTimeout(resolve, 100));
        }
        throw new Error('Timed out');
    }
    async function changeSelectElement(element, value) {
        await element.select(value);
        await element.evaluateHandle(e=>{ e.blur(); e.focus(); });
    }
    async function changeElementValue(element, value) {
        await element.focus();
        await element.evaluate((input,value)=>{ input.value = value; input.dispatchEvent(new Event('input',{bubbles:true})); input.dispatchEvent(new Event('change',{bubbles:true})); }, value);
    }
    async function typeIntoElement(element, value) {
        const textToType = await element.evaluate((input,newValue)=>{
            if (newValue.length <= input.value.length || !newValue.startsWith(input.value)) {
                input.value = '';
                return newValue;
            }
            const originalValue = input.value;
            input.value = '';
            input.value = originalValue;
            return newValue.substring(originalValue.length);
        }, value);
        await element.type(textToType);
    }
})();

Using Recorder dramatically reduces the time required to write end‑to‑end tests, allowing developers to generate reliable scripts quickly and focus on higher‑level testing tasks.

frontendPuppeteerautomationTestingChromeRecorder
TAL Education Technology
Written by

TAL Education Technology

TAL Education is a technology-driven education company committed to the mission of 'making education better through love and technology'. The TAL technology team has always been dedicated to educational technology research and innovation. This is the external platform of the TAL technology team, sharing weekly curated technical articles and recruitment information.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.