Easily integrate Algolia into native apps with FlutterFlow
Algolia's advanced search capabilities pair seamlessly with iOS or Android Apps when using FlutterFlow. App development and search design ...
Sr. Developer Relations Engineer
Algolia's advanced search capabilities pair seamlessly with iOS or Android Apps when using FlutterFlow. App development and search design ...
Sr. Developer Relations Engineer
In the midst of the Black Friday shopping frenzy, Algolia soared to new heights, setting new records and delivering an ...
Chief Executive Officer and Board Member at Algolia
When was your last online shopping trip, and how did it go? For consumers, it’s becoming arguably tougher to ...
Senior Digital Marketing Manager, SEO
Have you put your blood, sweat, and tears into perfecting your online store, only to see your conversion rates stuck ...
Senior Digital Marketing Manager, SEO
“Hello, how can I help you today?” This has to be the most tired, but nevertheless tried-and-true ...
Search and Discovery writer
We are proud to announce that Algolia was named a leader in the IDC Marketscape in the Worldwide General-Purpose ...
VP Corporate Marketing
Twice a year, B2B Online brings together America’s leading manufacturers and distributors to uncover learnings and industry trends. This ...
Director, Sales Enablement & B2B Practice Leader
Generative AI and large language models (LLMs). These two cutting-edge AI technologies sound like totally different, incomparable things. One ...
Search and Discovery writer
ChatGPT, Bing, Bard, YouChat, DALL-E, Jasper…chances are good you’re leveraging some version of generative artificial intelligence on ...
Search and Discovery writer
Your users are spoiled. They’re used to Google’s refined and convenient search interface, so they have high expectations ...
Technical Writer
Imagine if, as your final exam for a computer science class, you had to create a real-world large language ...
Sr. SEO Web Digital Marketing Manager
What do you think of the OpenAI ChatGPT app and AI language models? There’s lots going on: GPT-3 ...
Search and Discovery writer
In the fast-paced and dynamic realm of digital merchandising, being reactive to customer trends has been the norm. In ...
Staff User Researcher
You’re at a dinner party when the conversation takes a computer-science-y turn. Have you tried ChatGPT? What ...
Sr. SEO Web Digital Marketing Manager
It’s the era of Big Data, and super-sized language models are the latest stars. When it comes to ...
Search and Discovery writer
Did you know that 86% of the global population uses a smartphone? The 7 billion devices connected to the Internet ...
Staff SME Business & Optimization - UI/UX
The Cloud Native Foundation is known for being the organization behind Kubernetes and many other Cloud Native tools. To foster ...
TL;DR Revamp your technical documentation search experience with DocSearch! Previously only available to open-source projects, we're excited ...
Senior Engineering Manager
In the previous article of this series, we talked about how unit tests can help us test our functions easily, as well as how getting into the mindset of using unit tests can help us write purer, simpler, and more modular functions in the first place. But what happens when we need to test a system that doesn’t fit in a function? What if we can’t depend on a function’s output to tell us if it’s working? What if our function depends on state set by the UI? In these cases, it’s difficult to accurately model the complexity of how the user will navigate our application — if there isn’t a useful abstraction or simplification that we can test on, we might just have to spin up a live instance of the site and pretend to be a user.
Surely we can’t automate that process, right? It seems too manual and human-brain-dependent to do that. As it actually turns out, the solution lies not in automating anything inside your application, but in automating the input to the browser, simulating your mouse and keyboard so that the application that you’re testing can’t tell the simulator apart from a real human. This process is called browser automation. And what better tool to automate a browser than a headless version of the browser itself — the Chromium project maintains an API called Puppeteer that we’re going to use for this.
The first step is to get our site up and running with a dev instance, since we’ll want to test these things before we go to production. Since we’re just demonstrating here, I’m going to run this test on Algolia’s production homepage.
Next, we’ll set up a Node.JS program. Choose a logical location for this (probably a single folder inside the root of your project so that you can integrate it with your build process). Inside this folder, you’ll want to create an index.js
file that calls all of the tests. This way, just running node test
from the root directory of the repo will run all of your tests, and you can bury down into the folder to run more specific ones.
Now, there are some situations where it might make sense for you to make that a post-build command at the end of the build process, but these are going to be more sophisticated tests that you’ll probably want to run manually, so if you’re developing and testing locally, it’s probably best to just leave it there and let your devs manually run node test
or node test/specific-test.js
when they actually want to run the Puppeteer tests. If your application has a more complex build process that involves less testing locally and more dev instances in the cloud, it might be easier to set up a serverless project (something both Vercel and Netlify excels at), set it to read the tests as lambda functions, and then modify the tests to run when you ping that endpoint. The tests don’t actually need to be run from the same place as the application (since the tests are full-on clients), so you could make a little GUI with some buttons to trigger each test in the cloud.
After running npm install puppeteer
, you’ll need some boilerplate code. Here’s the structure around a sample test that’ll log into Algolia as me and get the credentials of some application:
const puppeteer = require('puppeteer');
const login = async ({width, height}) => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('<https://algolia.com>');
await page.setViewport({
width,
height,
deviceScaleFactor: 1
});
// test logic here
await browser.close();
};
module.exports = async () => {
login({
width: 375,
height: 667
});
login({
width: 1150,
height: 678
});
};;
A quick rundown:
login
and that runs our test. We don’t have any test logic in here right now, just the boilerplate that launches the headless Chrome browser, goes to the Algolia website, and sets the viewport to whatever we passed into the function. At the end, we close the browser so it doesn’t stay open in memory.To run this test from inside index.js
in the test
folder, we only need to run require("./login")();
. With this one-line-per-test setup, we can even group the tests into batches and trigger those batches with flags on the command line, e.g. node test --dashboard
. The sky is the limit with the customization here, so tweak it to the needs of your application.
In my case, I’m going to throw
errors whenever the tests fail and not worry about batching anything — I don’t have enough tests for it to be meaningful to focus my effort there. In your application though, it might be helpful for these test functions to return the result of the test instead of just throw
ing errors when they fail, as that’ll give you the ability to print out more detailed messages based on the tests run from index.js
. Right now, I’m aiming to make everything run silently unless there’s a problem that we need to fix.
Let’s dive a bit deeper into the actual test logic, which is arguably simpler than what you might expect:
// step 0. accept cookies so the site works like normal
await page.waitForSelector('a[href="/policies/cookies/"] + button');
await page.click('a[href="/policies/cookies/"] + button');
// step 1. wait for the log in link to appear and click on it
if (width < 960) {
await page.waitForSelector('[data-mobile-menu-button]');
await page.click('[data-mobile-menu-button]');
}
await page.waitForSelector('a[href="/users/sign_in"]');
await page.click('a[href="/users/sign_in"]');
// step 2. wait for the email input to appear on the new page, focus on it, and type the email from .env
await page.waitForSelector('input[type=email]');
await page.focus('input[type=email]')
await page.keyboard.type(process.env.email);
// step 3. focus on the password input, and type the password from .env
await page.focus('input[type=password]')
await page.keyboard.type(process.env.password);
// step 4. click the log in button
await page.click("form#new_user button[type=submit]");
// step 5. if we're on mobile, we'll need to click one more button to indicate we'd like to stay on mobile
if ((await page.url()).includes("mobile")) {
await page.waitForSelector('.continue-button');
await page.click('.continue-button');
}
// step 6. wait for the application selector to appear and click it
await page.waitForSelector('#application-select');
await page.click("#application-select");
// step 7. click on the application with the name in .env
await page.$$eval(
'div > span.options',
(options, applicationName) => {
const matchedOptions = options.filter(option => option.innerText.split("\\n")[1] == applicationName);
if (matchedOptions.length) {
matchedOptions[0].click();
} else {
console.log(`Application ${applicationName} does not exist`);
}
},
process.env.applicationName
);
// step 8. wait for the api keys button to appear on the new page, and click it
await page.waitForSelector('#overview-api-keys-link');
await page.click("#overview-api-keys-link");
// step 9. wait for the page to load and get the application id and public api key for this project
await page.waitForSelector('#api-keys-page-heading');
const [applicationID, publicAPIKey] = await page.$$eval(
'input[readonly]',
inputs => inputs.slice(0, 2).map(input => input.value)
);
// step 10. log the results of our test
if (applicationID != process.env.applicationID)
throw `Application ID is not correct. Test produced "${applicationID}", but it should have been "${process.env.applicationID}"`;
if (publicAPIKey != process.env.publicAPIKey)
throw `Public API key is not correct. Test produced "${publicAPIKey}", but it should have been "${process.env.publicAPIKey}"`;
// step 11. close the browser
await browser.close();
This isn’t a trivial example — if you read the comments above each step of the process, none of this should be too surprising. It’s very literally just mimicking the user’s behavior in a real browser, taking into account the quirks of how Algolia’s homepage handles mobile differently from desktop. All that goes along with this is an .env
file where I’ve defined the email
and password
to log in with, the name
of the intended application (which is what the user would use to find the right application), and the publicAPIKey
and applicationID
which the test should return. If all goes well, this outputs nothing. If something breaks though, it’ll throw a descriptive error so that we can jump in to fix it before the build finishes.
So what have we learned in this series? Well, there are two main types of testing:
It should be noted that these methods aren’t mutually exclusive — this particular functionality of displaying the correct API credentials to the developer in the Algolia dashboard is important enough to test with both methods. We’ve written a Puppeteer program to walk through the entire workflow from the user’s perspective, but in addition, it would be helpful to break down the hydration process of that API keys page that we scraped. If we isolated as many pure functions as possible in that page, we could test them with unit tests to ensure that the vast majority of that process never silently breaks. Since it’s pulling data from the database to display in the view, it’s probably not possible to make the whole thing pure, but that’s the benefit of using both types of testing: we don’t have to get 100% coverage with either method. As long as they cover everything together, we can be confident in our application’s durability and long-term sustainability. Happy testing!
Technical Writer
Powered by Algolia Recommend