├── .babelrc
├── .editorconfig
├── .eslintrc
├── .github
├── ISSUE_TEMPLATE
│ ├── --bug-report.md
│ ├── --documentation.md
│ ├── --feature-request.md
│ └── --question.md
└── stale.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── __tests__
├── app_features
│ └── app.feature
├── config
│ ├── wdio.app.android.conf.js
│ ├── wdio.app.ios.conf.js
│ ├── wdio.browsers.conf.js
│ └── wdio.shared.conf.js
├── features
│ ├── ambiguous.feature
│ ├── attach.feature
│ ├── data.feature
│ ├── failed.feature
│ ├── issue-16.feature
│ ├── no.scenario.feature
│ ├── outline.feature
│ ├── passed.feature
│ ├── pending.feature
│ ├── skipped.feature
│ └── undefined.feature
└── step_definitions
│ └── all.steps.js
├── docs
├── CONTRIBUTING.md
└── assets
│ ├── before-after.jpg
│ ├── custom-data.jpg
│ ├── feature-overview.jpg
│ ├── features-overview.jpg
│ ├── given-datatables.jpg
│ └── report-overview.jpg
├── lib
├── generateJson.js
└── reporter.js
├── package-lock.json
└── package.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "env"
4 | ],
5 | "plugins": [
6 | "transform-function-bind",
7 | "transform-object-rest-spread",
8 | "add-module-exports"
9 | ]
10 | }
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | indent_style = space
10 | indent_size = 4
11 |
12 | end_of_line = lf
13 | charset = utf-8
14 | trim_trailing_whitespace = true
15 | insert_final_newline = true
16 |
17 | [{.babelrc,.eslintrc,.travis.yml}]
18 | indent_size = 2
19 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "standard",
4 | "plugins": [
5 | "mocha"
6 | ],
7 | "rules": {
8 | "indent": [2, 4],
9 | "mocha/no-exclusive-tests": "error"
10 | },
11 | "globals": {
12 | "browser": true
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/--bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F41BBug report"
3 | about: Create a report to help us improve.
4 |
5 | ---
6 |
7 | **Environment (please complete the following information):**
8 | - Node.js version: [e.g. 8.9.1]
9 | - NPM version: [e.g. 5.8.0]
10 | - webdriver.io version: [e.g. 4.13.1]
11 | - wdio-cucumber-framework version: [e.g. 2.2.0 (if lower this module will not work as it should be)]
12 | - wdio-multiple-cucumber-html-reporter version: [e.g. 0.1.3]
13 |
14 | **Config of webdriver.io and the reporter**
15 | An example of how you configured the reporter in your webdriver.io config
16 |
17 | **Describe the bug**
18 | A clear and concise description of what the bug is.
19 |
20 | **To Reproduce**
21 | Steps to reproduce the behavior:
22 |
23 | [Include code or an example repository that can easily be set up]
24 |
25 | **Expected behavior**
26 | A clear and concise description of what you expected to happen.
27 |
28 | **Snapshots**
29 | If applicable, add snapshots to help explain your problem.
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/--documentation.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F4D6Documentation"
3 | about: Suggest improvements or report missing/unclear documentation.
4 |
5 | ---
6 |
7 | **Pre-check**
8 | - [ ] I'm aware that I can [edit the docs](https://github.com/wswebcreation/wdio-multiple-cucumber-html-reporter) and submit a pull request
9 |
10 | **Describe the improvement**
11 |
12 | I'd like to report
13 | - [ ] Unclear documentation
14 | - [ ] A typo
15 | - [ ] Missing documentation
16 | - [ ] Other
17 |
18 | **Description of the improvement / report**
19 | A clear and concise description.
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/--feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F4A1Feature request"
3 | about: Suggest an idea for this module.
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
9 |
10 | **Describe the solution you'd like**
11 | A clear and concise description of what you want to happen.
12 |
13 | **Describe alternatives you've considered**
14 | A clear and concise description of any alternative solutions or features you've considered.
15 |
16 | **Additional context**
17 | Add any other context or screenshots about the feature request here.
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/--question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F4ACQuestion"
3 | about: Ask questions.
4 |
5 | ---
6 |
7 | **Describe your question with as much detail as possible**
8 | A clear and concise question that doesn't require too much conversation. Need more help? [Find me on Gitter](https://gitter.im/wswebcreation)
9 |
10 |
11 | **If it's about a specific piece of code, try and include some of it to support your question.**
12 | [...]
13 |
14 |
15 | **Environment (please complete the following information):**
16 | - Node.js version: [e.g. 8.9.1]
17 | - NPM version: [e.g. 5.8.0]
18 | - webdriver.io version: [e.g. 4.13.1]
19 | - wdio-cucumber-framework version: [e.g. 2.2.0 (if lower this module will not work as it should be)]
20 | - wdio-multiple-cucumber-html-reporter version: [e.g. 0.1.3]
21 |
22 |
23 | **Additional context**
24 | Add any other context about the problem here.
25 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 14
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - bug
8 | # Label to use when marking an issue as stale
9 | staleLabel: wontfix
10 | # Comment to post when marking an issue as stale. Set to `false` to disable
11 | markComment: >
12 | This issue has been automatically marked as stale because it has not had
13 | recent activity. It will be closed if no further activity occurs. Thank you
14 | for your contributions.
15 | # Comment to post when closing a stale issue. Set to `false` to disable
16 | closeComment: false
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Directory for instrumented libs generated by jscoverage/JSCover
6 | lib-cov
7 |
8 | # Coverage directory used by tools like istanbul
9 | coverage
10 |
11 | # Compiled binary addons (http://nodejs.org/api/addons.html)
12 | build
13 |
14 | # Dependency directory
15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
16 | node_modules
17 |
18 | # JetBrains IDE
19 | .idea/
20 | .tmp/
21 | .dist/
22 | build/
23 |
24 | #Visual Studio Code
25 | .DS_Store
26 |
27 | package-lock.json
28 |
29 | apps/
30 |
31 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Directory for instrumented libs generated by jscoverage/JSCover
6 | lib-cov
7 |
8 | # Coverage directory used by tools like istanbul
9 | coverage
10 |
11 | # Dependency directory
12 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
13 | node_modules
14 |
15 | # JetBrains IDE
16 | .idea
17 |
18 | package-lock.json
19 |
20 | .tmp/
21 | .github/
22 | __tests__/
23 | apps/
24 | docs/
25 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | CHANGELOG
2 | =========
3 |
4 | See [Releases](https://github.com/wswebcreation/wdio-multiple-cucumber-html-reporter/releases)
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Wim Selles
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wdio-multiple-cucumber-html-reporter
2 | A WebdriverIO reporter plugin. Reporter that creates beautiful Cucumber HTML reports by using [multiple-cucumber-html-reporter](https://github.com/wswebcreation/multiple-cucumber-html-reporter)
3 |
4 | > **THIS MODULE IS DEPRECATED AND IS NOT MAINTAINED ANYMORE. IT DOESN'T SUPPORT CUCUMBERJS WITH WEBDRIVERIO V5 AND IT CAN'T BE MIGRATED TO V5 DUE TO TECHNICAL LIMITATIONS**
5 | > **IF YOU WANT TO USE CUCUMBERJS WITH WEBDRIVERIO V5 AND A SIMILAR IMPLEMENTATION LIKE THIS, PLEASE CHECK [wdio-cucumberjs-json-reporter](https://github.com/wswebcreation/wdio-cucumberjs-json-reporter)**
6 |
7 |
8 | > **~THIS REPORTER IS STILL UNDER DEVELOPMENT, FEEL FREE TO ADD PR'S~**
9 |
10 | > **THIS REPORTER NEEDS TO USE `wdio-cucumber-framework` `2.2.5` OR HIGHER, SEE FAQ'S**
11 |
12 | > **`multiple-cucumber-html-reporter`, WHICH IS USED IN THIS REPORTER PLUGIN WILL ONLY WORK ON NODE >= 7**
13 |
14 | > **THIS MODULE CAN ONLY WORK WITH WebdriverIO V4 BECAUSE `wdio-cucumber-framework` IS NOT V5 COMPATIBLE**
15 |
16 | ## What does it do?
17 | This reporter does two things
18 |
19 | 1. First of all it creates a CucumberJS-JSON file. This is needed because the BaseReporter of webdriver.io overwrites the CucumberJS report output
20 | 2. Based on the created JSON it creates a HTML-report like below
21 |
22 | 
23 |
24 | 3. It also supports `Given|When|Then`-keywords and Datatables
25 |
26 | 
27 |
28 | 4. Data can also be added to **steps** (so not `before|after`-hooks) with the following code
29 |
30 |
31 | ```js
32 | import multipleCucumberHtmlReporter from 'wdio-multiple-cucumber-html-reporter';
33 |
34 | // Attach a string
35 | multipleCucumberHtmlReporter.attach('just a string');
36 | // Attach JSON
37 | multipleCucumberHtmlReporter.attach({"json-string": true}, 'application/json');
38 | // Attach a screenshot
39 | multipleCucumberHtmlReporter.attach(browser.saveScreenshot(), 'image/png');
40 | // Or with
41 | multipleCucumberHtmlReporter.attach(browser.screenshot(), 'image/png');
42 | ```
43 |
44 | 5. Data can also be added to **`before|after`**-hooks. You can use the same method as above
45 | but you need to add an extra `hookType`, which is a string. For the `before`-hook you use the string `before` and for the `after`-hook you can use the string `after`.
46 | See the examples below
47 |
48 | > **Keep in mind this is only for the `before|after`-hooks, if you add them in steps you will mess up your own report**
49 |
50 | ```js
51 | import multipleCucumberHtmlReporter from 'wdio-multiple-cucumber-html-reporter';
52 |
53 | // BEFORE
54 | // Attach a string in a before hook
55 | multipleCucumberHtmlReporter.attach('just a string', 'before');
56 | // Attach JSON in a before hook
57 | multipleCucumberHtmlReporter.attach({"json-string": true}, 'application/json', 'before');
58 | // Attach a screenshot in a before hook
59 | multipleCucumberHtmlReporter.attach(browser.saveScreenshot(), 'image/png', 'before');
60 | // Or with
61 | multipleCucumberHtmlReporter.attach(browser.screenshot(), 'image/png', 'before');
62 |
63 |
64 | // AFTER
65 | // Attach a string in a after hook
66 | multipleCucumberHtmlReporter.attach('just a string', 'after');
67 | // Attach JSON in a after hook
68 | multipleCucumberHtmlReporter.attach({"json-string": true}, 'application/json', 'after');
69 | // Attach a screenshot in a after hook
70 | multipleCucumberHtmlReporter.attach(browser.saveScreenshot(), 'image/png', 'after');
71 | // Or with
72 | multipleCucumberHtmlReporter.attach(browser.screenshot(), 'image/png', 'after');
73 | ```
74 |
75 | Keep in mind that this will add a passed step for the `before|after`-hook as can be seen here
76 |
77 | 
78 |
79 | > Not all options / data that is provided in [multiple-cucumber-html-reporter](https://github.com/wswebcreation/multiple-cucumber-html-reporter) can be used due to limitations in the generated JSON file by this reporter
80 |
81 |
82 | ## Installation
83 | The easiest way is to keep `wdio-multiple-cucumber-html-reporter` as a devDependency in your `package.json`.
84 |
85 | ```json
86 | {
87 | "devDependencies": {
88 | "wdio-multiple-cucumber-html-reporter": "^0.2.0"
89 | }
90 | }
91 | ```
92 |
93 | You can simple do it by:
94 |
95 | ```bash
96 | npm install wdio-multiple-cucumber-html-reporter --save-dev
97 | ```
98 |
99 | so it will be added automatically to your `package.json`
100 |
101 | Instructions on how to install `WebdriverIO` can be found [here](http://webdriver.io/guide/getstarted/install.html).
102 |
103 | ## Configuration
104 | Configure the output directory in your wdio.conf.js file:
105 |
106 | ```js
107 | exports.config = {
108 | // ...
109 | reporters: ['multiple-cucumber-html'],
110 | reporterOptions: {
111 | htmlReporter: {
112 | jsonFolder: './tmp',
113 | reportFolder: `./tmp/report`,
114 | // ... other options, see Options
115 | }
116 | },
117 | // ...
118 | }
119 | ```
120 |
121 | ## Options
122 | ### `jsonFolder`
123 | - **Type:** `String`
124 | - **Mandatory:** Yes
125 |
126 | The directory where the JSON file, generated by this report, will be stored, relative from where the script is started.
127 |
128 | **N.B.:** If you use a npm script from the command line, like for example `npm run test` the `jsonFolder` will be relative from the path where the script is executed. Executing it from the root of your project will also create the `jsonFolder` in the root of you project.
129 |
130 | ### `reportFolder`
131 | - **Type:** `String`
132 | - **Mandatory:** Yes
133 |
134 | The directory in which the report needs to be saved, relative from where the script is started.
135 |
136 | **N.B.:** If you use a npm script from the command line, like for example `npm run test` the `reportFolder` will be relative from the path where the script is executed. Executing it from the root of your project will also save the report in the `reportFolder` in the root of you project.
137 |
138 | ### `removeFolders`
139 | - **Type:** `boolean`
140 | - **Mandatory:** No
141 |
142 | If `true` the the `jsonFolder` and the `reportFolder` will be removed to start the test with a clean state.
143 |
144 | ### `openReportInBrowser`
145 | - **Type:** `boolean`
146 | - **Mandatory:** No
147 |
148 | If `true` the report will automatically be opened in the default browser of the operating system.
149 |
150 | ### `saveCollectedJSON`
151 | - **Type:** `boolean`
152 | - **Mandatory:** No
153 |
154 | This reporter will first get the JSON-file and then enrich it with data that is used for the report. If `saveCollectedJSON :true` the JSON **AND** the enriched JSON will be saved in the `reportFolder`. They will be saved as:
155 |
156 | - `merged-output.json`
157 | - `enriched-output.json`
158 |
159 | ### `disableLog`
160 | - **Type:** `boolean`
161 | - **Mandatory:** No
162 | - **Default:** `false`
163 |
164 | This will disable the log so will **NOT** see this.
165 |
166 | ```shell
167 | =====================================================================================
168 | Multiple Cucumber HTML report generated in:
169 |
170 | /Users/wswebcreation/multiple-cucumber-html-reporter/.tmp/index.html
171 | ========================================================================
172 | ```
173 | ### `pageTitle`
174 | - **Type:** `string`
175 | - **Mandatory:** No
176 | - **Default:** Multiple Cucumber HTML Reporter
177 |
178 | You can change the report title in the HTML head Tag
179 |
180 | ### `reportName`
181 | - **Type:** `string`
182 | - **Mandatory:** No
183 |
184 | You can change the report name to a name you want
185 |
186 | ### `pageFooter`
187 | - **Type:** `string`
188 | - **Mandatory:** No
189 |
190 | You can customise Page Footer if required. You just need to provide a html string like `
`
191 |
192 | ### `displayDuration`
193 | - **Type:** `boolean`
194 | - **Mandatory:** No
195 |
196 | If set to `true` the duration of steps, scenarios and features is displayed on the Features overview and single feature page in an easily readable format.
197 | This expects the durations in the report to be in **nanoseconds**, which might result in incorrect durations when using a version of Cucumber(JS 2 and 3) that does not report in nanoseconds but in milliseconds. This can be changed to milliseconds by adding the parameter `durationInMS: true`, see below
198 |
199 | > **NOTE: Only the duration of a feature can be shown in the features overview. A total duration over all features CAN NOT be given because the module doesn't know if all features have been run in parallel**
200 |
201 | ### `durationInMS`
202 | - **Type:** `boolean`
203 | - **Default:** `false`
204 | - **Mandatory:** No
205 |
206 | If set to `true` the duration of steps will be expected to be in **milliseconds**, which might result in incorrect durations when using a version of Cucumber(JS 1 or 4) that does report in **nanaseconds**.
207 | This parameter relies on `displayDuration: true`
208 |
209 | ### `customStyle`
210 | - **Type:** `path`
211 | - **Mandatory:** No
212 |
213 | If you need add some custom style to your report. Add it like this `customStyle: 'your-path-where/custom.css'`
214 |
215 | ### `overrideStyle`
216 | - **Type:** `path`
217 | - **Mandatory:** No
218 |
219 | If you need replace default style for your report. Add it like this `overrideStyle: 'your-path-where/custom.css'`
220 |
221 | ### `customData`
222 | - **Type:** `object`
223 | - **Mandatory:** No
224 |
225 | You can add a custom data block to the report like this
226 |
227 | 
228 |
229 | ```js
230 | customData: {
231 | title: 'Run info',
232 | data: [
233 | {label: 'Project', value: 'Custom project'},
234 | {label: 'Release', value: '1.2.3'},
235 | {label: 'Cycle', value: 'B11221.34321'},
236 | {label: 'Execution Start Time', value: 'Nov 19th 2017, 02:31 PM EST'},
237 | {label: 'Execution End Time', value: 'Nov 19th 2017, 02:56 PM EST'}
238 | ]
239 | }
240 | ```
241 |
242 | #### `customData.title`
243 | - **Type:** `string`
244 | - **Mandatory:** No
245 | - **Default:** `Custom data title`
246 |
247 | Select a title for the custom data block. If not provided it will be defaulted.
248 |
249 | #### `customData.data`
250 | - **Type:** `array`
251 | - **Mandatory:** yes
252 |
253 | The data you want to add. This needs to be in the format
254 |
255 | ```js
256 | data: [
257 | {label: 'your label', value: 'the represented value'}
258 | ]
259 | ```
260 |
261 | ## Metadata
262 | The report can also show on which browser / device a feature has been executed. It is shown on the features overview in the table, like this
263 | 
264 | as well as on the feature overview in the container, like this
265 | 
266 |
267 | You can add this by adding the following object to your `capabilities`;
268 |
269 | ```js
270 | exports.config = {
271 | //..
272 | capabilities: [
273 | {
274 | browserName: 'chrome',
275 | // Add this
276 | metadata: {
277 | browser: {
278 | name: 'chrome',
279 | version: '58'
280 | },
281 | device: 'MacBook Pro 15',
282 | platform: {
283 | name: 'OSX',
284 | version: '10.12.6'
285 | }
286 | },
287 | },
288 | ],
289 | };
290 | ```
291 |
292 | See the metadata information [here](https://github.com/wswebcreation/multiple-cucumber-html-reporter#metadatabrowsername) for the correct values.
293 |
294 | > If you don't provide the `browser`-object in the metadata, this module will automatically determine it for you
295 |
296 | > If you don't provide the `device` and or the `platform`-object it will be defaulted for you to `not known`
297 |
298 | > If you don't provide a `browser.name` or a `browser.version` the module will try to determine this automatically. The rest will be shown as questionmarks in the report
299 |
300 | ## FAQ's
301 |
302 | ### How do I add screenshots to the report
303 | Just create a `After`-hook in a stepfile like this
304 |
305 | ```js
306 | const {After, Status} = require('cucumber');
307 | import multipleCucumberHtmlReporter from 'wdio-multiple-cucumber-html-reporter';
308 |
309 | After((scenarioResult)=>{
310 | if (scenarioResult.result.status === Status.FAILED) {
311 | // It will add the screenshot to the JSON
312 | multipleCucumberHtmlReporter.attach(browser.saveScreenshot(), 'image/png', 'after');
313 | // Or with
314 | multipleCucumberHtmlReporter.attach(browser.screenshot(), 'image/png', 'after');
315 | }
316 | return scenarioResult.status;
317 | });
318 | ```
319 |
320 | ### I don't see the keywords `Given, When, Then` in the report
321 | The module `wdio-cucumber-framework` only provides this information from version `2.2.0` and higher. Please upgrade to that version.
322 |
323 | ### `skipped` steps are marked as `pending`
324 | `skipped` steps are currently marked as `pending` because `wdio-cucumber-framework` can't distinguish them, there is a PR for this, see [here](https://github.com/webdriverio/wdio-cucumber-framework/pull/34)
325 |
326 | ### `ambiguous` steps are marked as `pending`
327 | CucumberJS has a status called `ambiguous`, this should also be shown in the report.
328 | Because `wdio-cucumber-framework` has it's own implementation to handle ambiguous steps , see [here](https://github.com/webdriverio/wdio-cucumber-framework#failambiguousdefinitions)
329 | it will not show the correct status in the report using this module.
330 |
331 | ### `undefined` steps are not marked as `undefined`
332 | CucumberJS has a status called `undefined`, this should also be shown in the report.
333 | Because `wdio-cucumber-framework` has it's own implementation to handle undefined steps , see [here](https://github.com/webdriverio/wdio-cucumber-framework#ignoreundefineddefinitions)
334 | it will not show the correct status in the report using this module.
335 |
336 | ## Changelog
337 | A changelog can be found [here](./CHANGELOG.md).
338 |
339 | ## Contributing
340 | How to contribute can be found [here](./docs/CONTRIBUTING.md).
341 |
342 | ## TODO:
343 | ### high priority
344 | Needs to be in the first beta
345 | - [x] Add `Before`-step to the json, see the remarks about the Before steps
346 | - [x] Add `After`-step to the json, see the remarks about the After steps
347 | - [x] Add browser data to the report, first start with the default capabilities
348 | - [x] Add screenshots to the report
349 | - [x] Add multiple screenshots to the report in 1 step
350 | - [x] Test in multiple browsers in parallel
351 | - [x] Check / add `Passed` status
352 | - [x] Check / add `Failed` status
353 | - [x] Check / add `Pending` status
354 | - [x] Check / add `Ambiguous` status
355 | - [x] Check / add `Skipped` status
356 | - [x] Check / add `undefined` status
357 |
358 | ### low priority
359 | Needs to be in, but are not mandatory
360 | - [ ] Investigate `Hooks` and if they can influence the outcome
361 | - [ ] Write UT's
362 | - [x] Test on Windows
363 | - [x] Test on Android
364 | - [x] Test on iOS
365 |
366 | ### research
367 | - [x] Find out where the keywords are, there is no `Given, When, Then` comming back from webdriver.io => => Created a PR for [wdio-cucumber-framework 136](https://github.com/webdriverio/wdio-cucumber-framework/pull/136) that has been merged and released as version `2.2.0`
368 | - [x] Add data tables to the report, see [data tables](https://github.com/cucumber/cucumber-js/blob/master/features/data_tables.feature) => Created a PR for [wdio-cucumber-framework 136](https://github.com/webdriverio/wdio-cucumber-framework/pull/136) that has been merged and released as version `2.2.0`
369 | - [ ] Find out where the description of the feature is. Can't find it in the methods and it will not be added to the report then
370 |
371 | ## Some remarks
372 | ### Before hooks
373 | Before hooks are not added to the WebdriverIO reporter. There is no way in telling they passed / failed.
374 | #### Pass
375 | Not logged in `wdio-cucumberjs-framework` => not in this module
376 | #### Failed
377 | Automatically logged by my implementation, not all data is logged like screenshots and so on
378 |
379 | #### Pending
380 | Pending state will result in the following:
381 | - Beforehook will not get the status, there is only a start, not a pass/failed/pending
382 | - All Scenario steps will get status pending
383 | Meaning I can't log this
384 |
385 | ### After hooks
386 | After hooks are not added to the WebdriverIO reporter. There is no way in telling they passed / failed
387 | #### Pass
388 | Not logged in `wdio-cucumberjs-framework` => not in this module
389 | #### Failed
390 | Automatically logged by my implementation, not all data is logged like screenshots and so on
391 |
392 | #### Pending
393 | Status pending of the After hook has no effect on the status of the report / wdio-cucumber-framework will not report this status
394 |
--------------------------------------------------------------------------------
/__tests__/app_features/app.feature:
--------------------------------------------------------------------------------
1 | @feature-tag
2 | Feature: Open the WDIO app
3 |
4 | Scenario: Open website
5 | Given I open the app
6 | Then the home screen is shown
7 |
--------------------------------------------------------------------------------
/__tests__/config/wdio.app.android.conf.js:
--------------------------------------------------------------------------------
1 | const { join } = require('path');
2 | const { config } = require('./wdio.shared.conf');
3 |
4 | // ============
5 | // Capabilities
6 | // ============
7 | // For all capabilities please check
8 | // http://appium.io/docs/en/writing-running-appium/caps/#general-capabilities
9 | config.capabilities = [
10 | {
11 | // The defaults you need to have in your config
12 | automationName: 'UiAutomator2',
13 | deviceName: 'Pixel_8.1',
14 | platformName: 'Android',
15 | platformVersion: '8.1',
16 | orientation: 'PORTRAIT',
17 | maxInstances: 1,
18 | app: join(process.cwd(), './apps/Android-NativeDemoApp-0.2.1.apk'),
19 | // Read the reset strategies very well, they differ per platform, see
20 | // http://appium.io/docs/en/writing-running-appium/other/reset-strategies/
21 | noReset: true,
22 | newCommandTimeout: 240,
23 | },
24 | ];
25 |
26 | // ====================
27 | // Specs
28 | // ====================
29 | config.specs = [ `${ process.cwd() }/__tests__/app_features/app.feature` ];
30 |
31 | // ====================
32 | // Appium Configuration
33 | // ====================
34 | // Default port for Appium
35 | config.port = 4723;
36 |
37 | exports.config = config;
38 |
--------------------------------------------------------------------------------
/__tests__/config/wdio.app.ios.conf.js:
--------------------------------------------------------------------------------
1 | const { join } = require('path');
2 | const { config } = require('./wdio.shared.conf');
3 |
4 | // ============
5 | // Capabilities
6 | // ============
7 | // For all capabilities please check
8 | // http://appium.io/docs/en/writing-running-appium/caps/#general-capabilities
9 | config.capabilities = [
10 | {
11 | // The defaults you need to have in your config
12 | deviceName: 'iPhone X',
13 | platformName: 'iOS',
14 | platformVersion: '12.2',
15 | orientation: 'PORTRAIT',
16 | maxInstances: 1,
17 | // The path to the app
18 | app: join(process.cwd(), './apps/iOS-Simulator-NativeDemoApp-0.2.1.app.zip'),
19 | // Read the reset strategies very well, they differ per platform, see
20 | // http://appium.io/docs/en/writing-running-appium/other/reset-strategies/
21 | noReset: true,
22 | newCommandTimeout: 240,
23 | },
24 | ];
25 |
26 | // ====================
27 | // Specs
28 | // ====================
29 | config.specs = [ `${ process.cwd() }/__tests__/app_features/app.feature` ];
30 |
31 | // ====================
32 | // Appium Configuration
33 | // ====================
34 | // Default port for Appium
35 | config.port = 4723;
36 |
37 | exports.config = config;
38 |
--------------------------------------------------------------------------------
/__tests__/config/wdio.browsers.conf.js:
--------------------------------------------------------------------------------
1 | const { config } = require('./wdio.shared.conf')
2 |
3 | config.capabilities = [
4 | {
5 | maxInstances: 5,
6 | browserName: 'chrome',
7 | chromeOptions: {
8 | args: [ '--headless', 'disable-infobars' ],
9 | prefs: {
10 | download: {
11 | prompt_for_download: false,
12 | directory_upgrade: true,
13 | default_directory: './tmp',
14 | },
15 | },
16 | },
17 | metadata: {
18 | browser: {
19 | name: 'chrome',
20 | version: '58'
21 | },
22 | device: 'MacBook Pro 15',
23 | platform: {
24 | name: 'OSX',
25 | version: '10.12.6'
26 | }
27 | },
28 | },
29 | // {
30 | // maxInstances: 5,
31 | // browserName: 'firefox',
32 | // 'moz:firefoxOptions': {
33 | // args: ['-headless']
34 | // }
35 | // },
36 | ];
37 |
38 | exports.config = config;
39 |
--------------------------------------------------------------------------------
/__tests__/config/wdio.shared.conf.js:
--------------------------------------------------------------------------------
1 | const { argv } = require('yargs');
2 | const chai = require('chai');
3 | const multipleCucumberHtmlReporter = require('../../build/reporter');
4 | multipleCucumberHtmlReporter.reporterName = 'multiple-cucumber-html-reporter';
5 |
6 | exports.config = {
7 | // ==================
8 | // Test Configuration
9 | // ==================
10 | sync: true,
11 | logLevel: argv.logLevel || 'silent',
12 | coloredLogs: true,
13 | bail: 0,
14 | baseUrl: 'http://webdriver.io',
15 | waitforTimeout: 20000,
16 | connectionRetryTimeout: 90000,
17 | connectionRetryCount: 3,
18 |
19 | // ======================
20 | // Cucumber configuration
21 | // ======================
22 | framework: 'cucumber',
23 | specs: getFeatureFiles(),
24 | cucumberOpts: {
25 | require: [
26 | '__tests__/**/*.steps.js',
27 | ],
28 | backtrace: false,
29 | compiler: [ 'js:babel-register' ],
30 | colors: true,
31 | snippets: true,
32 | source: true,
33 | tagExpression: 'not @wip and not @ignore',
34 | timeout: 60000,
35 | failAmbiguousDefinitions: false,
36 | ignoreUndefinedDefinitions: false,
37 | },
38 |
39 | // ====================
40 | // Reporter
41 | // ====================
42 | reporters: [ 'spec', multipleCucumberHtmlReporter ],
43 | reporterOptions: {
44 | htmlReporter: {
45 | removeFolders: true,
46 | jsonFolder: '.tmp/new/',
47 | reportFolder: '.tmp/multiple-cucumber-html-reporter/',
48 | displayDuration: true,
49 | openReportInBrowser: true,
50 | saveCollectedJSON: true,
51 | disableLog: true,
52 | pageTitle: 'pageTitle',
53 | reportName: 'reportName',
54 | pageFooter: 'Custom footer
',
55 | customData: {
56 | title: 'Run info',
57 | data: [
58 | { label: 'Project', value: 'Custom project' },
59 | { label: 'Release', value: '1.2.3' },
60 | { label: 'Cycle', value: 'B11221.34321' },
61 | { label: 'Execution Start Time', value: 'Nov 19th 2017, 02:31 PM EST' },
62 | { label: 'Execution End Time', value: 'Nov 19th 2017, 02:56 PM EST' }
63 | ]
64 | },
65 | }
66 | },
67 |
68 | /**
69 | * Gets executed before test execution begins. At this point you can access to all global
70 | * variables like `browser`.
71 | */
72 | before: () => {
73 | /**
74 | * Setup the Chai assertion framework
75 | */
76 | global.expect = chai.expect;
77 | global.assert = chai.assert;
78 | global.should = chai.should();
79 | },
80 | };
81 |
82 | /**
83 | * Get the featurefiles that need to be run based on an command line flag that is passed,
84 | * if nothing is passed all the featurefiles are run
85 | *
86 | * @example:
87 | *
88 | *
89 | * // For 1 feature
90 | * npm run test -- --feature=playground
91 | *
92 | * // For multiple features
93 | * npm run test -- --feature=playground,login,...
94 | *
95 | * // Else
96 | * npm run test
97 | *
98 | */
99 | function getFeatureFiles() {
100 | if (argv.feature) {
101 | return argv.feature.split(',').map(feature => `${ process.cwd() }/__tests__/**/${ feature }.feature`);
102 | }
103 |
104 | return [ `${ process.cwd() }/__tests__/**/*.feature` ];
105 | }
106 |
--------------------------------------------------------------------------------
/__tests__/features/ambiguous.feature:
--------------------------------------------------------------------------------
1 | Feature: Create an ambiguous feature
2 |
3 | Scenario: an ambiguous scenario
4 | Given an ambiguous step
5 |
--------------------------------------------------------------------------------
/__tests__/features/attach.feature:
--------------------------------------------------------------------------------
1 | #@wip
2 | Feature: Example
3 |
4 | Scenario: example attatch
5 | Given I open the url "http://127.0.0.1:8080/"
6 | When I'm a scenario when step
7 | Then I'm a scenario then step
8 |
--------------------------------------------------------------------------------
/__tests__/features/data.feature:
--------------------------------------------------------------------------------
1 | #@wip
2 | Feature: Create data feature
3 | This is a feature description
4 | Second description
5 |
6 | # Background: This is a background step
7 | ## This is a background description
8 | # Given I'm a given background
9 | ## When I'm a when background
10 | ## Then I would be a when background
11 |
12 | Scenario: a table scenario
13 | This should be a scenario description
14 | Given a table step
15 | | Cucumber | Cucumis sativus |
16 | | Burr Gherkin | Cucumis anguria |
17 | When I'm a scenario when step
18 | Then I'm a scenario then step
19 |
--------------------------------------------------------------------------------
/__tests__/features/failed.feature:
--------------------------------------------------------------------------------
1 | @feature-tag
2 | Feature: Create failed feature
3 |
4 | @scenario-tag
5 | Scenario: Open website
6 | Given I open "http://webdriver.io/"
7 | Then the title would say "WebdriverIO"
8 |
--------------------------------------------------------------------------------
/__tests__/features/issue-16.feature:
--------------------------------------------------------------------------------
1 | Feature: Create ignored undefined feature bassed on issue 16
2 |
3 | @ignore
4 | Scenario: I ignore this undefined scenario
5 | Given I ignore what I want to ignore
6 | Then this should not complain or break with an ignored error
7 |
--------------------------------------------------------------------------------
/__tests__/features/no.scenario.feature:
--------------------------------------------------------------------------------
1 | Feature: This feature has no scenario steps
2 |
--------------------------------------------------------------------------------
/__tests__/features/outline.feature:
--------------------------------------------------------------------------------
1 | Feature: Create scenario outline feature
2 | This is a feature description
3 | Second description
4 |
5 | Scenario Outline: a scenario outline header1: and header2:
6 | Given an outline step
7 | And an outline step
8 |
9 | Examples:
10 | | header1 | header2 |
11 | | outline 1 | value1 |
12 | | outline 2 | value2 |
13 |
--------------------------------------------------------------------------------
/__tests__/features/passed.feature:
--------------------------------------------------------------------------------
1 | @feature-tag
2 | Feature: Create passed feature
3 |
4 | @scenario-tag
5 | Scenario: Open website
6 | Given I open "http://webdriver.io/"
7 | Then the title would say "WebdriverIO · Next-gen WebDriver test framework for Node.js"
8 |
9 | @scenario-tag
10 | Scenario: Open other website
11 | Given I open "https://developer.mozilla.org/nl/"
12 | Then the title would say "MDN-webdocumenten"
13 |
--------------------------------------------------------------------------------
/__tests__/features/pending.feature:
--------------------------------------------------------------------------------
1 | Feature: Create a pending feature
2 |
3 | Scenario: a pending scenario
4 | Given a pending step
5 |
--------------------------------------------------------------------------------
/__tests__/features/skipped.feature:
--------------------------------------------------------------------------------
1 | Feature: Create a skipped feature
2 |
3 | Scenario: a skipped scenario
4 | Given a skipped step
5 |
--------------------------------------------------------------------------------
/__tests__/features/undefined.feature:
--------------------------------------------------------------------------------
1 | @wip
2 | Feature: Create an undefined feature
3 |
4 | Scenario: an undefined scenario
5 | Given an undefined step
6 |
--------------------------------------------------------------------------------
/__tests__/step_definitions/all.steps.js:
--------------------------------------------------------------------------------
1 | import { After, Before, Given, Status, Then, When } from 'cucumber';
2 | import multipleCucumberHtmlReporter from '../../build/reporter';
3 |
4 | Given(/I open "(.*)"/, function (url) {
5 | browser.url(url);
6 | /**
7 | * Enable below to test with double screenshots with the old way
8 | */
9 | // this.attach(browser.saveScreenshot(), 'image/png');
10 | // this.attach(browser.saveScreenshot(), 'image/png');
11 | /**
12 | * Enable below to test with double screenshots with the new way
13 | */
14 | // browser.saveScreenshot();
15 | // browser.saveScreenshot();
16 | });
17 |
18 | Given(/a table step/, table => {
19 | console.log('table = ', table);
20 | });
21 |
22 | Given(/a (skipped|pending) step/, stepType => {
23 | return Promise.resolve(stepType);
24 | });
25 |
26 | Given(/an ambiguous step/, stepType => {
27 | return Promise.resolve('ambiguous');
28 | });
29 |
30 | Given(/an ambiguous step/, stepType => {
31 | return Promise.resolve('ambiguous');
32 | });
33 |
34 | Given(/an outline (.*) step/, outline => {
35 | return Promise.resolve();
36 | });
37 |
38 | Then(/the title would say "(.*)"/, title => {
39 | expect(browser.getTitle()).to.equal(title);
40 | });
41 |
42 |
43 | Before((scenarioResult) => {
44 | // console.log('Before-hook');
45 | // console.log('beforeData = ', scenarioResult);
46 | // browser.pause(5000)
47 | // expect(true).to.equal(true);
48 | // return Promise.resolve('pending');
49 |
50 | multipleCucumberHtmlReporter.attach('Before string 1', 'before');
51 | multipleCucumberHtmlReporter.attach('Before string 2', 'text/plain', 'before');
52 | });
53 |
54 | /**
55 | * Hook for the old report hook
56 | */
57 | // After(function (scenarioResult) {
58 | // console.log('After-hook');
59 | // if (scenarioResult.status === Status.FAILED) {
60 | // this.attach(browser.saveScreenshot(), 'image/png');
61 | // }
62 | //
63 | // // console.log('after data = ', scenarioResult);
64 | // // return scenarioResult.status;
65 | // // expect(true).to.equal(true);
66 | // // return 'pending';
67 | //
68 | // return scenarioResult.status;
69 | // });
70 |
71 | /**
72 | * Hook for the new
73 | */
74 | // After(function (scenarioResult) {
75 | // if (scenarioResult.result.status === Status.FAILED) {
76 | // browser.saveScreenshot()
77 | // }
78 | //
79 | // this.attach('{"name": "some JSON"}', 'application/json');
80 | // this.attach('Some text');
81 | //
82 | // return scenarioResult.status;
83 | // });
84 |
85 | After(scenarioResult => {
86 | if (scenarioResult.result.status === Status.FAILED) {
87 | // It will add the screenshot to the JSON
88 | multipleCucumberHtmlReporter.attach('After string 1', 'after');
89 | multipleCucumberHtmlReporter.attach(browser.screenshot(), 'image/png', 'after');
90 | multipleCucumberHtmlReporter.attach('After string 2', 'text/plain', 'after');
91 | }
92 | return scenarioResult.status;
93 | });
94 |
95 | /**
96 | * Some extra's
97 | */
98 | Given(/I'm a given background/, () => {});
99 | When(/I'm a when background/, () => {});
100 | When(/I'm a scenario when step/, () => {});
101 | Then(/I would be a when background/, () => {});
102 | Then(/I'm a scenario then step/, () => {});
103 |
104 |
105 | /**
106 | * For the attach
107 | */
108 | Given(/I open the url "(.*)"/, url => {
109 | browser.url(url)
110 | multipleCucumberHtmlReporter.attach('just a string');
111 | multipleCucumberHtmlReporter.attach({ 'json-string': true }, 'application/json');
112 | multipleCucumberHtmlReporter.attach(browser.saveScreenshot(), 'image/png');
113 | multipleCucumberHtmlReporter.attach(browser.screenshot(), 'image/png');
114 | });
115 |
116 | /**
117 | * For the app
118 | */
119 | Given(/I open the app/, () => {});
120 | Given(/the home screen is shown/, () => {
121 | expect($('~Home').waitForVisible(20000)).to.equal(true);
122 | });
123 |
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | If you have a new feature or want to fix a bug please create a PR.
4 |
5 | > **Make sure you have Chrome and Firefox on your machine to let the tests work**
6 |
7 | ## Development
8 | Open a second terminal and run `npm run watch`, it will automatically watch all the files
9 |
10 | ## Testing
11 | Currently there are no unit tests. There are some integration tests to see if a report can be generated, run them by running `npm run t`.
12 |
--------------------------------------------------------------------------------
/docs/assets/before-after.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wswebcreation/wdio-multiple-cucumber-html-reporter/32c9823f5879120084c4c21775e13588ecbdc24e/docs/assets/before-after.jpg
--------------------------------------------------------------------------------
/docs/assets/custom-data.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wswebcreation/wdio-multiple-cucumber-html-reporter/32c9823f5879120084c4c21775e13588ecbdc24e/docs/assets/custom-data.jpg
--------------------------------------------------------------------------------
/docs/assets/feature-overview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wswebcreation/wdio-multiple-cucumber-html-reporter/32c9823f5879120084c4c21775e13588ecbdc24e/docs/assets/feature-overview.jpg
--------------------------------------------------------------------------------
/docs/assets/features-overview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wswebcreation/wdio-multiple-cucumber-html-reporter/32c9823f5879120084c4c21775e13588ecbdc24e/docs/assets/features-overview.jpg
--------------------------------------------------------------------------------
/docs/assets/given-datatables.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wswebcreation/wdio-multiple-cucumber-html-reporter/32c9823f5879120084c4c21775e13588ecbdc24e/docs/assets/given-datatables.jpg
--------------------------------------------------------------------------------
/docs/assets/report-overview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wswebcreation/wdio-multiple-cucumber-html-reporter/32c9823f5879120084c4c21775e13588ecbdc24e/docs/assets/report-overview.jpg
--------------------------------------------------------------------------------
/lib/generateJson.js:
--------------------------------------------------------------------------------
1 | import { ensureDirSync, outputJsonSync } from 'fs-extra';
2 | import { resolve } from 'path';
3 |
4 | /**
5 | * Generate the JSON file with all the Cucumber data in it
6 | *
7 | * @param {string} folder the location where the JSON file needs to be saved
8 | * @param {object} data the JSON data
9 | */
10 | export function generateJson(folder, data) {
11 | const jsonFolder = resolve(process.cwd(), folder);
12 | ensureDirSync(jsonFolder);
13 | const jsonFile = resolve(jsonFolder, `s${(new Date).getTime()}.json`);
14 | const json = [];
15 |
16 | Object.entries(data).forEach(([key, val]) => {
17 | Object.entries(val._elements).forEach(([key1, val1]) => {
18 | val.elements.push(val1);
19 | });
20 | // Delete temporary data
21 | delete val._elements;
22 | delete val._attachment;
23 | json.push(val)
24 | });
25 |
26 | outputJsonSync(jsonFile, json)
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/lib/reporter.js:
--------------------------------------------------------------------------------
1 | import events from 'events';
2 | import process from 'process';
3 | import { removeSync } from 'fs-extra';
4 | import { generateJson } from './generateJson';
5 | import { generate } from 'multiple-cucumber-html-reporter';
6 |
7 | const FEATURE = 'Feature';
8 | const SCENARIO = 'Scenario';
9 | const NOT_KNOWN = 'not known';
10 | const AFTER = 'After';
11 | const BEFORE = 'Before';
12 | const TEXT_PLAIN = 'text/plain';
13 |
14 | class MultipleCucumberHtmlReporter extends events.EventEmitter {
15 | constructor(baseReporter, config, options = {}) {
16 | super();
17 |
18 | if (!options.htmlReporter) {
19 | throw new Error('Options need to be provided.');
20 | }
21 |
22 | if (!options.htmlReporter.jsonFolder) {
23 | throw new Error('A path which holds the JSON files should be provided.');
24 | }
25 |
26 | if (!options.htmlReporter.reportFolder) {
27 | throw new Error('An output path for the reports should be defined, no path was provided.');
28 | }
29 |
30 | this.options = options.htmlReporter;
31 | // this.baseReporter = baseReporter;
32 | this.instanceData = {};
33 | this.results = {};
34 | this.scenarioName = null;
35 |
36 | // this.on('hook:start', ::this.hookStart);
37 | // this.on('hook:end', ::this.hookEnd);
38 |
39 | this.on('start', ::this.onStart);
40 |
41 | // Test framework events
42 | this.on('suite:start', ::this.suiteStart);
43 | // this.on('suite:end', ::this.suiteEnd);
44 | this.on('test:start', ::this.testStart);
45 | this.on('test:pass', ::this.testPass);
46 | this.on('test:fail', ::this.testFail);
47 | this.on('test:pending', ::this.testPending);
48 |
49 | // Runner events (webdriver)
50 | // this.on('start', ::this.start);
51 | // this.on('runner:command', ::this.runnerCommand);
52 | this.on('runner:result', ::this.runnerResult);
53 | this.on('end', ::this.onEnd);
54 |
55 | // Multiple Cucumber HTML events
56 | this.on('mchr:attachment', ::this.mchrAttachment)
57 | }
58 |
59 | onStart() {
60 | if (this.options.removeFolders) {
61 | removeSync(this.options.jsonFolder);
62 | removeSync(this.options.reportFolder)
63 | }
64 | }
65 |
66 | // hookStart(payload) {}
67 | // hookEnd(payload) {}
68 |
69 | suiteStart(suite) {
70 | const cid = suite.cid;
71 | if (!this.results[ cid ]) {
72 | this.results[ cid ] = this.getFeatureDataObject(suite);
73 | }
74 | this.scenarioName = suite.title;
75 | }
76 |
77 | // suiteEnd(suite) {}
78 |
79 | testStart(test) {
80 | if (!this.results[ test.cid ]._elements[ test.parent ]) {
81 | this.results[ test.cid ]._elements[ test.parent ] = this.getScenarioDataObject(test);
82 | }
83 | }
84 |
85 | testPass(test) {
86 | this.results[ test.cid ]._elements[ test.parent ].steps.push(this.getStepDataObject(test, 'passed'));
87 | }
88 |
89 | testFail(test) {
90 | this.results[ test.cid ]._elements[ test.parent ].steps.push(this.getStepDataObject(test, 'failed'));
91 | }
92 |
93 | testPending(test) {
94 | this.results[ test.cid ]._elements[ test.parent ].steps.push(this.getStepDataObject(test, 'pending'));
95 | }
96 |
97 | /**
98 | * Runner
99 | */
100 | // runnerCommand(command) {}
101 |
102 | runnerResult(result) {
103 | // Save browserdata so it can be used later
104 | const cid = result.cid;
105 | if (!this.instanceData[ cid ]) {
106 | this.instanceData[ cid ] = this.determineMetadata(result);
107 | }
108 | }
109 |
110 | onEnd(payload) {
111 | const jsonFolder = this.options.jsonFolder;
112 |
113 | // Generate the jsons
114 | generateJson(jsonFolder, this.results);
115 |
116 | // generate the report
117 | generate({
118 | // Required
119 | jsonDir: jsonFolder,
120 | reportPath: this.options.reportFolder,
121 | // Optional
122 | ...(this.options.customData ? { customData: this.options.customData } : {}),
123 | ...(this.options.customStyle ? { customStyle: this.options.customStyle } : {}),
124 | disableLog: this.options.disableLog || false,
125 | displayDuration: this.options.displayDuration || false,
126 | durationInMS: this.options.durationInMS || false,
127 | openReportInBrowser: this.options.openReportInBrowser || false,
128 | ...(this.options.overrideStyle ? { overrideStyle: this.options.overrideStyle } : {}),
129 | ...(this.options.pageFooter ? { pageFooter: this.options.pageFooter } : {}),
130 | ...(this.options.pageTitle ? { pageTitle: this.options.pageTitle } : {}),
131 | ...(this.options.reportName ? { reportName: this.options.reportName } : {}),
132 | saveCollectedJSON: this.options.saveCollectedJSON || false,
133 | });
134 | }
135 |
136 | /**
137 | * All functions
138 | */
139 |
140 | /**
141 | * Get the feature data object
142 | *
143 | * @param {object} featureData
144 | *
145 | * @returns {
146 | * {
147 | * keyword: string,
148 | * line: number,
149 | * name: string,
150 | * tags: string,
151 | * uri: string,
152 | * _elements: {object},
153 | * elements: Array,
154 | * id: string,
155 | * _attachment: Array
156 | * }
157 | * }
158 | */
159 | getFeatureDataObject(featureData) {
160 | return {
161 | keyword: FEATURE,
162 | description: this.escapeHTML(featureData.description),
163 | line: parseInt(featureData.uid.substring(featureData.title.length, featureData.uid.length)),
164 | name: this.escapeHTML(featureData.title),
165 | tags: featureData.tags,
166 | uri: featureData.specs[ 0 ],
167 | _elements: {}, // Temporary. All data will be copied to the `elements` when done
168 | elements: [],
169 | id: featureData.title.replace(/ /g, '-').toLowerCase(),
170 | _attachment: [], // Temporary, attachments will be added here and removed per step...
171 | ...this.instanceData[ featureData.cid ],
172 | }
173 | }
174 |
175 | /**
176 | * Get the scenario data object
177 | *
178 | * @param {object} scenarioData This is the testdata of the current scenario
179 | *
180 | * @returns {
181 | * {
182 | * keyword: string,
183 | * line: number,
184 | * name: string,
185 | * tags: string,
186 | * id: string,
187 | * steps: Array
188 | * }
189 | * }
190 | */
191 | getScenarioDataObject(scenarioData) {
192 | return {
193 | keyword: SCENARIO,
194 | description: this.escapeHTML(scenarioData.description),
195 | line: parseInt(scenarioData.parent.substring(this.scenarioName.length, scenarioData.parent.length)),
196 | name: this.escapeHTML(this.scenarioName),
197 | tags: scenarioData.tags,
198 | id: `${ this.results[ scenarioData.cid ].id };${ this.scenarioName.replace(/ /g, '-').toLowerCase() }`,
199 | steps: []
200 | }
201 | }
202 |
203 | /**
204 | * Get the step data object
205 | *
206 | * @param {object} stepData This is the testdata of the step
207 | * @param status
208 | * @returns {{arguments: Array, keyword: string, name: *, result: {status: *, duration: *}, line: number, match: {location: string}}}
209 | */
210 | getStepDataObject(stepData, status) {
211 | const stepResult = {
212 | arguments: stepData.argument || [],
213 | // keyword: ' ',
214 | keyword: stepData.keyword || ' ',
215 | name: this.escapeHTML(stepData.title),
216 | result: {
217 | status: status,
218 | duration: (stepData.duration * 1000000),
219 | ...this.addFailedMessage(stepData, status)
220 | },
221 | line: parseInt(stepData.uid.substring(stepData.title.length, stepData.uid.length)),
222 | // Add the screenshot embeddings if there are screenshots
223 | ...this.defineEmbeddings(stepData.cid),
224 | match: {
225 | location: 'can not be determined with webdriver.io'
226 | }
227 | }
228 | ;
229 |
230 | return stepResult;
231 | }
232 |
233 | /**
234 | * Define the embeddings
235 | *
236 | * @param {string} cid The current instance id
237 | *
238 | * @returns {{
239 | * embeddings: *
240 | * } || {}
241 | * }
242 | */
243 | defineEmbeddings(cid) {
244 | const embeddings = this.results[ cid ]._attachment.length > 0
245 | ? { embeddings: [ ...this.results[ cid ]._attachment ] }
246 | : {};
247 |
248 | // Empty the attachments because there is no data anymore, step has finished
249 | this.results[ cid ]._attachment = [];
250 |
251 | return embeddings;
252 | }
253 |
254 | /**
255 | * Add a failed message
256 | *
257 | * @param {object} testObject
258 | * @param {string} status
259 | *
260 | * @return {object}
261 | */
262 | addFailedMessage(testObject, status) {
263 | if (status === 'failed') {
264 | return {
265 | error_message: testObject.err.stack
266 | }
267 | }
268 |
269 | return {}
270 | }
271 |
272 | /**
273 | * Determine the metadata that needs to be added
274 | *
275 | * @TODO: Need to look at the implementation, is not that nice
276 | *
277 | * @param {object} data instance data
278 | *
279 | * @returns {
280 | * {
281 | * metadata: {
282 | * app: {
283 | * name: string,
284 | * version: string
285 | * },
286 | * browser: {
287 | * name: string,
288 | * version: string
289 | * },
290 | * device: string,
291 | * platform: {
292 | * name: string,
293 | * version: string
294 | * }
295 | * }
296 | * }
297 | * }
298 | */
299 | determineMetadata(data) {
300 | let app, browser;
301 | const desiredCapabilities = data.requestData.desiredCapabilities;
302 | const metadata = desiredCapabilities.metadata || {};
303 | const bodyValue = data.body.value || {};
304 |
305 | // When an app is used to test
306 | if (bodyValue.app || bodyValue.testobject_app_id || metadata.app) {
307 | const metaAppName = (metadata.app && metadata.app.name) ? metadata.app.name : 'No metadata.app.name available';
308 | const metaAppVersion = (metadata.app && metadata.app.version) ? metadata.app.version : 'No metadata.app.version available';
309 | const appPath = (bodyValue.app || bodyValue.testobject_app_id || metaAppName);
310 | const appName = appPath.substring(appPath.replace('\\', '/').lastIndexOf('/')).replace('/', '');
311 |
312 | // Provide the app name and version
313 | app = {
314 | app: {
315 | name: appName,
316 | version: metaAppVersion
317 | }
318 | };
319 | // Provide the devicename if there
320 | metadata.device = desiredCapabilities.deviceName
321 | || bodyValue.deviceName
322 | || metadata.device
323 | || null;
324 | metadata.platform = {
325 | name: desiredCapabilities.platformName
326 | || bodyValue.platformName
327 | || (metadata.platform && metadata.platform.name)
328 | || 'No metadata.platform.name available',
329 | version: desiredCapabilities.platformVersion
330 | || bodyValue.platformVersion
331 | || (metadata.platform && metadata.platform.version)
332 | || 'No metadata.platform.version available',
333 | }
334 | } else {
335 | const browserName = bodyValue.browserName
336 | || ((metadata && metadata.browser && metadata.browser.name) ? metadata.browser.name : 'No metadata.browser.name available')
337 | const browserVersion = bodyValue.version
338 | || bodyValue.browserVersion
339 | || ((metadata && metadata.browser && metadata.browser.version) ? metadata.browser.version : 'No metadata.browser.version available')
340 |
341 | browser = {
342 | browser: {
343 | name: browserName,
344 | version: browserVersion,
345 | }
346 | };
347 | }
348 |
349 | const device = (metadata && metadata.device) ? metadata.device : NOT_KNOWN;
350 | const platform = {
351 | name: (metadata && metadata.platform && metadata.platform.name) ? metadata.platform.name : NOT_KNOWN,
352 | version: (metadata && metadata.platform && metadata.platform.version) ? metadata.platform.version : NOT_KNOWN
353 | };
354 |
355 | return {
356 | metadata: {
357 | ...(app || browser),
358 | device,
359 | platform,
360 | }
361 | }
362 | }
363 |
364 | /**
365 | * Escape html in strings
366 | *
367 | * @param {string} string
368 | * @return {string}
369 | */
370 | escapeHTML(string) {
371 | return !string ? string : string.replace(/&/g, '&').replace(//g, '>').replace(/\"/g, '"').replace(/\'/g, ''');
372 | }
373 |
374 | /**
375 | * Attach data to the report
376 | *
377 | * @param {string|object} data
378 | * @param {string} type Default is `text/plain`, otherwise what people deliver as a MIME type, like `application/json`, `image/png`
379 | * @param {string|undefined} hookType This will tell if the attach needs to take place for a before or after hook
380 | */
381 | static attach(data, type = TEXT_PLAIN, hookType = undefined) {
382 | process.send({ event: 'mchr:attachment', ...{ data, type, hookType } })
383 | }
384 |
385 | /**
386 | * Add the attachment to the result
387 | *
388 | * @param {string} cid
389 | * @param {string|object} data
390 | * @param {string} type
391 | * @param {string|undefined} hookType This will tell if the attach needs to take place for a before or after hook
392 | */
393 | mchrAttachment({ cid, data, type, hookType }) {
394 | let hook;
395 | // It could be that people don't provide the type, but just the hookType, so check that here
396 | type = this.validateHookType(type);
397 | if (type === BEFORE || type === AFTER) {
398 | hook = type;
399 | // Set the type to the default if the type has been used to set the hookType
400 | type = TEXT_PLAIN;
401 | }
402 |
403 | if (hookType) {
404 | hook = this.validateHookType(hookType);
405 | }
406 |
407 | if (data.value) {
408 | data = data.value;
409 | } else if (data.data) {
410 | data = Buffer.from(data.data).toString('base64');
411 | }
412 |
413 | // This will push all data that has been attached to the global, this will be parsed and cleaned for
414 | // each executed step (test) in the `passed||failed||pending`-state
415 | this.results[ cid ]._attachment.push({
416 | data: data,
417 | mime_type: type,
418 | });
419 |
420 | // If a hook is used, add the hook data
421 | if (hook) {
422 | this.addHookData(cid, hook);
423 | }
424 | }
425 |
426 | /**
427 | * Validate if the hook type that is used is a valid hook or not
428 | *
429 | * @param {string} string
430 | *
431 | * @return {string}
432 | */
433 | validateHookType(string) {
434 | if (string && (this.capitalizeFirstLetter(string) === AFTER || this.capitalizeFirstLetter(string) === BEFORE)) {
435 | return this.capitalizeFirstLetter(string) === AFTER ? 'After' : 'Before';
436 | }
437 |
438 | return string;
439 | }
440 |
441 | /**
442 | * Capitalize the first letter of the string
443 | *
444 | * @param {string} string
445 | *
446 | * @return {string}
447 | */
448 | capitalizeFirstLetter(string) {
449 | return string.charAt(0).toUpperCase() + string.toLowerCase().slice(1);
450 | }
451 |
452 | /**
453 | * Determine the hook data and determine if a before or after hook step needs to be made
454 | *
455 | * @param {number} cid
456 | * @param {string} hookType
457 | */
458 | addHookData(cid, hookType) {
459 | const scenarios = this.results[ cid ]._elements;
460 | const currentScenario = Object.keys(scenarios)[ Object.keys(scenarios).length - 1 ];
461 | const hookStepData = {
462 | keyword: hookType,
463 | title: '',
464 | uid: '',
465 | duration: 0,
466 | cid: cid,
467 | }
468 | const stepsArray = scenarios[ currentScenario ].steps;
469 |
470 | if (hookType === AFTER) {
471 | this.addAfterHookData(cid, currentScenario, hookStepData, stepsArray)
472 | } else {
473 | this.addBeforeHookData(cid, currentScenario, hookStepData, stepsArray)
474 | }
475 | }
476 |
477 | /**
478 | * Add after hook data, also determine if there is already an after hook, if so, add it to the current one
479 | *
480 | * @param {number} cid
481 | * @param {string} currentScenario
482 | * @param {string} hookStepData
483 | * @param {object} stepsArray
484 | */
485 | addAfterHookData(cid, currentScenario, hookStepData, stepsArray) {
486 | if (stepsArray[ stepsArray.length - 1 ].keyword === AFTER) {
487 | stepsArray[ stepsArray.length - 1 ].embeddings.push(this.results[ cid ]._attachment[ 0 ])
488 | this.results[ cid ]._attachment = [];
489 | } else {
490 | const hookData = this.getStepDataObject(hookStepData, 'passed');
491 | this.results[ cid ]._elements[ currentScenario ].steps.push(hookData);
492 | }
493 | }
494 |
495 | /**
496 | * Add before hook data, also determine if there is already a before hook, if so, add it to the current one
497 | *
498 | * @param {number} cid
499 | * @param {string} currentScenario
500 | * @param {string} hookStepData
501 | * @param {object} stepsArray
502 | */
503 | addBeforeHookData(cid, currentScenario, hookStepData, stepsArray) {
504 | if (stepsArray[ 0 ] && stepsArray[ 0 ].keyword === BEFORE) {
505 | stepsArray[ stepsArray.length - 1 ].embeddings.push(this.results[ cid ]._attachment[ 0 ])
506 | this.results[ cid ]._attachment = [];
507 | } else {
508 | const hookData = this.getStepDataObject(hookStepData, 'passed');
509 | this.results[ cid ]._elements[ currentScenario ].steps.unshift(hookData);
510 | }
511 | }
512 | }
513 |
514 | MultipleCucumberHtmlReporter.reporterName = 'multiple-cucumber-html-reporter';
515 | export default MultipleCucumberHtmlReporter;
516 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wdio-multiple-cucumber-html-reporter",
3 | "version": "1.1.1",
4 | "description": "A WebdriverIO plugin. Report results in multiple-cucumber-html-reporter format.",
5 | "main": "build/reporter.js",
6 | "scripts": {
7 | "clean": "rimraf coverage build .tmp",
8 | "compile": "babel lib/ -d build/",
9 | "release": "np",
10 | "precompile": "npm run clean",
11 | "prerelease": "npm run compile",
12 | "prepublish": "npm run compile",
13 | "test.browsers": "node_modules/.bin/wdio __tests__/config/wdio.browsers.conf.js",
14 | "test.app.android": "node_modules/.bin/wdio __tests__/config/wdio.app.android.conf.js",
15 | "test.app.ios": "node_modules/.bin/wdio __tests__/config/wdio.app.ios.conf.js",
16 | "watch": "npm run compile -- --watch"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/wswebcreation/wdio-multiple-cucumber-html-reporter.git"
21 | },
22 | "keywords": [
23 | "multiple-cucumber-html-reporter",
24 | "cucumberjs",
25 | "reporter",
26 | "report",
27 | "webdriverio",
28 | "wdio",
29 | "wdio-plugin",
30 | "wdio-reporter"
31 | ],
32 | "author": "Wim Selles ",
33 | "license": "MIT",
34 | "bugs": {
35 | "url": "https://github.com/wswebcreation/wdio-multiple-cucumber-html-reporter/issues"
36 | },
37 | "homepage": "https://github.com/wswebcreation/wdio-multiple-cucumber-html-reporter#readme",
38 | "dependencies": {
39 | "babel-runtime": "^6.26.0",
40 | "fs-extra": "^6.0.1",
41 | "multiple-cucumber-html-reporter": "^1.11.6"
42 | },
43 | "peerDependencies": {
44 | "wdio-cucumber-framework": "^2.2.0"
45 | },
46 | "devDependencies": {
47 | "babel-cli": "^6.26.0",
48 | "babel-core": "^6.26.3",
49 | "babel-eslint": "^8.2.3",
50 | "babel-plugin-add-module-exports": "^0.2.1",
51 | "babel-plugin-transform-function-bind": "^6.22.0",
52 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
53 | "babel-preset-env": "^1.7.0",
54 | "chai": "^4.2.0",
55 | "eslint": "^4.19.1",
56 | "np": "^5.0.1",
57 | "rimraf": "^2.6.3",
58 | "wdio-cucumber-framework": "^2.2.8",
59 | "wdio-selenium-standalone-service": "0.0.10",
60 | "wdio-spec-reporter": "^0.1.4",
61 | "webdriverio": "^4.14.4",
62 | "yargs": "^11.0.0"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------