codeceptjs
Advanced tools
Comparing version 1.3.3 to 1.4.0
@@ -92,2 +92,3 @@ #!/usr/bin/env node | ||
.option('--tests', 'run only JS test files and skip features') | ||
.option('-p, --plugins <k=v,k2=v2,...>', 'enable plugins, comma-separated') | ||
@@ -94,0 +95,0 @@ // mocha options |
@@ -0,4 +1,16 @@ | ||
## 1.4.0 | ||
* [**Allure Reporter Integration**](https://codecept.io/reports/#allure). Full inegration with Allure Server. Get nicely looking UI for tests,including steps, nested steps, and screenshots. Thanks **Natarajan Krishnamurthy @krish** for sponsoring this feature. | ||
* [Plugins API introduced](https://codecept.io/hooks/#plugins). Create custom plugins for CodeceptJS by hooking into event dispatcher, and using promise recorder. | ||
* **Official [CodeceptJS plugins](https://codecept.io/plugins) added**: | ||
* **`stepByStepReport` - creates nicely looking report to see test execution as a slideshow**. Use this plugin to debug tests in headless environment without recording a video. | ||
* `allure` - Allure reporter added as plugin. | ||
* `screenshotOnFail` - saves screenshot on fail. Replaces similar functionality from helpers. | ||
* `retryFailedStep` - to rerun each failed step. | ||
* [Puppeteer] Fix `executeAsyncScript` unexpected token by @jonathanz | ||
* Added `override` option to `run-multiple` command by @svarlet | ||
## 1.3.3 | ||
* Added `initGLobals()` function to API of [custom runner](https://codecept.io/hooks/#custom-runner). | ||
* Added `initGlobals()` function to API of [custom runner](https://codecept.io/hooks/#custom-runner). | ||
@@ -5,0 +17,0 @@ ## 1.3.2 |
@@ -123,2 +123,14 @@ # Advanced Usage | ||
For Visual Studio Code, add the following configuration in launch.json: | ||
```json | ||
{ | ||
"type": "node", | ||
"request": "launch", | ||
"name": "codeceptjs", | ||
"args": ["run", "--grep", "@your_test_tag"], | ||
"program": "${workspaceFolder}/node_modules/.bin/codeceptjs" | ||
} | ||
``` | ||
## Parallel Execution | ||
@@ -125,0 +137,0 @@ |
@@ -39,2 +39,10 @@ # Codecept | ||
## initGlobals | ||
Creates global variables | ||
**Parameters** | ||
- `dir` **Any** | ||
## loadTests | ||
@@ -41,0 +49,0 @@ |
@@ -26,2 +26,3 @@ # Container | ||
- `newSupport` **Any** | ||
- `newPlugins` | ||
@@ -49,2 +50,10 @@ ## create | ||
## plugins | ||
Get all plugins | ||
**Parameters** | ||
- `name` **[string]** | ||
## support | ||
@@ -51,0 +60,0 @@ |
@@ -50,10 +50,2 @@ # debug | ||
# stepExecutionTime | ||
Print a step execution time | ||
**Parameters** | ||
- `step` | ||
# success | ||
@@ -60,0 +52,0 @@ |
@@ -1470,16 +1470,3 @@ const requireg = require('requireg'); | ||
async _failed(test) { | ||
const promisesList = []; | ||
if (withinStatus !== false) promisesList.push(this._withinEnd()); | ||
if (!this.options.disableScreenshots) { | ||
let fileName = clearString(test.title); | ||
if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`); | ||
if (this.options.uniqueScreenshotNames) { | ||
const uuid = test.uuid || test.ctx.test.uuid; | ||
fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`; | ||
} else { | ||
fileName += '.failed.png'; | ||
} | ||
promisesList.push(this.saveScreenshot(fileName, true)); | ||
} | ||
return Promise.all(promisesList); | ||
if (withinStatus !== false) await this._withinEnd(); | ||
} | ||
@@ -1486,0 +1473,0 @@ |
@@ -51,3 +51,3 @@ # Puppeteer | ||
#### Example #2: Wait for DOMContentLoaded event and 0 netowrk connections | ||
#### Example #2: Wait for DOMContentLoaded event and 0 network connections | ||
@@ -60,3 +60,3 @@ ```json | ||
"restart": false, | ||
"waitForNavigation": "networkidle0", | ||
"waitForNavigation": [ "domcontentloaded", "networkidle0" ], | ||
"waitForAction": 500 | ||
@@ -63,0 +63,0 @@ } |
@@ -170,6 +170,53 @@ # Hooks | ||
## Plugins | ||
Plugins allow to use CodeceptJS internal API to extend functionality. Use internal event dispatcher, container, output, promise recorder, to create your own reporters, test listeners, etc. | ||
CodeceptJS includes [built-in plugins](https://codecept.io/plugins/) which extend basic functionality and can be turned on and off on purpose. Taking them as [examples](https://github.com/Codeception/CodeceptJS/tree/master/lib/plugin) you can develop your custom plugins. | ||
A plugin is a basic JS module returning a function. Plugins can have individual configs which are passed into this function: | ||
```js | ||
const defaultConfig = { | ||
someDefaultOption: true | ||
} | ||
module.exports = function(config) { | ||
config = Object.assign(defaultConfig, config); | ||
// do stuff | ||
} | ||
``` | ||
Plugin can register event listeners or hook into promise chain with recorder. See [API reference](https://github.com/Codeception/CodeceptJS/tree/master/lib/helper). | ||
To enable your custom plugin in config add it to `plugins` section. Specify path to node module using `require`. | ||
```js | ||
"plugins": { | ||
"myPlugin": { | ||
"require": "./path/to/my/module", | ||
"enabled": true | ||
} | ||
} | ||
``` | ||
* `require` - specifies relative path to a plugin file. Path is relative to config file. | ||
* `enabled` - to enable this plugin. | ||
If a plugin is disabled (`enabled` is not set or false) this plugin can be enabled from command line: | ||
``` | ||
./node_modules/.bin/codeceptjs run --plugin myPlugin | ||
``` | ||
Several plugins can be enabled as well: | ||
``` | ||
./node_modules/.bin/codeceptjs run --plugin myPlugin,allure | ||
``` | ||
## Custom Hooks | ||
To extend internal CodeceptJS functionality you can use hooks. | ||
CodeceptJS provides API to connect to its internal event dispatcher, container, output, promise recorder, so you could hook into it to create your own reporters, test listeners, etc. | ||
*(deprecated, use [plugins](#plugins))* | ||
@@ -186,6 +233,8 @@ Hooks are JavaScript files same as for bootstrap and teardown, which can be registered inside `hooks` section of config. Unlike `bootstrap` you can have multiple hooks registered: | ||
Inside those JS files you can use CodeceptJS API to access its internals. | ||
Inside those JS files you can use CodeceptJS API (see below) to access its internals. | ||
## API | ||
**Use local CodeceptJS installation to get access to `codeceptjs` module** | ||
CodeceptJS provides an API which can be loaded via `require('codeceptjs')` when CodeceptJS is installed locally. | ||
@@ -206,15 +255,2 @@ These internal objects are available: | ||
### Config | ||
CodeceptJS config can be accessed from `require('codeceptjs').config.get()`: | ||
```js | ||
let config = require('codeceptjs').config.get(); | ||
if (config.myKey == 'value') { | ||
// run hook | ||
} | ||
``` | ||
### Event Listeners | ||
@@ -363,2 +399,5 @@ | ||
let UserPage = container.support('UserPage'); | ||
// get all registered plugins | ||
let plugins = container.plugins(); | ||
``` | ||
@@ -387,2 +426,15 @@ | ||
### Config | ||
CodeceptJS config can be accessed from `require('codeceptjs').config.get()`: | ||
```js | ||
let config = require('codeceptjs').config.get(); | ||
if (config.myKey == 'value') { | ||
// run hook | ||
} | ||
``` | ||
## Custom Runner | ||
@@ -403,8 +455,8 @@ | ||
// initialize codeceptjs in current dir | ||
codecept.initGlobals(__dirname); | ||
// create helpers, support files, mocha | ||
Container.create(config, opts); | ||
// initialize codeceptjs in current dir | ||
codecept.initGlobals(__dirname); | ||
// initialize listeners | ||
@@ -411,0 +463,0 @@ codecept.bootstrap(); |
@@ -106,2 +106,27 @@ # Mobile Testing | ||
## BrowserStack Configuration | ||
If you wish to use BrowserStack's [Automated Mobile App Testing](https://www.browserstack.com/app-automate) platform. Configure the Appium helper like this: | ||
```js | ||
"helpers": { | ||
"Appium": | ||
"app": "bs://<hashed app-id>", | ||
"host": "hub-cloud.browserstack.com", | ||
"port": 4444, | ||
"user": "BROWSERSTACK_USER", | ||
"key": "BROWSERSTACK_KEY", | ||
"device": "iPhone 7" | ||
} | ||
``` | ||
Here is the full list of [capabilities](https://www.browserstack.com/app-automate/capabilities). | ||
You need to upload your Android app (.apk) or iOS app (.ipa) to the BrowserStack servers using the REST API before running your tests. The App URL (`bs://hashed appid`) is returned in the response of this call. | ||
```sh | ||
curl -u "USERNAME:ACCESS_KEY" \ | ||
-X POST "https://api-cloud.browserstack.com/app-automate/upload" \ | ||
-F "file=@/path/to/app/file/Application-debug.apk" | ||
``` | ||
## Writing a Test | ||
@@ -108,0 +133,0 @@ |
@@ -72,3 +72,3 @@ # Robust Chrome Testing with Puppeteer | ||
WHen a test runs faster than application it is recommended to increase `waitForAction` config value. | ||
When a test runs faster than application it is recommended to increase `waitForAction` config value. | ||
It will wait for a small amount of time (100ms) by default after each user action is taken. | ||
@@ -155,3 +155,3 @@ | ||
In case some actions should be taken inside one element (a container or modal window) you can use `within` block to narrow the scope. | ||
Please take a not that you can't use within inside another within in Puppeteer helper: | ||
Please take a note that you can't use within inside another within in Puppeteer helper: | ||
@@ -201,2 +201,2 @@ ```js | ||
Yes, also the [demo project is available on GitHub](https://github.com/DavertMik/codeceptjs-todomvc-puppeteer) | ||
Yes, also the [demo project is available on GitHub](https://github.com/DavertMik/codeceptjs-todomvc-puppeteer) |
# Reporters | ||
## Cli (default) | ||
## Cli | ||
(default) | ||
By default CodeceptJS provides cli reporter with console output. | ||
@@ -84,2 +86,57 @@ Test names and failures will be printed to screen. | ||
## Allure | ||
(recommended) | ||
[Allure reporter](http://allure.qatools.ru/#) is a tool to store and display test reports. | ||
It provides nice web UI which contains all important information on test execution. | ||
CodeceptJS has built-in support for Allure reports. Inside reports you will have all steps, substeps and screenshots. | ||
![](https://user-images.githubusercontent.com/220264/45676511-8e052800-bb3a-11e8-8cbb-db5f73de2add.png) | ||
*Disclaimer: Allure is a standalone tool. Please refer to [Allure documentation](https://docs.qameta.io/allure/) to learn more about using Allure reports.* | ||
Allure requires **Java 8** to work. Then Allure can be installed via NPM: | ||
``` | ||
npm install -g allure-commandline --save-dev | ||
``` | ||
Add [Allure plugin](https://codecept.io/plugins/#allure) in config under `plugins` section. | ||
```js | ||
"plugins": { | ||
"allure": { | ||
} | ||
} | ||
``` | ||
Run tests with allure plugin enabled: | ||
``` | ||
codeceptjs run --plugin allure | ||
``` | ||
(optionally) To enable allure plugin permanently include `"enabled": true` into plugin config: | ||
```js | ||
"plugins": { | ||
"allure": { | ||
"enabled": true | ||
} | ||
} | ||
``` | ||
Launch Allure server and see the report like on a screenshot above: | ||
``` | ||
allure serve output | ||
``` | ||
Allure reporter aggregates data from other plugins like [*stepByStepReport*](https://codecept.io/plugins/#stepByStepReport) and [*screenshotOnFail*](https://codecept.io/plugins/#screenshotOnFail) | ||
## XML | ||
@@ -86,0 +143,0 @@ |
@@ -66,4 +66,6 @@ const Step = require('./step'); | ||
recorder.add(task, () => { | ||
event.emit(event.step.started, step); | ||
step.startTime = Date.now(); | ||
if (!step.startTime) { // step can be retries | ||
event.emit(event.step.started, step); | ||
step.startTime = Date.now(); | ||
} | ||
return val = step.run(...args); | ||
@@ -70,0 +72,0 @@ }); |
@@ -137,2 +137,3 @@ const fsPath = require('path'); | ||
} | ||
event.emit(event.all.before, this); | ||
mocha.run(() => { | ||
@@ -139,0 +140,0 @@ const done = () => { |
@@ -17,3 +17,3 @@ const { | ||
const childOpts = {}; | ||
const copyOptions = ['steps', 'reporter', 'verbose', 'config', 'reporter-options', 'grep', 'fgrep', 'debug']; | ||
const copyOptions = ['override', 'steps', 'reporter', 'verbose', 'config', 'reporter-options', 'grep', 'fgrep', 'debug']; | ||
@@ -20,0 +20,0 @@ // codeceptjs run:multiple smoke:chrome regression:firefox - will launch smoke run in chrome and regression in firefox |
@@ -37,3 +37,2 @@ const getConfig = require('./utils').getConfig; | ||
if (err) throw new Error(`Error while running bootstrap file :${err}`); | ||
event.emit(event.all.before, this); | ||
codecept.loadTests(); | ||
@@ -40,0 +39,0 @@ codecept.run(test); |
@@ -16,2 +16,7 @@ const fs = require('fs'); | ||
gherkin: {}, | ||
plugins: { | ||
screenshotOnFail: { | ||
enabled: true, // will be disabled by default in 2.0 | ||
}, | ||
}, | ||
}; | ||
@@ -18,0 +23,0 @@ |
@@ -10,2 +10,3 @@ const path = require('path'); | ||
support: {}, | ||
plugins: {}, | ||
mocha: {}, | ||
@@ -35,2 +36,3 @@ translation: {}, | ||
container.support = createSupportObjects(config.include || {}); | ||
container.plugins = createPlugins(config.plugins || {}, opts); | ||
if (config.gherkin) loadGherkinSteps(config.gherkin.steps || []); | ||
@@ -40,2 +42,15 @@ } | ||
/** | ||
* Get all plugins | ||
* | ||
* @api | ||
* @param {string} [name] | ||
*/ | ||
static plugins(name) { | ||
if (!name) { | ||
return container.plugins; | ||
} | ||
return container.plugins[name]; | ||
} | ||
/** | ||
* Get all support objects or get support object by name | ||
@@ -100,5 +115,6 @@ * | ||
*/ | ||
static clear(newHelpers, newSupport) { | ||
static clear(newHelpers, newSupport, newPlugins) { | ||
container.helpers = newHelpers || {}; | ||
container.support = newSupport || {}; | ||
container.plugins = newPlugins || {}; | ||
container.translation = loadTranslation(); | ||
@@ -194,2 +210,29 @@ } | ||
function createPlugins(config, options = {}) { | ||
const plugins = {}; | ||
const enabledPluginsByOptions = (options.plugins || '').split(','); | ||
for (const pluginName in config) { | ||
if (!config[pluginName]) config[pluginName] = {}; | ||
if (!config[pluginName].enabled && (enabledPluginsByOptions.indexOf(pluginName) < 0)) { | ||
continue; // plugin is disabled | ||
} | ||
let module; | ||
try { | ||
if (config[pluginName].require) { | ||
module = config[pluginName].require; | ||
if (module.startsWith('.')) { // local | ||
module = path.resolve(global.codecept_dir, module); // custom plugin | ||
} | ||
} else { | ||
module = `./plugin/${pluginName}`; | ||
} | ||
plugins[pluginName] = require(module)(config); | ||
} catch (err) { | ||
throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}`); | ||
} | ||
} | ||
return plugins; | ||
} | ||
function getSupportObject(config, name) { | ||
@@ -196,0 +239,0 @@ const module = config[name]; |
const container = require('./container'); | ||
const debug = require('./output').debug; | ||
const output = require('./output'); | ||
@@ -151,7 +151,7 @@ /** | ||
debug(msg) { | ||
debug(msg); | ||
output.debug(msg); | ||
} | ||
debugSection(section, msg) { | ||
debug(`[${section}] ${msg}`); | ||
output.debug(`[${section}] ${msg}`); | ||
} | ||
@@ -158,0 +158,0 @@ } |
@@ -1057,16 +1057,3 @@ const requireg = require('requireg'); | ||
async _failed(test) { | ||
const promisesList = []; | ||
if (withinStatus !== false) promisesList.push(this._withinEnd()); | ||
if (!this.options.disableScreenshots) { | ||
let fileName = clearString(test.title); | ||
if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`); | ||
if (this.options.uniqueScreenshotNames) { | ||
const uuid = test.uuid || test.ctx.test.uuid; | ||
fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`; | ||
} else { | ||
fileName += '.failed.png'; | ||
} | ||
promisesList.push(this.saveScreenshot(fileName, true)); | ||
} | ||
return Promise.all(promisesList); | ||
if (withinStatus !== false) await this._withinEnd(); | ||
} | ||
@@ -1073,0 +1060,0 @@ |
@@ -270,21 +270,2 @@ let EC; | ||
await this._withinEnd(); | ||
if (this.options.disableScreenshots) return; | ||
let fileName = clearString(test.title); | ||
if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`); | ||
if (this.options.uniqueScreenshotNames) { | ||
const uuid = test.uuid || test.ctx.test.uuid; | ||
fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`; | ||
} else { | ||
fileName += '.failed.png'; | ||
} | ||
return this.saveScreenshot(fileName, true).catch((err) => { | ||
if (err && | ||
err.type && | ||
err.type === 'RuntimeError' && | ||
err.message && | ||
(err.message.indexOf('was terminated due to') > -1 || err.message.indexOf('no such window: target window already closed') > -1) | ||
) { | ||
this.isRunning = false; | ||
} | ||
}); | ||
} | ||
@@ -291,0 +272,0 @@ |
@@ -81,3 +81,3 @@ const requireg = require('requireg'); | ||
* | ||
* #### Example #2: Wait for DOMContentLoaded event and 0 netowrk connections | ||
* #### Example #2: Wait for DOMContentLoaded event and 0 network connections | ||
* | ||
@@ -90,3 +90,3 @@ * ```json | ||
* "restart": false, | ||
* "waitForNavigation": "networkidle0", | ||
* "waitForNavigation": [ "domcontentloaded", "networkidle0" ], | ||
* "waitForAction": 500 | ||
@@ -190,3 +190,3 @@ * } | ||
retries: 3, | ||
when: err => err.message.indexOf('Cannot find context with specified id') > -1, | ||
when: err => err.message.indexOf('context') > -1, // ignore context errors | ||
}); | ||
@@ -1178,3 +1178,3 @@ if (this.options.restart && !this.options.manualStart) return this._startBrowser(); | ||
const args = Array.from(arguments); | ||
const fn = eval(args.shift()); // eslint-disable-line no-eval | ||
const fn = eval(`(${args.shift()})`); // eslint-disable-line no-eval | ||
return new Promise((done) => { | ||
@@ -1337,15 +1337,3 @@ args.push(done); | ||
async _failed(test) { | ||
const promisesList = []; | ||
await this._withinEnd(); | ||
if (!this.options.disableScreenshots) { | ||
let fileName = clearString(test.title); | ||
if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`); | ||
if (this.options.uniqueScreenshotNames) { | ||
const uuid = test.uuid || test.ctx.test.uuid; | ||
fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`; | ||
} else { | ||
fileName += '.failed.png'; | ||
} | ||
await this.saveScreenshot(fileName, true); | ||
} | ||
} | ||
@@ -1352,0 +1340,0 @@ |
@@ -29,2 +29,13 @@ const getParamNames = require('./utils').getParamNames; | ||
function loadCustomHook(module) { | ||
try { | ||
if (module.startsWith('.')) { | ||
module = fsPath.resolve(global.codecept_dir, module); // custom plugin | ||
} | ||
return require(module); | ||
} catch (err) { | ||
throw new Error(`Could not load hook from module '${module}':\n${err.message}`); | ||
} | ||
} | ||
function callSync(callable, done) { | ||
@@ -31,0 +42,0 @@ if (isAsync(callable)) { |
@@ -32,4 +32,6 @@ const { Parser } = require('gherkin'); | ||
metaStep.actor = step.keyword.trim(); | ||
metaStep.humanize = () => step.text; | ||
const setMetaStep = step => step.metaStep = metaStep; | ||
const setMetaStep = (step) => { | ||
if (step.metaStep) step = step.metaStep; // assign metastep to metastep for nested steps | ||
step.metaStep = metaStep; | ||
}; | ||
const fn = matchStep(step.text); | ||
@@ -36,0 +38,0 @@ if (step.argument) { |
const colors = require('chalk'); | ||
const symbols = require('mocha/lib/reporters/base').symbols; | ||
const figures = require('figures'); | ||
@@ -26,3 +26,3 @@ const styles = { | ||
*/ | ||
level: (level) => { | ||
level(level) { | ||
if (level !== undefined) outputLevel = level; | ||
@@ -36,3 +36,3 @@ return outputLevel; | ||
*/ | ||
process: (process) => { | ||
process(process) { | ||
if (process) outputProcess = `[${process}]`; | ||
@@ -45,5 +45,5 @@ return outputProcess; | ||
*/ | ||
debug: (msg) => { | ||
debug(msg) { | ||
if (outputLevel >= 2) { | ||
print(' '.repeat(this.stepShift), styles.debug(`> ${msg}`)); | ||
print(' '.repeat(this.stepShift), styles.debug(truncate(`${figures.pointerSmall} ${msg}`, this.spaceShift))); | ||
} | ||
@@ -55,4 +55,4 @@ }, | ||
*/ | ||
log: (msg) => { | ||
if (outputLevel >= 3) print(' '.repeat(this.stepShift), styles.log(` ${msg}`)); | ||
log(msg) { | ||
if (outputLevel >= 3) print(' '.repeat(this.stepShift), styles.log(truncate(` ${msg}`, this.spaceShift))); | ||
}, | ||
@@ -63,3 +63,3 @@ | ||
*/ | ||
error: (msg) => { | ||
error(msg) { | ||
print(styles.error(msg)); | ||
@@ -71,6 +71,10 @@ }, | ||
*/ | ||
success: (msg) => { | ||
success(msg) { | ||
print(styles.success(msg)); | ||
}, | ||
plugin(name, msg) { | ||
this.debug(`<${name}> ${msg}`); | ||
}, | ||
/** | ||
@@ -85,3 +89,4 @@ * Print a step | ||
if (outputLevel < 2) return; | ||
stepLine = ' '.repeat(2) + colors.green(stepLine); | ||
this.stepShift += 2; | ||
stepLine = colors.green(truncate(stepLine, this.spaceShift)); | ||
} | ||
@@ -91,31 +96,4 @@ if (step.comment) { | ||
} | ||
if (step.isMetaStep && step.isMetaStep() && outputLevel === 2) { | ||
stepLine += '\n'; // meta steps don't have execution time | ||
} | ||
const sym = ' '; | ||
if (outputLevel === 2) { | ||
newline = false; | ||
return process.stdout.write(`${' '.repeat(this.stepShift)} ${sym} ${stepLine}`); | ||
} | ||
print(' '.repeat(this.stepShift), `${sym} ${stepLine}`); | ||
}, | ||
/** | ||
* Print a step execution time | ||
*/ | ||
stepExecutionTime(step) { | ||
if (outputLevel < 2) return; | ||
if (!step) return; | ||
const time = (step.endTime - step.startTime) / 1000; | ||
if (Number.isNaN(time)) return; | ||
if (outputLevel === 2) { | ||
newline = true; | ||
process.stdout.write(`${styles.debug(` ("${time} sec")\n`)}`); | ||
return; | ||
} | ||
this.log(`Step finished in ${time} sec`); | ||
print(' '.repeat(this.stepShift), truncate(stepLine, this.spaceShift)); | ||
}, | ||
@@ -132,12 +110,12 @@ | ||
test: { | ||
started: (test) => { | ||
started(test) { | ||
print(` ${colors.magenta.bold(test.title)}`); | ||
}, | ||
passed: (test) => { | ||
print(` ${colors.green.bold(symbols.ok)} ${test.title} ${colors.grey(`in ${test.duration}ms`)}`); | ||
passed(test) { | ||
print(` ${colors.green.bold(figures.tick)} ${test.title} ${colors.grey(`in ${test.duration}ms`)}`); | ||
}, | ||
failed: (test) => { | ||
print(` ${colors.red.bold(symbols.err)} ${test.title} ${colors.grey(`in ${test.duration}ms`)}`); | ||
failed(test) { | ||
print(` ${colors.red.bold(figures.cross)} ${test.title} ${colors.grey(`in ${test.duration}ms`)}`); | ||
}, | ||
skipped: (test) => { | ||
skipped(test) { | ||
print(` ${colors.yellow.bold('S')} ${test.title}`); | ||
@@ -148,11 +126,9 @@ }, | ||
scenario: { | ||
started: (test) => { | ||
}, | ||
passed: (test) => { | ||
print(` ${colors.green.bold(`${symbols.ok} OK`)} ${colors.grey(`in ${test.duration}ms`)}`); | ||
started(test) {}, | ||
passed(test) { | ||
print(` ${colors.green.bold(`${figures.tick} OK`)} ${colors.grey(`in ${test.duration}ms`)}`); | ||
print(); | ||
}, | ||
failed: (test) => { | ||
print(` ${colors.red.bold(`${symbols.err} FAILED`)} ${colors.grey(`in ${test.duration}ms`)}`); | ||
failed(test) { | ||
print(` ${colors.red.bold(`${figures.cross} FAILED`)} ${colors.grey(`in ${test.duration}ms`)}`); | ||
print(); | ||
@@ -162,7 +138,7 @@ }, | ||
say: (message) => { | ||
say(message) { | ||
if (outputLevel >= 1) print(` ${colors.cyan.bold(message)}`); | ||
}, | ||
result: (passed, failed, skipped, duration) => { | ||
result(passed, failed, skipped, duration) { | ||
let style = colors.bgGreen; | ||
@@ -195,7 +171,15 @@ let msg = ` ${passed || 0} passed`; | ||
} | ||
console.log.apply(this, msg); | ||
} | ||
function ind() { | ||
return ' '; | ||
function truncate(msg, gap = 0) { | ||
if (msg.indexOf('\n') > 0) { | ||
return msg; // don't cut multi line steps | ||
} | ||
const width = (process.stdout.columns || 200) - gap - 1; | ||
if (msg.length > width) { | ||
msg = msg.substr(0, width - 1) + figures.ellipsis; | ||
} | ||
return msg; | ||
} |
@@ -8,3 +8,3 @@ const Base = require('mocha/lib/reporters/base'); | ||
const cursor = Base.cursor; | ||
let currentMetaStep = null; | ||
let currentMetaStep = []; | ||
@@ -31,2 +31,9 @@ class Cli extends Base { | ||
if (showSteps) { | ||
const Containter = require('../container'); | ||
output.print(); | ||
output.print(output.styles.debug(`Enabled Helpers: ${Object.keys(Containter.helpers()).join(', ')}`)); | ||
output.print(output.styles.debug(`Enabled Plugins: ${Object.keys(Containter.plugins()).join(', ')}`)); | ||
} | ||
runner.on('start', () => { | ||
@@ -63,3 +70,3 @@ console.log(); | ||
runner.on('test', (test) => { | ||
currentMetaStep = ''; | ||
currentMetaStep = []; | ||
if (test.steps) { | ||
@@ -71,14 +78,19 @@ output.test.started(test); | ||
event.dispatcher.on(event.step.started, (step) => { | ||
if (step.metaStep) { | ||
if (currentMetaStep !== step.metaStep.toString()) { | ||
output.step(step.metaStep); | ||
currentMetaStep = step.metaStep.toString(); | ||
output.stepShift = 3; | ||
const printMetaStep = (metaStep) => { | ||
if (!metaStep) return currentMetaStep.shift(); | ||
if (currentMetaStep.indexOf(metaStep.toString()) >= 0) return; // step is the same | ||
if (metaStep.metaStep) { | ||
printMetaStep(metaStep.metaStep); | ||
} | ||
} else { | ||
currentMetaStep = ''; | ||
} | ||
currentMetaStep.unshift(metaStep.toString()); | ||
output.step(metaStep); | ||
}; | ||
printMetaStep(step.metaStep); | ||
output.step(step); | ||
}); | ||
event.dispatcher.on(event.step.passed, step => output.stepExecutionTime(step)); | ||
event.dispatcher.on(event.step.failed, step => output.stepExecutionTime(step)); | ||
event.dispatcher.on(event.step.finished, (step) => { | ||
output.stepShift = 0; | ||
}); | ||
} | ||
@@ -106,6 +118,8 @@ | ||
// remove error message from stacktrace | ||
const lines = err.stack.split('\n'); | ||
lines.splice(0, 1); | ||
err.stack = lines.join('\n'); | ||
if (err.stack) { | ||
// remove error message from stacktrace | ||
const lines = err.stack.split('\n'); | ||
lines.splice(0, 1); | ||
err.stack = lines.join('\n'); | ||
} | ||
} | ||
@@ -112,0 +126,0 @@ if (output.level() < 3) { |
@@ -1,8 +0,7 @@ | ||
const Config = require('./config'); | ||
const path = require('path'); | ||
const STACK_LINE = 4; | ||
// using support objetcs for metastep detection | ||
// deprecated | ||
let support; | ||
const STACK_LINE = 3; | ||
/** | ||
@@ -40,5 +39,5 @@ * Each command in test executed through `I.` object is wrapped in Step. | ||
result = this.helper[this.helperMethod].apply(this.helper, this.args); | ||
this.status = 'success'; | ||
this.setStatus('success'); | ||
} catch (err) { | ||
this.status = 'failed'; | ||
this.setStatus('failed'); | ||
throw err; | ||
@@ -49,2 +48,7 @@ } | ||
setStatus(status) { | ||
this.status = status; | ||
if (this.metaStep) this.metaStep.setStatus(status); | ||
} | ||
humanize() { | ||
@@ -86,3 +90,2 @@ return this.name | ||
const lines = this.stack.split('\n'); | ||
// 3rd line is line where step has been called in test | ||
if (lines[STACK_LINE]) return lines[STACK_LINE].trim(); | ||
@@ -112,2 +115,6 @@ return ''; | ||
humanize() { | ||
return this.name; | ||
} | ||
setTrace() { | ||
@@ -124,3 +131,28 @@ } | ||
function detectMetaStep(stack) { | ||
if (!support) loadSupportObjects(); // deprecated | ||
for (let i = STACK_LINE; i < stack.length; i++) { | ||
const line = stack[i].trim(); | ||
if (isTest(line) || isBDD(line)) break; | ||
const fnName = line.match(/^at (\w+)\.(\w+)\s\(/); | ||
if (!fnName) continue; | ||
if (fnName[1] === 'Generator') return; // don't track meta steps inside generators | ||
if (fnName[1] === 'recorder') return; // don't track meta steps inside generators | ||
if (fnName[1] === 'Object') { | ||
// detect PO name from includes | ||
for (const name in support) { | ||
const file = support[name]; | ||
if (line.indexOf(file) > -1) { | ||
return new MetaStep(`${name}:`, fnName[2]); | ||
} | ||
} | ||
} | ||
return new MetaStep(`${fnName[1]}:`, fnName[2]); | ||
} | ||
} | ||
function loadSupportObjects() { | ||
const Config = require('./config'); | ||
const path = require('path'); | ||
support = Config.get('include', {}); | ||
@@ -135,29 +167,8 @@ if (support) { | ||
function detectMetaStep(stack) { | ||
if (!support) loadSupportObjects(); | ||
for (let i = STACK_LINE; i < stack.length; i++) { | ||
const line = stack[i].trim(); | ||
if (!isTest(line)) continue; | ||
// console.log('aaa'); | ||
if (i === STACK_LINE) return; | ||
for (const name in support) { | ||
const file = support[name]; | ||
const caller = stack[i - 1].trim(); | ||
// const caller = stack[i-1].trim().match(/^at (\w+\.\w+)\s\(/); | ||
if (caller.indexOf(file) > -1) { | ||
// console.log('YEAAAH!!!!', name); | ||
let fnName = caller.match(/^at \w+\.(.*?)\(/); | ||
if (fnName[1]) { | ||
fnName = fnName[1].trim(); | ||
} | ||
return new MetaStep(name, fnName); | ||
} | ||
} | ||
return; | ||
} | ||
} | ||
function isTest(line) { | ||
return line.trim().match(/^at Test\.Scenario/); | ||
} | ||
function isBDD(line) { | ||
return line.trim().match(/^at (Given|When|Then)/); | ||
} |
@@ -256,1 +256,15 @@ const fs = require('fs'); | ||
}; | ||
module.exports.deleteDir = function (dir_path) { | ||
if (fs.existsSync(dir_path)) { | ||
fs.readdirSync(dir_path).forEach(function (entry) { | ||
const entry_path = path.join(dir_path, entry); | ||
if (fs.lstatSync(entry_path).isDirectory()) { | ||
this.deleteDir(entry_path); | ||
} else { | ||
fs.unlinkSync(entry_path); | ||
} | ||
}); | ||
fs.rmdirSync(dir_path); | ||
} | ||
}; |
@@ -5,2 +5,3 @@ const output = require('./output'); | ||
const event = require('./event'); | ||
const Step = require('./step'); | ||
const { isGenerator, isAsyncFunction } = require('./utils'); | ||
@@ -14,6 +15,7 @@ const resumeTest = require('./scenario').resumeTest; | ||
return recorder.add('register within wrapper', () => { | ||
const metaStep = new Step.MetaStep('Within', `"${locator}"`); | ||
const defineMetaStep = step => step.metaStep = metaStep; | ||
recorder.session.start('within'); | ||
output.stepShift = 2; | ||
if (output.level() > 0) output.print(` Within ${output.colors.yellow.bold(locator)}:`); | ||
event.dispatcher.on(event.step.before, defineMetaStep); | ||
@@ -25,2 +27,3 @@ Object.keys(helpers).forEach((helper) => { | ||
const finalize = () => { | ||
event.dispatcher.removeListener(event.step.before, defineMetaStep); | ||
recorder.add('Finalize session within session', () => { | ||
@@ -27,0 +30,0 @@ output.stepShift = 1; |
{ | ||
"name": "codeceptjs", | ||
"version": "1.3.3", | ||
"version": "1.4.0", | ||
"description": "Modern Era Acceptance Testing Framework for NodeJS", | ||
@@ -39,7 +39,9 @@ "keywords": [ | ||
"dependencies": { | ||
"allure-js-commons": "^1.3.2", | ||
"chalk": "^1.1.3", | ||
"commander": "^2.16.0", | ||
"commander": "^2.18.0", | ||
"css-to-xpath": "^0.1.0", | ||
"cucumber-expressions": "^6.0.1", | ||
"escape-string-regexp": "^1.0.3", | ||
"figures": "^2.0.0", | ||
"fn-args": "^3.0.0", | ||
@@ -58,3 +60,3 @@ "gherkin": "^5.1.0", | ||
"@types/inquirer": "^0.0.35", | ||
"@types/node": "^8.10.21", | ||
"@types/node": "^8.10.29", | ||
"chai": "^3.4.1", | ||
@@ -66,3 +68,3 @@ "chai-as-promised": "^5.2.0", | ||
"eslint-config-airbnb-base": "^12.1.0", | ||
"eslint-plugin-import": "^2.13.0", | ||
"eslint-plugin-import": "^2.14.0", | ||
"faker": "^4.1.0", | ||
@@ -75,4 +77,4 @@ "gulp": "^3.9.1", | ||
"nyc": "^11.9.0", | ||
"protractor": "^5.3.2", | ||
"puppeteer": "^1.6.0", | ||
"protractor": "^5.4.1", | ||
"puppeteer": "^1.8.0", | ||
"rosie": "^1.6.0", | ||
@@ -83,3 +85,3 @@ "sinon": "^1.17.2", | ||
"unirest": "^0.5.1", | ||
"webdriverio": "^4.13.1", | ||
"webdriverio": "^4.13.2", | ||
"xmldom": "^0.1.27", | ||
@@ -86,0 +88,0 @@ "xpath": "0.0.27" |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1185156
211
24528
17
33
+ Addedallure-js-commons@^1.3.2
+ Addedfigures@^2.0.0
+ Addedallure-js-commons@1.3.2(transitive)
+ Addedfigures@2.0.0(transitive)
+ Addedfile-type@7.7.1(transitive)
+ Addedfs-extra@6.0.1(transitive)
+ Addedgraceful-fs@4.2.11(transitive)
+ Addedjs2xmlparser@3.0.0(transitive)
+ Addedjsonfile@4.0.0(transitive)
+ Addedmime@2.6.0(transitive)
+ Addeduniversalify@0.1.2(transitive)
+ Addeduuid@3.4.0(transitive)
+ Addedxmlcreate@1.0.2(transitive)
Updatedcommander@^2.18.0