QA Automation with Flagpole and Puppeteer

Setting up headless browser testing with Flagpole is really easy. So let’s walk through setting up scenario from start to finish.

Sep 5 · 5 min read

First thing’s first, let’s very quickly jump through getting Flagpole set up in your project. I’m going to assume you already have your project initialized with npm, so we’ll skip that.

Install Flagpole both globally and as a dev dependency in your project:

npm i -g flagpole
npm i --save-dev flagpole

You also need to install Puppeteer as a dev dependency:

npm i --save-dev puppeteer

Next, you need to initialize Flagpole in your project. The CLI will walk you through setting this up. So just run the following command and answer the questions appropriately.

flagpole init

Now you will have a project with Flagpole ready to go, but no test suites created yet. Let’s fix that.

flagpole add suite

For the purposes of this example suite, I want to test npmjs.com and make sure we can do basic things like search. So here’s my answers:

Once you run this command, you will find a new npmjs.js file has been added to your tests folder. It is named this way because that is what I put for the name of my suite. In it there is the scaffold of a basic test suite.

We could actually go ahead and run this test right now, although it wouldn’t do much. But let’s do it:

flagpole run -s npmjs

And the result looks like this:

By default the Puppeteer test will run in headless mode, and you won’t see anything. However, I often find that it’s easier to write the test initially in headful mode so we can see what we’re doing right or wrong. To do this, we will create an opts object and define a few things.

const opts = {
headless: false,
width: 1280,
height: 600
};

And then we will pass these opts in as the second argument on this line:

suite.browser("Search for Flagpole", opts)

If you run that same command again, this time you’ll see Chromium flash up there and then go away after it runs. Awesome. So now let’s go forward and do something meaningful in this test.

Our actual test assertions will go inside of the next method. And you can chain those next methods, as you see as I go further here. I like to add a first argument to those methods to self document what we’re doing in each next block. So I’m going to do that like this:

.next('Verify homepage looks valid', async context => {

Now I’m going to look for a few key things just as a smoke test. I’ll put a lot of comments in some you know what each line is doing.

.next('Verify homepage looks valid', async context => {
// Wait for the page to finish loading and find the hero section
const heroContents = await context
.waitForExists('#hero_contents');
// Inside of the hero, grab the h1 and link to the products page
const h1 = await heroContents.find('h1');
const cta = await heroContents.find('a[href="/products/"]');
// We expect the h1 will exist
context.assert(h1).exists();
// Text in h1 will be similar to this, but ignore caps and spaces
context.assert(await h1.getText()).like('build amazing things');
// The products link should exist too
context.assert(cta).exists();
// And it should have this text, capitalization matters
context.assert(await cta.getText()).equals('See plans');
})

Now let’s add a second next block and do a search.

.next('Fill out the search box and submit', async context => {
// Find the search form and assert it
const form = await context.find('form#search');
context.assert(form).exists();
// Within search form, find the text box and make sure it exists
const input = await form.find('input[name="q"]');
context.assert(input).exists();
// Type search term in the field and make sure its value matches
await input.type('Flagpole');
context.assert(await input.getValue()).equals('Flagpole');
// Now submit the form and wait for it to submit
return form.submit();
})

Before we check our final result, let’s two more next blocks to wait for the search results page to load and verify that Flagpole shows up in the search results list.

.next('Validate search results page', async context => {
// Wait for the new page to stop loading
await context.waitForNavigation();
// Look for the main element and get the header + results list
const main = await context.waitForExists('main');
const heading = await main.find('h2');
const searchResults = await main.findAll('section');
// The header should say something like "2 packages found"
context.assert(await heading.getText())
.contains('packages found');
// We should see at least one result (currently there is 2)
context.assert('Should be search results', searchResults)
.length.greaterThan(0);
// Return the first search result for the following next block
return searchResults[0];
})
.next('First result should be Flagpole', async context => {
// We returned the first result from the last next block...
// so we can retrieve it with context.result
// passing the reference you'll need next is a good practice
const firstResult = await context.result;
// Under that first result, we'll find a link with a title in it
const title = await firstResult.find('h3');
const link = await title.getParent();
// Make sure our title existed and has the value we expect
context.assert('Found search result title', title).exists();
context.assert(
'Title should say exactly flagpole',
await title.getText()
).exactly('flagpole');
// The parent of the title should be a link, check that is true
context.assert('Parent of title should exist', link)
.exists();
context.assert(
'Parent of title should be an <a> tag',
await link.getTagName()
).equals('a');
// Verify the link is to the Flagpole project
context.assert(
'Href attribute of link should point to flagpole package',
await link.getAttribute('href')
).contains('/package/flagpole');
});

Okay. So let’s put it all together and run it. It should pass!

I hope this helped! Here is the full source for this sample suite.

Jason Byrne

Written by

Entrepreneur, developer, historian, journalist, Christian, family man, and track & field fan. VP of Software Development @ FloSports. Founder of MileSplit.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade