├── .gitignore
├── README.md
├── karma.conf.js
├── package.json
├── src
├── client.js
└── function-to-test.js
├── static
├── .gitkeep
└── index.html
├── test
├── integration
│ ├── .gitkeep
│ └── sample-integration.spec.js
├── out
│ └── screens
│ │ └── .gitkeep
└── unit
│ ├── .gitkeep
│ └── sample-unit.spec.js
├── wdio.conf.js
└── webpack.conf.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /test/out/screens/*
3 | /static/client.js
4 | /static/client.js.map
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Front-end Javascript Testing
2 |
3 | Goals:
4 |
5 | 1. Unit testing
6 | 2. Integrations testing
7 |
8 | The libraries used for both of them have been
9 | kept as similar as possible to each other.
10 | In fact, only the runner library differs,
11 | but the framework and assertions libraries used are the same.
12 |
13 | This module aims to be a reference/ starter
14 | for a testable front-end Javascript application.
15 |
16 | ## Unit testing
17 |
18 | Unit tests will need to test individual functions (white-box) using:
19 |
20 | - Runner: Karma
21 | - Framework: Mocha
22 | - Assertions: Chai Expect, Chai-as-promised
23 |
24 | Individual functions will be required,
25 | and tested in isolation.
26 | This is white box testing,
27 | as we are concerned with the internal details of how each function works.
28 | Karma is used as the test runner
29 | rather than Mocha directly,
30 | because we need access browser functionality.
31 |
32 | ## Integration testing
33 |
34 | Integration tests will need to test entire application (black-box) using:
35 |
36 | - Runner: Webdriver.io
37 | - Framework: Mocha
38 | - Assertions: Chai Expect, Chai-as-promised
39 |
40 | The entire application will be run,
41 | and tests will simulation actual usage of the web site.
42 | This is black box testing,
43 | as we are not concerned with the details of how the application works,
44 | just the end results.
45 | Webdriver.io is used as the test runner,
46 | rather than Mocha directly,
47 | because it is used to interface with a selenium server.
48 |
49 | ## Author
50 |
51 | [Brendan Graetz](http://bguiz.com)
52 |
53 | ## Licence
54 |
55 | GPL-3.0
56 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = karmaConfig;
4 |
5 | function karmaConfig(configuration) {
6 | configuration.set({
7 | autoWatch: true,
8 | basePath: '',
9 | browsers: ['Chrome'],
10 | colors: true,
11 | preprocessors: {
12 | 'test/unit/**/*.js': ['webpack'],
13 | },
14 | files: [
15 | 'test/unit/**/*.spec.js'
16 | ],
17 | frameworks: [
18 | 'mocha',
19 | 'sinon-chai'
20 | ],
21 | reporters: ['progress'],
22 | port: 8123,
23 | singleRun: false,
24 | });
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "front-end-js-testing",
3 | "version": "0.0.1",
4 | "description": "Starter project to demonstrate webdriver.io",
5 | "main": "index.js",
6 | "scripts": {
7 | "global-install": "npm i --global selenium-standalone@5.0.0 http-server@0.9.0 && selenium-standalone install",
8 | "server": "http-server ./static -p 8888 -c-1",
9 | "build": "webpack --config webpack.conf.js",
10 | "build-dev": "npm run server & webpack --config webpack.conf.js --watch",
11 | "start-selenium-server": "selenium-standalone start",
12 | "test": "npm run integration-test && npm run unit-test",
13 | "unit-test": "karma start karma.conf.js --single-run --no-auto-watch",
14 | "unit-test-background": "karma start karma.conf.js",
15 | "integration-test": "wdio ./wdio.conf.js"
16 | },
17 | "dependencies": {
18 | },
19 | "devDependencies": {
20 | "chai": "^3.5.0",
21 | "chai-as-promised": "^5.2.0",
22 | "karma": "^0.13.8",
23 | "karma-chrome-launcher": "^0.2.0",
24 | "karma-mocha": "^0.2.0",
25 | "karma-sinon-chai": "^1.0.0",
26 | "karma-webpack": "^1.7.0",
27 | "mocha": "^2.4.5",
28 | "sinon": "^1.17.2",
29 | "sinon-chai": "^2.8.0",
30 | "wdio-mocha-framework": "^0.2.11",
31 | "webdriverio": "^3.4.0",
32 | "webpack": "^1.12.14"
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "git+https://github.com/bguiz/front-end-js-testing.git"
37 | },
38 | "keywords": [
39 | "webdriver",
40 | "webdriverio",
41 | "selenium",
42 | "integration",
43 | "testing"
44 | ],
45 | "author": "bguiz",
46 | "license": "GPL-3.0",
47 | "bugs": {
48 | "url": "https://github.com/bguiz/front-end-js-testing/issues"
49 | },
50 | "homepage": "https://github.com/bguiz/front-end-js-testing#readme"
51 | }
52 |
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const functionToTest = require('./function-to-test.js');
4 |
5 | document.addEventListener('DOMContentLoaded', () => {
6 | var pressMeButton = document.querySelector('.press-me');
7 | var doNotPressMeButton = document.querySelector('.do-not-press-me');
8 | var outputAreaSpan = document.querySelector('.output-area');
9 | [
10 | { button: pressMeButton, value: true, },
11 | { button: doNotPressMeButton, value: false, }
12 | ].forEach((input) => {
13 | input.button.addEventListener('click', buttonPress.bind(undefined, input.value));
14 | });
15 |
16 | function buttonPress(shouldPass) {
17 | functionToTest(shouldPass)
18 | .then((value) => {
19 | outputAreaSpan.innerHTML = `Resolved: ${value}`;
20 | })
21 | .catch((err) => {
22 | outputAreaSpan.innerHTML = `Rejected: ${err}`;
23 | });
24 | }
25 | });
26 |
--------------------------------------------------------------------------------
/src/function-to-test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = functionToTest;
4 |
5 | function functionToTest(shouldPass) {
6 | return new Promise((resolve, reject) => {
7 | setTimeout(() => {
8 | if (shouldPass) {
9 | return resolve('Good');
10 | }
11 | else {
12 | return reject('Bad');
13 | }
14 | }, 10);
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bguiz/front-end-js-testing/f7ff96033c7dee8b57fe5a0b6d30d5ab06f6b876/static/.gitkeep
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Front-end Javascript Testing
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/test/integration/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bguiz/front-end-js-testing/f7ff96033c7dee8b57fe5a0b6d30d5ab06f6b876/test/integration/.gitkeep
--------------------------------------------------------------------------------
/test/integration/sample-integration.spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const chai = require('chai');
4 | const expect = chai.expect;
5 |
6 | describe('[main page]', function() {
7 | it('should have a title', function() {
8 | return expect(browser.url('/').getTitle())
9 | .to.eventually.be.equal('Front-end Javascript Testing');
10 | });
11 |
12 | it('should have a press me button', function() {
13 | return expect(browser.getText('.press-me'))
14 | .to.eventually.be.equal('Press Me');
15 | });
16 |
17 | it('should have a press me button', function() {
18 | return expect(browser.getText('.press-me'))
19 | .to.eventually.be.equal('Press Me');
20 | });
21 |
22 | it('should have an output area which is intially empty', function() {
23 | return expect(browser.getText('.output-area'))
24 | .to.eventually.be.equal('');
25 | });
26 |
27 | it('should click the press me button', function() {
28 | return browser.click('.press-me');
29 | });
30 |
31 | it('should have an output area with resolved text', function() {
32 | return expect(browser.getText('.output-area'))
33 | .to.eventually.be.equal('Resolved: Good');
34 | });
35 |
36 | it('should have a do not press me button', function() {
37 | return expect(browser.getText('.do-not-press-me'))
38 | .to.eventually.be.equal('Do Not Press Me');
39 | });
40 |
41 | it('should click the do not press me button', function() {
42 | return browser.click('.do-not-press-me');
43 | });
44 |
45 | it('should have an output area with rejected text', function() {
46 | return expect(browser.getText('.output-area'))
47 | .to.eventually.be.equal('Rejected: Bad');
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/test/out/screens/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bguiz/front-end-js-testing/f7ff96033c7dee8b57fe5a0b6d30d5ab06f6b876/test/out/screens/.gitkeep
--------------------------------------------------------------------------------
/test/unit/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bguiz/front-end-js-testing/f7ff96033c7dee8b57fe5a0b6d30d5ab06f6b876/test/unit/.gitkeep
--------------------------------------------------------------------------------
/test/unit/sample-unit.spec.js:
--------------------------------------------------------------------------------
1 | const chaiAsPromised = require('chai-as-promised');
2 | chai.use(chaiAsPromised);
3 |
4 | const functionToTest = require('../../src/function-to-test.js');
5 |
6 | describe('[sample unit]', function() {
7 | it('should pass functionToTest with true input', function() {
8 | return expect(functionToTest(true))
9 | .to.eventually.be.equal('Good');
10 | });
11 |
12 | it('should fail functionToTest with false input', function() {
13 | return expect(functionToTest(false))
14 | .to.eventually.be.rejectedWith('Bad');
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/wdio.conf.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai');
2 | const chaiAsPromised = require('chai-as-promised');
3 |
4 | exports.config = {
5 |
6 | //
7 | // ==================
8 | // Specify Test Files
9 | // ==================
10 | // Define which test specs should run. The pattern is relative to the directory
11 | // from which `wdio` was called. Notice that, if you are calling `wdio` from an
12 | // NPM script (see https://docs.npmjs.com/cli/run-script) then the current working
13 | // directory is where your package.json resides, so `wdio` will be called from there.
14 | //
15 | specs: [
16 | './test/integration/**/*.spec.js'
17 | ],
18 | // Patterns to exclude.
19 | exclude: [
20 | // 'path/to/excluded/files'
21 | ],
22 | //
23 | // ============
24 | // Capabilities
25 | // ============
26 | // Define your capabilities here. WebdriverIO can run multiple capabilties at the same
27 | // time. Depending on the number of capabilities, WebdriverIO launches several test
28 | // sessions. Within your capabilities you can overwrite the spec and exclude option in
29 | // order to group specific specs to a specific capability.
30 | //
31 | // If you have trouble getting all important capabilities together, check out the
32 | // Sauce Labs platform configurator - a great tool to configure your capabilities:
33 | // https://docs.saucelabs.com/reference/platforms-configurator
34 | //
35 | capabilities: [{
36 | browserName: 'chrome'
37 | }],
38 | //
39 | // ===================
40 | // Test Configurations
41 | // ===================
42 | // Define all options that are relevant for the WebdriverIO instance here
43 | //
44 | // Level of logging verbosity: silent | verbose | command | data | result | error
45 | logLevel: 'verbose',
46 | //
47 | // Enables colors for log output.
48 | coloredLogs: true,
49 | //
50 | // Saves a screenshot to a given path if a command fails.
51 | screenshotPath: './test/out/screens/',
52 | //
53 | // Set a base URL in order to shorten url command calls. If your url parameter starts
54 | // with "/", the base url gets prepended.
55 | baseUrl: 'http://localhost:8888',
56 | //
57 | // Default timeout for all waitForXXX commands.
58 | waitforTimeout: 10000,
59 | //
60 | // Default timeout in milliseconds for request
61 | // if Selenium Grid doesn't send response
62 | connectionRetryTimeout: 90000,
63 | //
64 | // Default request retries count
65 | connectionRetryCount: 3,
66 | //
67 | // Initialize the browser instance with a WebdriverIO plugin. The object should have the
68 | // plugin name as key and the desired plugin options as property. Make sure you have
69 | // the plugin installed before running any tests. The following plugins are currently
70 | // available:
71 | // WebdriverCSS: https://github.com/webdriverio/webdrivercss
72 | // WebdriverRTC: https://github.com/webdriverio/webdriverrtc
73 | // Browserevent: https://github.com/webdriverio/browserevent
74 | // plugins: {
75 | // webdrivercss: {
76 | // screenshotRoot: 'my-shots',
77 | // failedComparisonsRoot: 'diffs',
78 | // misMatchTolerance: 0.05,
79 | // screenWidth: [320,480,640,1024]
80 | // },
81 | // webdriverrtc: {},
82 | // browserevent: {}
83 | // },
84 | //
85 | // Test runner services
86 | // Services take over a specfic job you don't want to take care of. They enhance
87 | // your test setup with almost no self effort. Unlike plugins they don't add new
88 | // commands but hook themself up into the test process.
89 | // services: [],//
90 | // Framework you want to run your specs with.
91 | // The following are supported: mocha, jasmine and cucumber
92 | // see also: http://webdriver.io/guide/testrunner/frameworks.html
93 | //
94 | // Make sure you have the wdio adapter package for the specific framework installed
95 | // before running any tests.
96 | framework: 'mocha',
97 | //
98 | // Test reporter for stdout.
99 | // The following are supported: dot (default), spec and xunit
100 | // see also: http://webdriver.io/guide/testrunner/reporters.html
101 | // reporters: ['dot'],
102 | //
103 | // Options to be passed to Mocha.
104 | // See the full list at http://mochajs.org/
105 | mochaOpts: {
106 | ui: 'bdd'
107 | },
108 | //
109 | // =====
110 | // Hooks
111 | // =====
112 | // WedriverIO provides a several hooks you can use to intefere the test process in order to enhance
113 | // it and build services around it. You can either apply a single function to it or an array of
114 | // methods. If one of them returns with a promise, WebdriverIO will wait until that promise got
115 | // resolved to continue.
116 | //
117 | // Gets executed once before all workers get launched.
118 | // onPrepare: function (config, capabilities) {
119 | // },
120 | //
121 | // Gets executed before test execution begins. At this point you can access to all global
122 | // variables like `browser`. It is the perfect place to define custom commands.
123 | before: function (capabilties, specs) {
124 | chai.Should();
125 | chai.use(chaiAsPromised);
126 | chaiAsPromised.transferPromiseness = browser.transferPromiseness;
127 | },
128 |
129 | // Hook that gets executed before the suite starts
130 | // beforeSuite: function (suite) {
131 | // },
132 | //
133 | // Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling
134 | // beforeEach in Mocha)
135 | // beforeHook: function () {
136 | // },
137 | //
138 | // Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling
139 | // afterEach in Mocha)
140 | // afterHook: function () {
141 | // },
142 | //
143 | // Function to be executed before a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
144 | // beforeTest: function (test) {
145 | // },
146 | //
147 | // Runs before a WebdriverIO command gets executed.
148 | // beforeCommand: function (commandName, args) {
149 | // },
150 | //
151 | // Runs after a WebdriverIO command gets executed
152 | // afterCommand: function (commandName, args, result, error) {
153 | // },
154 | //
155 | // Function to be executed after a test (in Mocha/Jasmine) or a step (in Cucumber) starts.
156 | // afterTest: function (test) {
157 | // },
158 | //
159 | // Hook that gets executed after the suite has ended
160 | // afterSuite: function (suite) {
161 | // },
162 | //
163 | // Gets executed after all tests are done. You still have access to all global variables from
164 | // the test.
165 | // after: function (capabilties, specs) {
166 | // },
167 | //
168 | // Gets executed after all workers got shut down and the process is about to exit. It is not
169 | // possible to defer the end of the process using a promise.
170 | // onComplete: function(exitCode) {
171 | // }
172 | }
173 |
--------------------------------------------------------------------------------
/webpack.conf.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var path = require('path');
4 | var cwd = __dirname;
5 |
6 | module.exports = {
7 | entry: './src/client.js',
8 | output: {
9 | filename: './static/client.js',
10 | },
11 | module: {
12 | loaders: [
13 | { test: /\.css$/, loader: "style!css" }
14 | ]
15 | },
16 | colors: true,
17 | devtool: 'source-map',
18 | };
19 |
--------------------------------------------------------------------------------