.. | ||
.eslintrc | ||
config.js | ||
README.md | ||
setup.js | ||
test-login.js | ||
tunnel.js |
TL;DR
Copy the contents of .env-template
to .env
and fill up the missing keys with
information from your SauceLabs account.
$ npm install
$ npm run tunnel
$ npm test && git commit
TODO
- Use gulp to parallelize mocha invocations with different browsers
- Figure out good system for changing subdomain through test scripts
Welcome to our test suite, let me be your guide
Dear reader, first of all thanks for taking your time reading this document. The purpose of this document is to give you an overview on what we want to test and how we are doing it.
How it works (bird's-eye view)
You will notice that the setup is a bit convoluted. This section will explain you why. Testing single functions in JavaScript is not that hard (if you don't need to interact with the DOM), and can be easily achieved using frameworks like Mocha. Integration and cross browser testing is, on the other side, a huge PITA. Moreover, "browser testing" includes also "mobile browser testing". On the top of that the same browser (type and version) can behave in a different way on different operating systems.
To achieve that you can have your own cluster of machines with different operating systems and browsers or, if you don't want to spend the rest of your life configuring an average of 100 browsers for each different operating system, you can pay someone else to do that. Check out this article if you want to know why using Selenium Grid is better than a DIY approach.
We decided to use saucelabs cloud (they support over 700 combinations of operating systems and browsers) to run our tests.
Components and tools
Right now we are just running the test locally, so no Continuous Integration™.
The components involved are:
-
Selenium WebDriver: it's a library that can control a browser. You can use the WebDriver to load new URLs, click around, fill out forms, submit forms etc. It's basically a way to control remotely a browser. The protocol (language agnostic) is called JsonWire,
wd
wraps it and gives you a nice API you can use in JavaScript. There are other implementations in Python, PHP, Java, etc. Also, a WebDriver can be initialized with a list of desired capabilities describing which features (like the platform, browser name and version) you want to use to run your tests. -
Selenium Grid: it's the controller for the cluster of machines/devices that can run browsers. Selenium Grid is able to scale by distributing tests on several machines, manage multiple environments from a central point, making it easy to run the tests against a vast combination of browsers / OS, minimize the maintenance time for the grid by allowing you to implement custom hooks to leverage virtual infrastructure for instance.
-
Saucelabs: a private company providing a cluster to run your tests on over 700 combinations of browsers/operating systems. (They do other things, check out their websites).
-
SauceConnect: is a Java software by Saucelabs to connect to your
localhost
to test the application. There is also a Node.js wrapper sauce-connect-launcher, so you can use it programmatically within your code for tests. Please note that this module is just a wrapper around the actual software. Runningnpm install
should install the additional Java software as well.
On the JavaScript side, we use:
-
Mocha: a test framework running on Node.js.
-
chai: a BDD/TDD assertion library for node that can be paired with any javascript testing framework.
-
chaiAsPromised: an extension for Chai with a fluent language for asserting facts about promises. The extension is actually quite cool, we can do assertions on promises without writing callbacks but just chaining operators. Check out their
README
on GitHub to see an example. -
dotenv: a super nice package to load environment variables from
.env
intoprocess.env
.
How to set up your .env
config file
In the root of this repository there is a file called .env-template
. Create a
copy and call it .env
. This file will store some values we need to connect to
Saucelabs.
There are two values to be set:
SAUCE_ACCESS_KEY
SAUCE_USERNAME
The two keys are the default ones used by many products related to Saucelabs. This allow us to keep the configuration fairly straightforward and simple.
After logging in to https://saucelabs.com/, you can find your api key under
the My Account. Copy paste the value in your .env
file.
Anatomy of a test
First, you need to learn how Mocha works. Brew a coffee (or tea, if coffee is not your cup of tea), sit down and read the docs.
Done? Great, let's move on and analyze how a test is written.
From a very high level, the flow of a test is the following:
- load a page with a specific URL
- do something on the page (click a button, submit a form, etc.)
- maybe wait some seconds, or wait if something has changed
- check if the new page contains some text you expect to be there
This is not set in stone, so go crazy if you want. But keep in mind that we
have a one page application, there might be some gotchas on how to wait for
stuff to happen. I suggest you to read the section Wait for
something to understand
better which tools you have to solve this problem.
Again, take a look to the wd
implementation of the JsonWire
protocol
to know all the methods you can use to control the browser.
Import the libraries we need.
'use strict';
require('dotenv').load();
const wd = require('wd');
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
Set up chai
to use chaiAsPromised
.
chai.use(chaiAsPromised);
chai.should();
browser
is the main object to interact with Saucelab "real" browsers. We will
use this object a lot. It allows us to load pages, click around, check if a
specific text is present, etc.
describe('Login logs users in', function() {
let browser;
Create the driver to control the browser. before
will be executed once at the
start of the test before any it
functions. Use beforeEach
instead if you'd
like to run some code before each it
function.
before(function() {
browser = wd.promiseChainRemote('ondemand.saucelabs.com', 80);
// Start the browser, go to /login, and wait for the react app to render
return browser
.init({ browserName, version, platform })
.get(config.APP_URL + '/login')
.waitForElementByCss('.ascribe-default-app', asserters.isDisplayed, 10000);
});
Close the browser after finishing all tests. after
will be executed at the end
of all it
functions. Use afterEach
instead if you'd like to run some code
after each it
function.
after(function() {
// Destroys the browser session
return browser.quit();
});
The actual test. We query the browser
object to get the title of the page.
Note that .title()
returns a promise
but, since we are using
chaiAsPromised
, we have some syntactic sugar to handle the promise in line,
without writing new functions.
it('should contain "Log in" in the title', function() {
return browser.title().should.become('Log in');
});
});
All together:
function testSuite(browserName, version, platform) {
describe(`[${browserName} ${version} ${platform}] Login logs users in`, function() {
// Set timeout to zero so Mocha won't time out.
this.timeout(0);
let browser;
before(function() {
// No need to inject `username` or `access_key`, by default the constructor
// looks up the values in `process.env.SAUCE_USERNAME` and `process.env.SAUCE_ACCESS_KEY`
browser = wd.promiseChainRemote('ondemand.saucelabs.com', 80);
// Start the browser, go to /login, and wait for the react app to render
return browser
.init({ browserName, version, platform })
.get(config.APP_URL + '/login')
.waitForElementByCss('.ascribe-default-app', asserters.isDisplayed, 10000);
});
after(function() {
return browser.quit();
});
it('should contain "Log in" in the title', function() {
return browser.title().should.become('Log in');
});
});
}
How to run the test suite
To run the tests, type:
$ mocha
By default the test suite runs on http://www.localhost.com:3000/
, if you
want to change the URL, change the APP_URL
env variable.
How to have fun
Try this!
$ mocha -R nyan