Skip to content

Commit

Permalink
fixed hooks, added override runner option, runner tests included
Browse files Browse the repository at this point in the history
  • Loading branch information
DavertMik committed Dec 11, 2016
1 parent 7514c64 commit 5582131
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 41 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
@@ -1,6 +1,18 @@
## 0.4.12

* Bootstrap / Teardown improved with [Hooks](codecept.io/configuration/#hooks). Various options for setup/teardown provided.
* Added `--override` or `-o` option for runner to dynamically override configs. Valid JSON should be passed:

```
codeceptjs run -o '{ "bootstrap": "bootstrap.js"}'
codeceptjs run -o '{ "helpers": {"WebDriverIO": {"browser": "chrome"}}}'
```

* Added [regression tests](https://github.com/Codeception/CodeceptJS/tree/master/test/runner) for codeceptjs tests runner.

## 0.4.11

* Fixed regression in 0.4.11
* Fixed regression in 0.4.10
* Added `bootstrap`/`teardown` config options to accept functions as parameters by @pscanf. See updated [config reference](http://codecept.io/configuration/) #319

## 0.4.10
Expand Down
1 change: 1 addition & 0 deletions bin/codecept.js
Expand Up @@ -57,6 +57,7 @@ program.command('run [suite] [test]')
.option('--steps', 'show step-by-step execution')
.option('--debug', 'output additional information')
.option('--verbose', 'output internal logging information')
.option('-o, --override [value]', 'override current config options')
.option('--profile [value]', 'configuration profile to be used')
.option('--config [file]', 'configuration file to be used')

Expand Down
6 changes: 6 additions & 0 deletions docs/commands.md
Expand Up @@ -54,6 +54,12 @@ Select config file manually
codeceptjs run --config my.codecept.conf.js`
```

Override config on the fly. Provide valid JSON which will be merged into current config:

```
codeceptjs run --override '{ "helpers": {"WebDriverIO": {"browser": "chrome"}}}'
```

Run tests and produce xunit report:

```
Expand Down
67 changes: 38 additions & 29 deletions docs/configuration.md
Expand Up @@ -13,26 +13,15 @@ Here is an overview of available options with their defaults:
* **helpers**: `{}` - list of enabled helpers
* **mocha**: `{}` - mocha options, [reporters](http://codecept.io/reports/) can be configured here
* **name**: `"tests"` - test suite name (not used)
* **bootstrap**: `"./bootstrap.js"` - an option to run code _before_ tests are run (see example in a section below). It can either be:
* a path to a js file that will be executed (via `require`) before tests. If the file exports a
function, the function is called right away with a callback parameter. When the
callback is called with no arguments, tests are executed. If instead the callback is called with an
error as first argument, test execution is aborted and the process stops.
* a function (dynamic configuration only). The function is called before tests with a callback function
as the only parameter. When the callback is called with no arguments, tests are executed. If instead
the callback is called with an error as first argument, test execution is aborted and the process stops.
* **teardown**: - an option to run code _after_ tests are run (see example in a section below). It can either be:
* a path to a js file that will be executed (via `require`) after tests. If the file exports a
function, the function is called right away with a callback parameter.
* a function (dynamic configuration only). The function is called after tests with a callback parameter.

* **bootstrap**: `"./bootstrap.js"` - an option to run code _before_ tests are run [Hooks](#hooks)).
* **teardown**: - an option to run code _after_ tests are run (see [Hooks](#hooks)).
* **translation**: - [locale](http://codecept.io/translation/) to be used to print steps output, as well as used in source code.


## Dynamic Configuration

By default `codecept.json` is used for configuration. However, you can switch to JS format for more dynamic options.
Create `codecept.conf.js` file and make it export `config` property.
Create `codecept.conf.js` file and make it export `config` property:

See the config example:

Expand Down Expand Up @@ -71,39 +60,62 @@ exports.config = {

(Don't copy-paste this config, it's just demo)

### Bootstrap / Teardown
## Hooks

Hooks are implemented as `bootstrap` and `teardown` options in config. You can use them to prepare test environment before execution and cleanup after.
They can be used to launch stop webserver, selenium server, etc. There are different sync and async ways to define bootstrap and teardown functions.

`bootstrap` and `teardown` options can be:

`bootstrap` and `teardown` options can accept either a JS file to be executed or a function in case of dynamic config.
If a file returns a function with a param it is considered to be asynchronous. This can be useful to start server in the beginning of tests and stop it after:
* JS file, executed as is (synchronously).
* JS file exporting a function; If function accepts a callback is executed asynchronously. See example:

File: `codecept.json`:
Config (`codecept.json`):

```js
"bootstrap": "./bootstrap.js"
"teardown": "./teardown.js"
```

File: `bootstrap.js`:
Bootstrap file (`bootstrap.js`):

```js
global.server = require('./app_server');
// bootstrap.js
var server = require('./app_server');
module.exports = function(done) {
server.launch(done);
}
```

File: `teardown.js`:
* JS file exporting an object with `bootstrap` and (or) `teardown` methods for corresponding hooks.

Config (`codecept.json`):

```js
module.exports = function(done) {
server.stop(done);
"bootstrap": "./bootstrap.js"
"teardown": "./bootstrap.js"
```

Bootstrap file (`bootstrap.js`):

```js
// bootstrap.js
var server = require('./app_server');
module.exports = {
bootstrap: function(done) {
server.launch(done);
},
teardown: function(done) {
server.stop(done);
}
}
```

In case of dynamic config bootstrap/teardown functions can be placed inside a config itself:
* JS function in case of dynamic config. If function accepts a callback is executed asynchronously. See example:

Config JS (`codecept.conf.js`):

```js
let server = require('./app_server');
var server = require('./app_server');

exports.config = {
bootstrap: function(done) {
Expand All @@ -118,9 +130,6 @@ exports.config = {

```

If bootstrap / teardown function doesn't accept a param it is executed as is, in sync manner.
Synchronous execution also happens if bootstrap/teardown file is required but not exporting anything.

## Profile

Using values from `process.profile` you can change the config dynamically.
Expand Down
6 changes: 6 additions & 0 deletions lib/command/run.js
@@ -1,6 +1,7 @@
'use strict';
let getConfig = require('./utils').getConfig;
let getTestRoot = require('./utils').getTestRoot;
let deepMerge = require('./utils').deepMerge;
let Codecept = require('../codecept');
let output = require('../output');

Expand All @@ -12,6 +13,11 @@ module.exports = function (suite, test, options) {

let testRoot = getTestRoot(suite);
let config = getConfig(testRoot, configFile);

// override config with options
if (options.override) {
config = deepMerge(config, JSON.parse(options.override));
}
try {
codecept = new Codecept(config, options);
codecept.init(testRoot, function (err) {
Expand Down
26 changes: 26 additions & 0 deletions lib/command/utils.js
Expand Up @@ -12,6 +12,11 @@ module.exports.getTestRoot = function (currentPath) {

module.exports.getConfig = function (testRoot, configFile) {

// using relative config
if (configFile && !path.isAbsolute(configFile)) {
configFile = path.join(testRoot, configFile);
}

let config,
manualConfigFile = configFile && path.resolve(configFile),
jsConfigFile = path.join(testRoot, 'codecept.conf.js'),
Expand Down Expand Up @@ -41,8 +46,29 @@ module.exports.getConfig = function (testRoot, configFile) {
process.exit(1);
};

function isObject(item) {
return (item && typeof item === 'object' && !Array.isArray(item));
}

function deepMerge(target, source) {
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
deepMerge(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return target;
}

module.exports.deepMerge = deepMerge;

function configWithDefaults(config) {
if (!config.include) config.include = {};
if (!config.helpers) config.helpers = {};
return config;
}

8 changes: 7 additions & 1 deletion lib/hooks.js
Expand Up @@ -2,6 +2,7 @@

let getParamNames = require('./utils').getParamNames;
let fsPath = require('path');
let fileExists = require('./utils').fileExists;

module.exports = function(hook, config, done) {
if (typeof config[hook] === 'string' && fileExists(fsPath.join(codecept_dir, config[hook]))) {
Expand All @@ -10,6 +11,10 @@ module.exports = function(hook, config, done) {
callSync(callable, done);
return;
}
if (typeof callable === 'object' && callable[hook]) {
callSync(callable[hook], done);
return;
}
} else if (typeof config[hook] === 'function') {
callSync(config[hook], done);
return;
Expand All @@ -27,5 +32,6 @@ function callSync(callable, done) {
}

function isAsync(fn) {
return getParamNames(fn).length > 0;
let params = getParamNames(fn);
return params && params.length;
}
79 changes: 69 additions & 10 deletions test/runner/config_test.js
@@ -1,9 +1,13 @@
'use strict';
let should = require('chai').should();
let assert = require('assert');
let path = require('path');
const exec = require('child_process').exec;
let runner = path.join(__dirname, '/../../bin/codecept.js');
let codecept_dir = path.join(__dirname, '/../data/sandbox')
let codecept_run = runner +' run '+codecept_dir + ' ';
let config_run_config = (config) => `${codecept_run} --config ${config}`;
let config_run_override = (config) => `${codecept_run} --override '${JSON.stringify(config)}'`;
let fs;

describe('CodeceptJS Runner', () => {
Expand All @@ -13,31 +17,86 @@ describe('CodeceptJS Runner', () => {
});

it('should be executed', (done) => {
exec(runner +' run '+codecept_dir, (err, stdout, stderr) => {
exec(codecept_run, (err, stdout, stderr) => {
stdout.should.include('Filesystem'); // feature
stdout.should.include('check current dir'); // test name
assert(!err);
done();
})
});
});

xit('should return -1 on fail', () => {

it('should show failures and exit with 1 on fail', (done) => {
exec(config_run_config('codecept.failed.json'), (err, stdout, stderr) => {
stdout.should.include('Not-A-Filesystem');
stdout.should.include('file is not in dir');
stdout.should.include('FAILURES');
err.code.should.eql(1);
done();
});
});

xit('should run bootstrap', () => {

it('should run bootstrap', (done) => {
exec(config_run_config('codecept.bootstrap.sync.json'), (err, stdout, stderr) => {
stdout.should.include('Filesystem'); // feature
stdout.should.include('I am bootstrap');
assert(!err);
done();
});
});

xit('should run teardown', () => {
it('should run teardown', (done) => {
exec(config_run_override({teardown: 'bootstrap.sync.js'}), (err, stdout, stderr) => {
stdout.should.include('Filesystem'); // feature
stdout.should.include('I am bootstrap');
assert(!err);
done();
});
});

it('should run async bootstrap', (done) => {
exec(config_run_override({bootstrap: 'bootstrap.async.js'}), (err, stdout, stderr) => {
stdout.should.include('Ready: 0');
stdout.should.include('Go: 1');
stdout.should.include('Filesystem'); // feature
assert(!err);
done();
});
});

xit('should be executed with config', () => {
it('should run bootstrap/teardown as object', (done) => {
exec(config_run_config('codecept.hooks.obj.json'), (err, stdout, stderr) => {
stdout.should.include('Filesystem'); // feature
stdout.should.include('I am bootstrap');
stdout.should.include('I am teardown');
assert(!err);
done();
});
});

it('should run dynamic config', (done) => {
exec(config_run_config('config.js'), (err, stdout, stderr) => {
stdout.should.include('Filesystem'); // feature
assert(!err);
done();
});
});

xit('should try different configs to load', () => {
it('should run dynamic config with profile', (done) => {
exec(config_run_config('config.js') + ' --profile failed', (err, stdout, stderr) => {
stdout.should.include('FAILURES');
stdout.should.not.include('I am bootstrap');
assert(err.code);
done();
});
});

})
it('should run dynamic config with profile 2', (done) => {
exec(config_run_config('config.js') + ' --profile bootstrap', (err, stdout, stderr) => {
stdout.should.not.include('FAILURES'); // feature
stdout.should.include('I am bootstrap');
assert(!err);
done();
});
});

});

0 comments on commit 5582131

Please sign in to comment.