Skip to content

Commit

Permalink
Merge pull request #52 from yachr/develop
Browse files Browse the repository at this point in the history
Release: 
markdown as html
Filtering by tag
  • Loading branch information
DesHorsley committed Jun 16, 2020
2 parents 0b46015 + 53e9a61 commit dd3e0d9
Show file tree
Hide file tree
Showing 10 changed files with 324 additions and 44 deletions.
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"workbench.colorCustomizations": {
"activityBar.background": "#52326f",
"activityBar.activeBackground": "#52326f",
"activityBar.activeBorder": "#000000",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
Expand All @@ -12,7 +13,9 @@
"titleBar.inactiveForeground": "#e7e7e799",
"statusBar.background": "#38224c",
"statusBarItem.hoverBackground": "#52326f",
"statusBar.foreground": "#e7e7e7"
"statusBar.foreground": "#e7e7e7",
"statusBar.border": "#38224c",
"titleBar.border": "#38224c"
},
"python.linting.pylintEnabled": true,
"cucumberautocomplete.steps": [
Expand Down
20 changes: 15 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "yachr",
"version": "0.1.8",
"version": "0.1.10",
"description": "Yet another cucumber html reporter",
"scripts": {
"prebuild": "shx rm -rf dist",
Expand All @@ -11,7 +11,7 @@
"lint": "tslint -c tslint.json -p tsconfig.json 'src/**/*.ts'",
"cover": "NODE_ENV=test nyc --report lcovonly _mocha --config .mocharc.json src/**/*.spec.ts",
"e2eWithBuild": "npm run build && npm run e2e",
"e2e": "tsc -p e2e/tsconfig.json && cucumber-js ./e2e/features --no-strict",
"e2e": "tsc -p e2e/tsconfig.json && cucumber-js ./e2e/features --no-strict && node ./runReport.js",
"poste2e": "shx rm -rf e2e/step_definitions/**/*.js e2e/reportOutput/**/*.html",
"coveralls": "npm run cover && shx cat ./coverage/lcov.info | coveralls"
},
Expand Down Expand Up @@ -62,7 +62,7 @@
"cucumber": "^5.1.0",
"mocha": "^6.2.0",
"mocha-lcov-reporter": "^1.3.0",
"yachr": "^0.1.5",
"yachr": "^0.1.10",
"nyc": "^15.0.0",
"shx": "^0.3.2",
"source-map-support": "^0.5.11",
Expand All @@ -71,8 +71,10 @@
"typescript": "^3.3.3333"
},
"dependencies": {
"@types/marked": "^0.7.4",
"chart.js": "^2.8.0",
"handlebars": "^4.7.3"
"handlebars": "^4.7.3",
"marked": "^1.1.0"
},
"files": [
"dist/"
Expand Down
7 changes: 7 additions & 0 deletions src/models/reportOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ export interface IReportOptions {
/** Path to Cucumber file in JSON format */
jsonFile: string;

/**
* Comma separated tags
* @example
* tags: '@includeThis, @includeThis, ~@ignoreThis'
*/
tags?: string;

/**
* Path to the custom template to use
* @description A custom html template can be supplied. Templates use https://handlebarsjs.com/
Expand Down
135 changes: 129 additions & 6 deletions src/reporter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { expect } from 'chai';
import * as fs from 'fs';
import { } from 'mocha';

import { IReportOptions } from './models/reportOptions';
import { Reporter } from './reporter';
import { FeatureSummary } from './models/aggregator/featureSummary';
import { ScenarioSummary } from './models/aggregator/scenarioSummary';
import { ICucumberFeatureSuite } from './models/reporter/cucumberFeatureSuite';
import { IReportOptions } from './models/reportOptions';
import { Reporter } from './reporter';

describe('reporter', () => {
let reporter: Reporter;
Expand Down Expand Up @@ -55,7 +55,7 @@ describe('reporter', () => {
});

it('should update options with required defaults if the user does not supply them', () => {
const options = <IReportOptions>{};
const options = <IReportOptions> {};

const actual = reporter.populateDefaultOptionsIfMissing(options);

Expand All @@ -64,7 +64,7 @@ describe('reporter', () => {
});

it('populateDefaultOptionsIfMissing should not overwrite existing values', () => {
const options = <IReportOptions>{
const options = <IReportOptions> {
htmlTemplate: 'templatePath',
jsonFile: 'somepath'
};
Expand Down Expand Up @@ -166,11 +166,134 @@ describe('reporter', () => {
const html = fs.readFileSync(options.output, 'utf8');

// tslint:disable-next-line: max-line-length
const expectedText = /<span>Ability: Login<\/span>([.\n\s]*)<span class="feature-rollup-summary">([.\n\s]*)<i class="material-icons" title="Failing">clear<\/i>1/;
const expectedText = /<span>Ability: Login<\/span>([.\n\s]*)<span class="feature-rollup-summary">([.\n\s]*)<span class="summary-counts-failing"><i class="material-icons" title="Failing">clear<\/i>1/;

expect(html.search(expectedText)).to.greaterThan(0);
// Clean up test
// Comment this out if you want to view the generated html
fs.unlinkSync(options.output);
});

it('should filter to includedTags', () => {
const rawFeatureSuite: ICucumberFeatureSuite = {
features: [
{
description: 'Sample Feature Description',
keyword: 'Ability',
name: 'Login',
line: 1,
id: 'login',
tags: [{
line: 1,
name: '@includeMe',
}],
uri: 'e2e\\src\\features\\abilities\\user\\login.feature',
elements: [
{
id: 'login;login-via-login-page',
keyword: 'Scenario',
line: 11,
name: 'Login via login page',
tags: [{
line: 1,
name: '@includeMe',
}],
type: 'scenario',
steps: [
{
keyword: 'Before',
hidden: true,
match: {
location: 'e2e\\src\\steps\\searchForUser.steps.ts:10'
},
result: {
status: 'passed',
duration: 1
}
}
]
},
{
id: 'login;login-via-login-page',
keyword: 'Scenario',
line: 11,
name: 'Login via login page',
tags: [{
line: 1,
name: '@dontIncludeMe',
}],
type: 'scenario',
steps: [
{
keyword: 'Before',
hidden: true,
match: {
location: 'e2e\\src\\steps\\searchForUser.steps.ts:10'
},
result: {
status: 'passed',
duration: 1
}
}
]
}
]
},
{
description: 'Sample Not going to be included',
keyword: 'Ability',
name: 'Login',
line: 1,
id: 'login',
tags: [
{
line: 1,
name: '@dontIncludeMe',
}
],
uri: 'e2e\\src\\features\\abilities\\user\\login.feature',
elements: [
{
id: 'login;login-via-login-page',
keyword: 'Scenario',
line: 11,
name: 'Login via login page',
tags: [],
type: 'scenario',
steps: [
{
keyword: 'Before',
hidden: true,
match: {
location: 'e2e\\src\\steps\\searchForUser.steps.ts:10'
},
result: {
status: 'passed',
duration: 1
}
}
]
}
]
}
]
};

// Should filter to supplied tags
let filteredResults = reporter.filterResults(rawFeatureSuite, { tags: '@includeMe' } as IReportOptions);
expect(filteredResults.features.length).to.eq(1, 'Should only include features with matching tag');

// Should allow only ecluded tags to be supplied
filteredResults = reporter.filterResults(rawFeatureSuite, { tags: '~@whatever' } as IReportOptions);
expect(filteredResults.features.length).to.eq(2, 'Accept tags that only include exclusions');

// Should not filter if no tags supplied
filteredResults = reporter.filterResults(rawFeatureSuite, { } as IReportOptions);
expect(filteredResults.features.length).to.eq(2, 'Should include all features');

// Should strip out scenarios with excluded tags
filteredResults = reporter.filterResults(rawFeatureSuite, { tags: '~@dontIncludeMe' } as IReportOptions);
expect(rawFeatureSuite.features[0].elements.length).to.eq(2, 'Should start with two scenarios');
expect(filteredResults.features[0].elements.length).to.eq(1, 'Should strip out scenarios with excluded tags');
});
});
52 changes: 45 additions & 7 deletions src/reporter.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as fs from 'fs';
import * as Handlebars from 'handlebars';
import * as marked from 'marked';

import { FeatureSuiteSummary } from './models/aggregator/featureSuiteSummary';
import { FeatureSummary } from './models/aggregator/featureSummary';
import { ScenarioSuiteSummary } from './models/aggregator/scenarioSuiteSummary';
import { ScenarioSummary } from './models/aggregator/scenarioSummary';
import { IHtmlModel } from './models/htmlModel';
import { ICucumberFeature } from './models/reporter/cucumberFeature';
Expand All @@ -28,11 +27,14 @@ export class Reporter {
public generate(options: IReportOptions): void {
options = this.populateDefaultOptionsIfMissing(options);

const results = this.parseJsonFile(options.jsonFile);
const rawResults = this.parseJsonFile(options.jsonFile);

// if a filter has been passed in, filter out the results
const results = this.filterResults(rawResults, options);

const aggregator = new ReportAggregator();

const data = <IHtmlModel>{
const data = <IHtmlModel> {
cucumberReportSummary: aggregator.getSummaryForSuite(results),
cucumberResult: results,
generateTime: (new Date()).toLocaleString()
Expand Down Expand Up @@ -67,7 +69,7 @@ export class Reporter {
// Gross work around because the template engine seems to reject
// the work undefined as a property.
Handlebars.registerHelper('countOf', (obj, property: 'string'): number =>
(<any>obj)[property] as number
(obj)[property] as number
);

Handlebars.registerPartial({
Expand All @@ -85,6 +87,10 @@ export class Reporter {
Handlebars.registerHelper('getScenarioCss', (scenarioSummary: ScenarioSummary) =>
this.getScenarioCss(scenarioSummary));

Handlebars.registerHelper('markdown2Html', (markdown: string) =>
marked(markdown && markdown.trim() || '')
);

Handlebars.registerHelper('getStepCss', (step: IStep) => {

switch (step.result.status) {
Expand Down Expand Up @@ -130,13 +136,45 @@ export class Reporter {
}
}

/**
* Filters the features and scenario's based on tags
*/
public filterResults(featureSuiteOrig: ICucumberFeatureSuite, options: IReportOptions): ICucumberFeatureSuite {
// Don't modify the original suite
const featureSuite = JSON.parse(JSON.stringify(featureSuiteOrig)) as ICucumberFeatureSuite;

if (options.tags && options.tags.length) {
const tags: string[] = (options.tags || '').split(',').map(t => t.trim());
const includeTags = tags.filter(t => t.startsWith('@'));
const excludeTags = tags.filter(t => t.startsWith('~')).map(t => t.substring(1)); // Drop the tilde

let filteredFeatures = featureSuite.features.filter(f => {
const x = (!includeTags.length || f.tags.some(t => includeTags.includes(t.name))) ;
const y = (!excludeTags.length || !f.tags.some(t => excludeTags.includes(t.name)));
return x && y;
});

filteredFeatures.forEach(f => f.elements = f.elements.filter(el => {
const x = (!includeTags.length || el.tags.some(t => includeTags.includes(t.name)));
const y = (!excludeTags.length || !el.tags.some(t => excludeTags.includes(t.name)));
return x && y;
}));

filteredFeatures = filteredFeatures.filter(f => f.elements.length);

return { features: filteredFeatures };
}

return featureSuite;
}

/**
* Parses a JSON String and returns a strongly typed data model
* reflecting the Cucumber Test Report data structure
* @param results An array of Cucumber Features from the Test Report
*/
public parseJsonString(results: string): ICucumberFeatureSuite {
const features: ICucumberFeature[] = <ICucumberFeature[]>JSON.parse(results);
const features: ICucumberFeature[] = <ICucumberFeature[]> JSON.parse(results);
return { features };
}

Expand All @@ -145,7 +183,7 @@ export class Reporter {
* @param options The options as passed in by the user
*/
public populateDefaultOptionsIfMissing(options: IReportOptions): IReportOptions {
const defaultOptions = <IReportOptions>{
const defaultOptions = <IReportOptions> {
featureTemplate: __dirname + '/templates/feature.html',
htmlTemplate: __dirname + '/templates/standard.html',
scenarioTemplate: __dirname + '/templates/scenario.html'
Expand Down

0 comments on commit dd3e0d9

Please sign in to comment.