Skip to content

Commit

Permalink
add support for nested suites (fixes #38, via #43)
Browse files Browse the repository at this point in the history
  • Loading branch information
just-boris authored and baev committed Aug 26, 2019
1 parent 4b8874b commit ab47cdd
Show file tree
Hide file tree
Showing 13 changed files with 150 additions and 48 deletions.
62 changes: 34 additions & 28 deletions packages/allure-mocha/src/AllureReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
ContentType,
LabelName,
Stage,
Status
Status,
StatusDetails
} from "allure-js-commons";
import { createHash } from "crypto";
import { MochaAllureInterface } from "./MochaAllureInterface";
Expand Down Expand Up @@ -45,11 +46,9 @@ export class AllureReporter {
}

public startSuite(suiteName: string) {
if (suiteName) {
const scope = this.currentSuite || this.runtime;
const suite = scope.startGroup(suiteName);
this.pushSuite(suite);
}
const scope = this.currentSuite || this.runtime;
const suite = scope.startGroup(suiteName || "Global");
this.pushSuite(suite);
}

public endSuite() {
Expand All @@ -62,39 +61,50 @@ export class AllureReporter {
}
}

public startCase(suiteName: string, testName: string) {
public startCase(test: Mocha.Test) {
if (this.currentSuite === null) {
throw new Error("No active suite");
}

this.currentTest = this.currentSuite.startTest(testName);
this.currentTest.fullName = testName;
this.currentTest = this.currentSuite.startTest(test.title);
this.currentTest.fullName = test.name;
this.currentTest.historyId = createHash("md5")
.update(JSON.stringify({ suite: suiteName, test: testName }))
.update(test.fullTitle())
.digest("hex");
this.currentTest.stage = Stage.RUNNING;
this.currentTest.addLabel(LabelName.SUITE, this.currentSuite.name);

if (test.parent) {
const [parentSuite, suite, ...subSuites] = test.parent.titlePath();
if (parentSuite) {
this.currentTest.addLabel(LabelName.PARENT_SUITE, parentSuite);
}
if (suite) {
this.currentTest.addLabel(LabelName.SUITE, suite);
}
if (subSuites.length > 0) {
this.currentTest.addLabel(LabelName.SUB_SUITE, subSuites.join(" > "));
}
}
}

public passTestCase() {
this.endTest();
this.endTest(Status.PASSED);
}

public pendingTestCase(test: Mocha.Test) {
if (this.currentTest === null && this.currentSuite !== null) {
this.currentTest = this.currentSuite.startTest(test.title);
this.currentTest.statusDetails = { message: "Test ignored" };
if (this.currentTest === null) {
this.startCase(test);
}

this.endTest(undefined, Status.SKIPPED);
this.endTest(Status.SKIPPED, { message: "Test ignored" });
}

public failTestCase(test: Mocha.Test, error: Error) {
if (this.currentTest === null && this.currentSuite !== null) {
this.currentTest = this.currentSuite.startTest(test.fullTitle());
if (this.currentTest === null) {
this.startCase(test);
}
const status = error.name === "AssertionError" ? Status.FAILED : Status.BROKEN;

this.endTest(error);
this.endTest(status, { message: error.message, trace: error.stack });
}

public writeAttachment(content: Buffer | string, type: ContentType): string {
Expand All @@ -117,19 +127,15 @@ export class AllureReporter {
this.suites.pop();
}

private endTest(error?: Error, status?: Status): void {
private endTest(status: Status, details?: StatusDetails): void {
if (this.currentTest === null) {
throw new Error("endTest while no test is running");
}

if (error) {
this.currentTest.statusDetails = { message: error.message, trace: error.stack };
if (details) {
this.currentTest.statusDetails = details;
}

let errorStatus = Status.PASSED;
if (error) errorStatus = error.name === "AssertionError" ? Status.FAILED : Status.BROKEN;

this.currentTest.status = status || errorStatus;
this.currentTest.status = status;
this.currentTest.stage = Stage.FINISHED;
this.currentTest.endTest();
this.currentTest = null;
Expand Down
4 changes: 1 addition & 3 deletions packages/allure-mocha/src/MochaAllureReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,14 @@ export class MochaAllureReporter extends Mocha.reporters.Base {
}

private onTest(test: Mocha.Test) {
const suite = test.parent;
this.allure.startCase((suite && suite.title) || "Unnamed", test.title);
this.allure.startCase(test);
}

private onPassed(test: Mocha.Test) {
this.allure.passTestCase();
}

private onFailed(test: Mocha.Test, error: Error) {
console.error(error);
this.allure.failTestCase(test, error);
}

Expand Down
25 changes: 25 additions & 0 deletions packages/allure-mocha/test/fixtures/specs/nested.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { expect } from "chai";

it("top-level test", () => {
expect(1).to.be.eq(1);
});

describe("Parent suite", () => {
it("shallow test", () => {
expect(1).to.be.eq(1);
});

describe("Nested suite", () => {
describe("Sub suite", () => {
it("child test", () => {
expect(1).to.be.eq(1);
});

describe("Incredibly nested suite", () => {
it("the deepest test", () => {
expect(1).to.be.eq(1);
});
});
});
});
});
7 changes: 7 additions & 0 deletions packages/allure-mocha/test/fixtures/specs/pending.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
describe("Pending tests", () => {
xit("simple pending", () => {});

it("skipped in runtime", function() {
this.skip();
});
});
19 changes: 17 additions & 2 deletions packages/allure-mocha/test/specs/common.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Status } from "allure-js-commons";
import { LabelName, Status } from "allure-js-commons";
import { expect } from "chai";
import { suite } from "mocha-typescript";
import { runTests } from "../utils";
import { findLabelValue, runTests } from "../utils";

@suite
class CommonSuite {
Expand All @@ -21,4 +21,19 @@ class CommonSuite {
expect(skippedTest.statusDetails.message).eq("Test ignored");
expect(skippedTest.statusDetails.trace).eq(undefined);
}

@test
async shouldHaveCorrectSuitesForPendingTests() {
const writerStub = await runTests("pending");

const simplePending = writerStub.getTestByName("simple pending");
expect(simplePending.status).eq(Status.SKIPPED);
expect(simplePending.historyId).eq("a50daf11cd50a0dcb2583dfaa90f796a");
expect(findLabelValue(simplePending, LabelName.PARENT_SUITE)).eq("Pending tests");

const skippedInRuntime = writerStub.getTestByName("skipped in runtime");
expect(skippedInRuntime.status).eq(Status.SKIPPED);
expect(skippedInRuntime.historyId).eq("e67a7b309c00b0cfa384a68790ef7f57");
expect(findLabelValue(skippedInRuntime, LabelName.PARENT_SUITE)).eq("Pending tests");
}
}
4 changes: 2 additions & 2 deletions packages/allure-mocha/test/specs/feature.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Status } from "allure-js-commons";
import { expect } from "chai";
import { suite } from "mocha-typescript";
import { findLabel, runTests } from "../utils";
import { findLabelValue, runTests } from "../utils";

@suite
class FeatureSuite {
Expand All @@ -11,6 +11,6 @@ class FeatureSuite {
const test = writerStub.getTestByName("shouldAssignFeature");

expect(test.status).eq(Status.PASSED);
expect(findLabel(test, "feature")!.value).eq("Login");
expect(findLabelValue(test, "feature")).eq("Login");
}
}
45 changes: 45 additions & 0 deletions packages/allure-mocha/test/specs/nesting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { InMemoryAllureWriter, LabelName } from "allure-js-commons";
import { expect } from "chai";
import { suite } from "mocha-typescript";
import { findLabelValue, runTests } from "../utils";

@suite
class NestingSupport {
private writerStub!: InMemoryAllureWriter;

async before() {
this.writerStub = await runTests("nested");
}

@test
shouldAssignAllSuites() {
const test = this.writerStub.getTestByName("child test");
expect(findLabelValue(test, LabelName.PARENT_SUITE)).eq("Parent suite");
expect(findLabelValue(test, LabelName.SUITE)).eq("Nested suite");
expect(findLabelValue(test, LabelName.SUB_SUITE)).eq("Sub suite");
}

@test
shouldSkipMissingLevels() {
const test = this.writerStub.getTestByName("shallow test");
expect(findLabelValue(test, LabelName.PARENT_SUITE)).eq("Parent suite");
expect(findLabelValue(test, LabelName.SUITE)).eq(undefined);
expect(findLabelValue(test, LabelName.SUB_SUITE)).eq(undefined);
}

@test
shouldHandleTopLevelTests() {
const test = this.writerStub.getTestByName("top-level test");
expect(findLabelValue(test, LabelName.PARENT_SUITE)).eq(undefined);
expect(findLabelValue(test, LabelName.SUITE)).eq(undefined);
expect(findLabelValue(test, LabelName.SUB_SUITE)).eq(undefined);
}

@test
shouldMergeSubSuiteNames() {
const test = this.writerStub.getTestByName("the deepest test");
expect(findLabelValue(test, LabelName.PARENT_SUITE)).eq("Parent suite");
expect(findLabelValue(test, LabelName.SUITE)).eq("Nested suite");
expect(findLabelValue(test, LabelName.SUB_SUITE)).eq("Sub suite > Incredibly nested suite");
}
}
4 changes: 2 additions & 2 deletions packages/allure-mocha/test/specs/owner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Status } from "allure-js-commons";
import { expect } from "chai";
import { suite } from "mocha-typescript";
import { findLabel, runTests } from "../utils";
import { findLabelValue, runTests } from "../utils";

@suite
class OwnerSuite {
Expand All @@ -11,6 +11,6 @@ class OwnerSuite {
const test = writerStub.getTestByName("shouldAssignOwner");

expect(test.status).eq(Status.PASSED);
expect(findLabel(test, "owner")!.value).eq("sskorol");
expect(findLabelValue(test, "owner")).eq("sskorol");
}
}
4 changes: 2 additions & 2 deletions packages/allure-mocha/test/specs/severity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Severity, Status } from "allure-js-commons";
import { expect } from "chai";
import { suite } from "mocha-typescript";
import { findLabel, runTests } from "../utils";
import { findLabelValue, runTests } from "../utils";

@suite
class SeveritySuite {
Expand All @@ -10,6 +10,6 @@ class SeveritySuite {
const writerStub = await runTests("severity");
const test = writerStub.getTestByName("shouldAssignSeverity");
expect(test.status).eq(Status.PASSED);
expect(findLabel(test, "severity")!.value).eq(Severity.BLOCKER);
expect(findLabelValue(test, "severity")).eq(Severity.BLOCKER);
}
}
4 changes: 2 additions & 2 deletions packages/allure-mocha/test/specs/story.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Severity, Status } from "allure-js-commons";
import { expect } from "chai";
import { suite } from "mocha-typescript";
import { findLabel, runTests } from "../utils";
import { findLabelValue, runTests } from "../utils";

@suite
class StorySuite {
Expand All @@ -11,6 +11,6 @@ class StorySuite {
const test = writerStub.getTestByName("shouldAssignStory");

expect(test.status).eq(Status.PASSED);
expect(findLabel(test, "story")!.value).eq("Common story");
expect(findLabelValue(test, "story")).eq("Common story");
}
}
4 changes: 2 additions & 2 deletions packages/allure-mocha/test/specs/tag.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Status } from "allure-js-commons";
import { expect } from "chai";
import { suite } from "mocha-typescript";
import { findLabel, runTests } from "../utils";
import { findLabelValue, runTests } from "../utils";

@suite
class TagSuite {
Expand All @@ -11,6 +11,6 @@ class TagSuite {
const test = writerStub.getTestByName("shouldAssignTag");

expect(test.status).eq(Status.PASSED);
expect(findLabel(test, "tag")!.value).eq("smoke");
expect(findLabelValue(test, "tag")).eq("smoke");
}
}
12 changes: 9 additions & 3 deletions packages/allure-mocha/test/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ export function runTests(...specs: string[]): Promise<InMemoryAllureWriter> {
});
}

export function findLabel(test: TestResult, labelName: string) {
return test.labels.find(label => label.name === labelName);
export function findLabelValue(test: TestResult, labelName: string) {
const label = test.labels.find(label => label.name === labelName);
return label && label.value;
}

export function findParameter(test: TestResult, parameterName: string): any {
Expand All @@ -28,5 +29,10 @@ function assignSpecs(mocha: Mocha, specs: string[]) {
jetpack
.dir(testDir)
.find({ matching: specs.map(spec => `${spec}.js`) })
.forEach(file => mocha.addFile(path.join(testDir, file)));
.forEach(file => {
const testPath = path.resolve(testDir, file);
// remove the test from node_modules cache, so it can be executed again
delete require.cache[testPath];
mocha.addFile(testPath);
});
}
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"target": "es6",
"lib": ["es6"],
"target": "es2017",
"lib": ["es2017"],
"module": "commonjs",
"moduleResolution": "node",
"declaration": true,
Expand Down

0 comments on commit ab47cdd

Please sign in to comment.