Jordan Upgrades Integration Tests and Includes Puppeteer-Firefox

Demo code herePull request showing changes

This post on further upgrading integration tests is based on the previous article that I did here where I built some integation tests with Mochajs and Puppeteer. In that post I do some testing on Citadel Packaging and then in a later post I go further into automating it by setting up Puppeteer in Digital Ocean.

Phew. That was a lot of links. I’m going to try to cool it with the links going further. This post is going to be focused on upgrading the previous code linked above so that it’s a bit better AND, the cool part, starting to use Puppeteer-Firefox (did you really not want me to link this?)!

Puppeteer-firefox

I was inspired by Google I/O where the maintainers (makers?) of Puppeteer presented and introduced some cool stuff. They discussed testing and a bit of integration tests Their full presentation is here. The first thing I did was update my setUpBrowser function to start looking for a “firefox” command line argument. I then set up some conditions to launch firefox if that option was there instead of the normal puppeteer.

async function setUpBrowser() {
    let browser: Browser;

    let ubuntu = cliArgs.includes('ubuntu');
    let headless = cliArgs.includes('headless');
    let firefox = cliArgs.includes('firefox');

    if (!headless && process.env.hasOwnProperty("PPTR_HEADLESS") && String(process.env.PPTR_HEADLESS) === 'true') {
        headless = true;
    }

    console.log('puppeteer: ');
    console.log(`    headless: ${headless}`);
    console.log(`    ubuntu: ${ubuntu}`);
    console.log(`    firefox: ${firefox}`);

    if (ubuntu && !firefox) {
        const pptrArgs: puppeteer.LaunchOptions = {
            headless: true,
            ignoreHTTPSErrors: true,
            args: [
                '--no-sandbox',
                '--disable-setuid-sandbox',
                '--disable-infobars',
                '--window-position=0,0',
                '--ignore-certifcate-errors',
                '--ignore-certifcate-errors-spki-list',
                '--user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3312.0 Safari/537.36"'
            ]
        };

        if (process.env.hasOwnProperty("PPTR_EXEC_PATH")) {
            pptrArgs.executablePath = process.env.PPTR_EXEC_PATH;
        }

        browser = await puppeteer.launch(pptrArgs);
    }
    else if (ubuntu && firefox) {
        const pptrArgs: puppeteer.LaunchOptions = {
            headless: true,
            ignoreHTTPSErrors: true,
            args: [
                '--no-sandbox',
                '--disable-setuid-sandbox',
                '--disable-infobars',
                '--window-position=0,0',
                '--ignore-certifcate-errors',
                '--ignore-certifcate-errors-spki-list',
                '--user-agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3312.0 Safari/537.36"'
            ]
        };

        if (process.env.hasOwnProperty("PPTR_EXEC_PATH")) {
            pptrArgs.executablePath = process.env.PPTR_EXEC_PATH;
        }

        browser = await puppeteerFirefox.launch();

    }
    else if (firefox) {
        const pptrArgs: puppeteer.LaunchOptions = {
            headless,
            args: [`--window-size=${1800},${1200}`]
        };

        if (process.env.hasOwnProperty("PPTR_EXEC_PATH")) {
            pptrArgs.executablePath = process.env.PPTR_EXEC_PATH;
        }

        browser = await puppeteerFirefox.launch(pptrArgs);

    }
    else {
        const pptrArgs: puppeteer.LaunchOptions = {
            headless,
            args: [`--window-size=${1800},${1200}`]
        };

        if (process.env.hasOwnProperty("PPTR_EXEC_PATH")) {
            pptrArgs.executablePath = process.env.PPTR_EXEC_PATH;
        }

        browser = await puppeteer.launch(pptrArgs);
    }

    return Promise.resolve(browser);
}

Now I could use the same set of code for both Chrome and Firefox testing. I made sure to update my scripts as below so that my command line arguments would be ready to run based on the script I was running.

Updating my scripts.

Browser context

The next cool thing I saw from the Google I/O talk was using an incognito browser context. The main goal with this was so that you have a fresh session for each test but it’s a lot faster than closing and opening a new browser each time.

Old version (top) vs new version (bottom)

I’m comparing both versions in the screen shot above. The top one was the old way without a separate browser context for each test and the bottom with the separate browser context. As you can see, the total time really doesn’t vary that much. A second longer on the bottom, which is more likely just a deviation than an actual difference.

The main code changes were to initialize the browser at a global scope and then after it’s set up in the before section, within each test I can use the createIncognitoBrowserContext to get a separate session.

        it('should have 5 tabs', async () => {
            const context = await browser.createIncognitoBrowserContext();
            page = await context.newPage();

            const url = 'https://www.citadelpackaging.com/';
            await page.goto(url);

            await page.waitForSelector('#menu-main-menu > li');
            const tabs = await page.$$('#menu-main-menu > li');

            expect(tabs.length).to.equal(5);
            await context.close();
        });

With the separate browser context I had one problem. In my previous way of doing the tests, I adding items to the cart in one tests and then removing them in a following test. The second test would open do a full page refresh and the session had saved the cart items, as expected. With the new browser context I add to cart and then do my test all within the same context, which I think is a cleaner test but because adding to cart is an ajax call, knowing when to check for the added items was kind of tricky.

This is where I used one final tidbit from what Andrey and Joel used in their I/O talk. It was the waitForResponsemethod and it worked great. In my case I just await page.waitForResponse(res => res.url().endsWith('/?wc-ajax=add_to_cart')); and then did my test as normal and it hasn’t failed yet.

Moral of the story: cool stuff from Joel and Andrey. They’re doing awesome work with Puppeteer and it’s been a pleasure to use.

Demo code herePull request showing changes

Leave a Reply

Your email address will not be published. Required fields are marked *