So, lets start at the very beginning and define some terms I may refer to (some of these are based on popular opinion and do not have a concrete definition, so I'll try to define in the most commonly used context):
This involves testing a single 'unit' or portion of a system.
One such example would be testing a single function in a program (although the function itself may call additional functions).
Typically this is useful to confirm a code refactor/change does not break an existing functions I/O or side-effects, so users of the function (other code that calls on it) will not break when the functions are changed.
This involves testing the 'integration' or various components that make up a whole product/software/architecture (basically, anything that the process you wish to test depends on).
Some examples of integration testing would be if your code hits an external process (a database, a mail queue, a network based API etc.) - a good way to remember this - if its not part of your codebase and you're testing or depending on it's I/O to function in a certain way, that is an integration test.
This is very close to integration testing, however what separates it is that (ideally) it is testing the 'functional' requirements of a product (typically defined by 'the business', or whoever wrote or defines the software requirements).
Functional testing may involve things such as clicking a link on a webpage and confirming the click triggers the expected action(s), such as being redirected to a new page, seeing a pop up etc.
This is basically a synonym for any of the other 3 test types (afterall, all the test types are there to confirm new code doesn't break old code/processes), but you may most often find it used in place of functional testing and very rarely in reference to unit testing.
So, if you're reading this blog, chance are you have some interest in automation (or you're just very bored).
While this list could span many pages, we'll keep it topical to just the things being discussed here:
This is a software testing framework for web applications - essentially it allows one to script actions that will be performed against a web browser (most often Chrome or Firefox, by using chromedriver or geckodriver respectively - the drivers can just be thought of plugins/programs that 'drive' or translate commands to the desired browser).
While Selenium does offer a Selenium IDE (browser plugin) to automate test actions by recording actions in the browser, this article will focus only on automation via nodejs scripting.
Selenium works by 'listening' on a port (if running selenium-standalone server locally, this will be a port on the user's localhost).
This is a programming language based on javascript (the most commonly used/available browser based language).
It has an easy to high learning curve (code can be written in a simple verbose form, or a complicated terse form).
This is the 'node package manager' and handles installing node dependencies (equivalents in other languages/OSes would be 'maven' or 'ant' in java, 'composer' in php, 'brew' in OSX, 'apt' in Ubuntu etc.).
Ah, finally, the subject of this post. So, http://webdriver.io is a 'package' (or library) for node that ties together a node script and the selenium software.
The website has a lot of great documentation and guide (we'll refer to in a bit).
Installing is also very easy (essentially just a single npm command).
This is loosely related, but worth mentioning. In a very general, non-technical sense, docker is quite similar to a tiny/temporary virtual machine (it is most often referred to as a 'container').
When considering selenium usage, it can be very beneficial to 'containerize' or run selenium-standalone server and the firefox/chrome browsers in a container, because occasionally you'll run into snags with a local selenium-standalone and versions of firefox/chrome that change or deprecate features selenium depends on (which will cause your scripts to break simply due to having upgraded your system's web browser).
Well, while you may be familiar with unit testing (and perhaps even TDD, test driven development), and think of 'integration testing' in the sense that you annotate your unit tests with 'integration' test if it touches the database, and think of 'functional testing' as that manual process of clicking around the product, you may be surprised to find out all 3 parts can be automated to some degree.
Great, you're ready to start scripting some tests - well, maybe not yet.
First you'll need to set up your local environment before you start tinkering with it.
The best guide to use is on the webdriver.io site here: http://webdriver.io/guide.html
As long as you run each command identified there, you should end up with a pretty solid setup to begin writing some more interesting test cases (we'll go over how to fill out a form, something the guide doesn't discuss).
After you're done with the guide, confirm your directory structure resembles this:
├── node_modules/ ├── geckodriver ├── package.json ├── selenium-server-standalone-3.0.1.jar ├── test │ └── specs │ └── blub.js ├── test.js └── wdio.conf.js 3 directories, 6 files
So, after completing the guide, you saw how your browser opened a site on its own, ran the test assertion (in my case, the file blub.js has the 'describe' and 'it' assertion wrappers, you may have named your file differently).
Lets create a new file in the test/specs directory and call it, bmiTest.js.
Run something like (assuming you're in the project root):
touch test/specs/bmiTest.js
to create the initial file.
After that, open it in your favorite text editor and add the following:
var assert = require('assert'); browser.url('https://www.cdc.gov/healthyweight/assessing/bmi/adult_bmi/english_bmi_calculator/bmi_calculator.html'); describe('BMI calculator page', function () { it('Should tell me the correct page title for BMI calculations', function () { var title = browser.getTitle() assert.equal(title, 'Adult BMI Calculator | Healthy Weight | CDC') }) it('Should calculate my BMI when submitting', function () { // We can chain these calls, instead of putting 'browser' in front of each browser .setValue('#feet', '5') .setValue('#inches', '10') .setValue('#pounds', '150') .click('#calc') // Here we are selecting the class="bmiNum" nested under the id="normal" element var myBmi = browser.getText('#normal .bmiNum') assert.equal(myBmi, '21.5') }) })
Now, change the browser to chrome if using firefox 53.
This is because of an error in geckodriver with FF 53+:
https://github.com/webdriverio/webdriverio/issues/1999
You can do this by opening the wdio.conf.js file and replacing the 'firefox' word with 'chrome' (line 47, under browserName).
After you save the file, start your selenium server. If running the project local one that the wdio guide had you install, you can use (assuming it isn't already running):
java -jar -Dwebdriver.gecko.driver=./geckodriver selenium-server-standalone-3.0.1.jar
to start it.
Then, you can run your test suite with the following command again:
./node_modules/.bin/wdio wdio.conf.js
You will notice it actually runs both the test files (the one that opens the BMI calculator page, and the one that opens the webdriver.io page for asserting the title is correct).
Assuming neither site is down, you should see results as follows:
./node_modules/.bin/wdio wdio.conf.js ․․․ 3 passing (4.50s)
Which means all our tests worked as planned!
Cool, automation is easy and fun! Well, it would be more fun if all that code made sense, so lets begin to break it down by adding some inline code comments describing what each thing is doing:
// This is pulling in a package/library intalled from npm // into this specific script (sometimes called a 'module') // and assigning it into a variable called 'assert' var assert = require('assert'); // Where does 'browser' come from? Well, when you use the wdio test // runner, it is a variable similar to assert above, that is // automatically populated with the browser/webdriver configuration // that we specified manually in the very first test.js file the wdio // guide had us work on. // In this case, we call a 'method' or 'function' that is bound to the // instance of the browser object, and this causes selenium to open // the browser to that page. browser.url('https://www.cdc.gov/healthyweight/assessing/bmi/adult_bmi/english_bmi_calculator/bmi_calculator.html'); // So, what the heck is this? Describe is a function call from the // mocha test framework (this was set up in the wdio.conf.js file that // we clicked through). // The function is taking 2 arguments, a string 'BMI calculator page' // and another function, often called an 'anonymous // function' (see how it doesn't have a name next to it?) describe('BMI calculator page', function () { // Similar to 'describe', the 'it' wrapper is almost identical, // a function from mocha that takes a string and an anonymous function // The general mocha structure is to 'describe' a general test case, // then define all the things 'it' should do - each 'it' block then contains // different 'assert'ions, which all must be satisfied for the test cases to pass. it('Should tell me the correct page title for BMI calculations', function () { // Finally, we do something useful, we use the 'browser' variable // from up above to request the page title and plop it in a variable. var title = browser.getTitle() // After we get the page title, we run an 'assert' to confirm // it equals what we expect it to equal assert.equal(title, 'Adult BMI Calculator | Healthy Weight | CDC') }) // Here, we define another thing 'it' should do it('Should calculate my BMI when submitting', function () { // We can chain these calls, instead of putting 'browser' in front of each browser .setValue('#feet', '5') .setValue('#inches', '10') .setValue('#pounds', '150') .click('#calc') // Up above, we set some values in various DOM elements (specified // by their unique id attribute in the HTML, using a CSS // selector), then 'click'ed the submit button. // Here we are selecting the class="bmiNum" nested under the id="normal" element // and assigning it into a variable to assert against in a moment. var myBmi = browser.getText('#normal .bmiNum') // Notice that we used a getText call, not a getValue call - getText is for // grabbing HTML text, getValue is for getting an input field value. // Finally, we confirm the calculation functioned as expected with // another assertion and a pre-determined value we know to be correct. assert.equal(myBmi, '21.5') }) })