Browser automation is an invaluable tool. At a personal level, you can use it to automate repetitive tasks online. But browser automation can deliver so much more. At its best, it can run tasks with consistent results. Tasks that need lots of manual execution and complexity. Tasks like checking button placement, evaluating user login, and registration workflows. And in our modern era, we have our pick of browser automation frameworks.
We’ve covered browser automation before, but in this guide, we’ll do a deep dive into one of those frameworks. Additionally, we’ll cover using those testing frameworks alongside a specific continuous integration application.
In this guide we’ll walk through three testing scenarios using Selenium and GitHub Actions:
- Check if a button rendered
- Test a login workflow
- Ensure the contents of a sent email
We’ll use our sample application to make our examples concrete, informative, and useful.
Repo URL: https://github.com/mailsac/mailsac-capture-service-example-nextjs
With all that said, let’s get started!
First, let’s talk about what you need to enable GitHub Actions.
Getting started with GitHub Actions
First, if you’d like a primer on GitHub Actions their page is a great resource. To summarize, it’s an easy way to run tasks when someone takes an “action” against your repo.
Actions can be:
- Pull request merges
- Commits
- Repo pushes
GitHub takes your action and runs a task like integration tests, unit tests, etc. All you need to do is create a .github/workflows/main.yml
file. Note: Running tasks is just one of the many possibilities for GitHub Actions. You can encompass entire workflows and even produce packages.
We’ll introduce more details further along the guide. For now, you can start by adding an empty file at:
.github/workflows/main.yml
The Sample Application We’ll Use
We’ll fork our sample application at the URL:
https://github.com/mailsac/mailsac-capture-service-example-nextjs
We’ll use this repo to walk through the examples mentioned in the intro. These sample scenarios are not meant to be comprehensive, of course. They’re meant to kick-start your journey with GitHub Actions.
To start, you’ll need to run the standard node installation and run the command:
npm install
npm run dev
Navigate to http://localhost:3000
and see our application in action:
With the preliminary steps done, on to the tests.
First Selenium Test: Check if the button rendered
Let’s start with the simplest test, a button render assertion test.
For this first section, we will:
- Add the node packages required to test
- Add our selenium test
- Add testing scripts to our package.json so that GitHub Actions can call our tests
- Add a simple Github Actions workflow file
Let’s start by adding a new branch (or you can fork the repo if you’d like):
git checkout -B cicd-selenium-app-test
Now let’s move on to our sample packages.
Packages: Selenium WebDriver & Start-Server-And-Test
We’ll need to add selenium and a start-server-and-test package. start-server-and-test
starts our application. Then it calls our selenium tests so they can run against our running application.
npm install --save-dev start-server-and-test selenium-webdriver
start-server-and-test
is one of our most straightforward ways to add live server testing capabilities. If you need more flexible frameworks, you may want to investigate mocha or cypress.
We’ll just focus on the core GitHub actions and selenium testing in this guide.
Selenium Test: Button Render
On to the test itself.
Let’s create a test under tests/button-render.js
using selenium and headless chrome:
const chrome = require('selenium-webdriver/chrome');
const {Builder, Browser, By } = require('selenium-webdriver');
const screen = {
width: 1920,
height: 1080
};
(async function buttonRender() {
let driver = await new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(new chrome.Options().headless().windowSize(screen))
.build();
try {
await driver.get('<http://localhost:3000>');
let didSendButtonRender = await driver.findElement(By.id('sendbutton')).isDisplayed()
if (!didSendButtonRender){
throw new Error(`Send button was not rendered properly.`);
}
} finally {
await driver.quit();
}
})();
tests/button-render.js
You can find the full usage guide on selenium’s documentation page.
To help the test out, we’ll add an ID attribute to the Send
button:
...
<button
onClick={sendEmail}
id="sendbutton"
className="mt-3 w-full inline-flex items-center justify-center px-4 py-2 border border-transparent shadow-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
>
Send
</button>
...
pages/index.js
Telling GitHub Actions How to Run Our Tests
We’ll need to add a couple of testing scripts to package.json
:
...
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "node tests/button-render.js",
"e2e-test": "start-server-and-test dev <http://localhost:3000> test"
},
...
package.json
Now let’s add the workflow main.yml
file to the repo. This is the main file that kicks off a GitHub Action workflow. To keep things simple, we’ll execute our actions based on when a comitter pushes code. You can find the full list over at GitHub’s documentation pages.
on: [push]
jobs:
tests:
runs-on: ubuntu-latest
name: Run Selenium Tests
steps:
- name: Start selenoid
uses: Xotabu4/selenoid-github-action@v2
- uses: actions/checkout@v1
- run: npm ci
- name: Run end to end tests
run: npm run e2e-test
.github/workflows/main.yml
For our project, we’ll use solenoid-github-action, a GitHub Action that starts a selenium grid instance in a docker container. Solenoid is a golang reimplementation of Selenium. It makes it very easy to integrate with any continuous integration/deployment environment.
In the last portion of the file, the npm run e2e-test
section kicks off the end-to-end test that starts our server and runs the selenium tests.
That’s it! Before you commit and push your code, try running it locally:
npm run e2e-test
The test should pass in your local environment. If it fails due to chrome driver issues you can find a full guide on browser driver installations here.
Send it over to GitHub via git push.
git push --set-upstream origin cicd-selenium-app-test
Head over to your repo on GitHub and under the Actions
tab you should now see a selenium test run
You can drill down and see where our specific tests ran
Congrats! That completes our first successful test using selenium and GitHub actions. Let’s move on to something a bit more useful.
Second Selenium Test: Open and login into a web app
Let’s expand on our tests a bit. Since we’re aiming for simplicity in this guide, I won’t add a whole authentication workflow to our application. Instead, let’s focus on an existing website and attempt (and fail) to log in.
We’ll start by adding a new test to our repo.
Selenium Test: Login to Dev.to
Let’s bring in our external login Selenium test, but slightly modified:
const chrome = require('selenium-webdriver/chrome');
const {Builder, Browser, By, until } = require('selenium-webdriver');
const screen = {
width: 1920,
height: 1080
};
(async function externalLogin() {
let driver = await new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(new chrome.Options().headless().windowSize(screen))
.build();
try {
await driver.get('<https://dev.to>');
await driver.findElement(By.linkText('Log in')).click();
await driver.wait(until.titleContains('Welcome - DEV Community'), 3000);
await driver.findElement(By.name('commit')).click();
await driver.wait(until.titleIs(''), 3000);
let errorBox = await driver.findElement(By.className('registration__error-notice'));
await driver.wait(until.elementIsVisible(errorBox));
let errorText = await errorBox.getText();
if (!errorText.includes('Unable to login')){
throw new Error(`Error text does not contain expected value "${errorText}"`);
}
}
finally {
await driver.quit();
}
})();
tests/external-login.js
Only real changes are that we added a headless option for chrome.
Modifying Our Tests
Keeping things simple, let’s just add a new test script:
....
"test": "node tests/button-render.js",
"external-tests": "node tests/external-login.js",
...
package.json
And add a separate testing task to our workflow:
...
- name: Run external login test
run: npm run external-tests
...
.github/workflows/main.yml
Let’s check our result on GitHub:
Looks like a failure due to an incorrect title.
Let’s make the match less precise and rerun the test:
...
await driver.findElement(By.linkText('Log in')).click();
await driver.wait(until.titleContains('Welcome! - DEV Community'), 3000);
await driver.findElement(By.name('commit')).click();
...
tests/external-login.js
Let’s try another git push and..
Success!
Conclusion
This was the first part of a two-part series about diving into GitHub Actions and Selenium tests. In the second part, we’ll run through:
- running our application in GitHub actions
- sending emails via our web form
- reading and comparing the email contents
- deleting the email contents afterward
All are driven by our selenium tests.
We hope you enjoyed the guide, and hope to hear from you on our forums or follow our other content on LinkedIn.