Skip to content

Commit

Permalink
feat(commons): process metadata messages from before hooks (fixes #1075
Browse files Browse the repository at this point in the history
…, via #1041)
  • Loading branch information
baev authored Aug 29, 2024
1 parent 086c849 commit 995c10e
Show file tree
Hide file tree
Showing 5 changed files with 243 additions and 37 deletions.
51 changes: 51 additions & 0 deletions packages/allure-jest/test/spec/hooks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,57 @@ it("should support steps in beforeEach hooks", async () => {
);
});

it("should support labels in beforeEach hooks", async () => {
const { tests } = await runJestInlineTest({
"sample.test.js": `
const { label } = require("allure-js-commons");
beforeEach(async () => {
await label("feature", "value 1");
await label("story", "value 2");
});
it("passed 1", () => {});
it("passed 2", () => {});
`,
});

expect(tests).toHaveLength(2);
expect(tests).toEqual(
expect.arrayContaining([
expect.objectContaining({
status: Status.PASSED,
name: "passed 1",
labels: expect.arrayContaining([
{
name: "feature",
value: "value 1",
},
{
name: "story",
value: "value 2",
},
]),
}),
expect.objectContaining({
status: Status.PASSED,
name: "passed 2",
labels: expect.arrayContaining([
{
name: "feature",
value: "value 1",
},
{
name: "story",
value: "value 2",
},
]),
}),
]),
);
});

it("should support steps in afterEach hooks", async () => {
const { tests, groups } = await runJestInlineTest({
"sample.test.js": `
Expand Down
46 changes: 26 additions & 20 deletions packages/allure-js-commons/src/sdk/reporter/LifecycleState.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,80 @@
import type { FixtureResult, StepResult, TestResult } from "../../model.js";
import type { FixtureType, FixtureWrapper, TestScope } from "./types.js";
import type { FixtureResultWrapper, FixtureType, TestResultWrapper, TestScope } from "./types.js";

export class LifecycleState {
scopes = new Map<string, TestScope>();
#scopes = new Map<string, TestScope>();

testResults = new Map<string, TestResult>();
#testResults = new Map<string, TestResultWrapper>();

stepResults = new Map<string, StepResult>();
#stepResults = new Map<string, StepResult>();

fixturesResults = new Map<string, FixtureWrapper>();
#fixturesResults = new Map<string, FixtureResultWrapper>();

getScope = (uuid: string) => this.scopes.get(uuid);
getScope = (uuid: string) => this.#scopes.get(uuid);

getWrappedFixtureResult = (uuid: string) => this.fixturesResults.get(uuid);
getWrappedFixtureResult = (uuid: string) => this.#fixturesResults.get(uuid);

getFixtureResult = (uuid: string) => this.getWrappedFixtureResult(uuid)?.value;

getTestResult = (uuid: string) => this.testResults.get(uuid);
getWrappedTestResult = (uuid: string) => this.#testResults.get(uuid);

getStepResult = (uuid: string) => this.stepResults.get(uuid);
getTestResult = (uuid: string) => this.getWrappedTestResult(uuid)?.value;

getStepResult = (uuid: string) => this.#stepResults.get(uuid);

getExecutionItem = (uuid: string): FixtureResult | TestResult | StepResult | undefined =>
this.getFixtureResult(uuid) ?? this.getTestResult(uuid) ?? this.getStepResult(uuid);

// test results
setTestResult = (uuid: string, result: TestResult) => {
this.testResults.set(uuid, result);
setTestResult = (uuid: string, result: TestResult, scopeUuids: string[] = []) => {
this.#testResults.set(uuid, { value: result, scopeUuids });
};

deleteTestResult = (uuid: string) => {
this.testResults.delete(uuid);
this.#testResults.delete(uuid);
};

// steps
setStepResult = (uuid: string, result: StepResult) => {
this.stepResults.set(uuid, result);
this.#stepResults.set(uuid, result);
};

deleteStepResult = (uuid: string) => {
this.stepResults.delete(uuid);
this.#stepResults.delete(uuid);
};

// fixtures
setFixtureResult = (uuid: string, type: FixtureType, result: FixtureResult) => {
const wrappedResult: FixtureWrapper = {
setFixtureResult = (scopeUuid: string, uuid: string, type: FixtureType, result: FixtureResult) => {
const wrappedResult: FixtureResultWrapper = {
uuid,
type,
value: result,
scopeUuid,
};
this.fixturesResults.set(uuid, wrappedResult);
this.#fixturesResults.set(uuid, wrappedResult);
return wrappedResult;
};

deleteFixtureResult = (uuid: string) => {
this.fixturesResults.delete(uuid);
this.#fixturesResults.delete(uuid);
};

// test scopes
setScope = (uuid: string, data: Partial<TestScope> = {}) => {
const scope: TestScope = {
labels: [],
links: [],
parameters: [],
fixtures: [],
tests: [],
...data,
uuid,
};
this.scopes.set(uuid, scope);
this.#scopes.set(uuid, scope);
return scope;
};

deleteScope = (uuid: string) => {
this.scopes.delete(uuid);
this.#scopes.delete(uuid);
};
}
96 changes: 82 additions & 14 deletions packages/allure-js-commons/src/sdk/reporter/ReporterRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ import { LifecycleState } from "./LifecycleState.js";
import { Notifier } from "./Notifier.js";
import { createFixtureResult, createStepResult, createTestResult } from "./factory.js";
import { hasSkipLabel } from "./testplan.js";
import type { FixtureType, FixtureWrapper, LinkConfig, ReporterRuntimeConfig, TestScope, Writer } from "./types.js";
import type {
FixtureResultWrapper,
FixtureType,
LinkConfig,
ReporterRuntimeConfig,
TestScope,
Writer,
} from "./types.js";
import { deepClone, formatLinks, getTestResultHistoryId, getTestResultTestCaseId, randomUuid } from "./utils.js";
import { buildAttachmentFileName } from "./utils/attachments.js";
import { resolveWriter } from "./writer/loader.js";
Expand Down Expand Up @@ -144,7 +151,7 @@ export class ReporterRuntime {
}

const uuid = randomUuid();
const wrappedFixture = this.state.setFixtureResult(uuid, type, {
const wrappedFixture = this.state.setFixtureResult(scopeUuid, uuid, type, {
...createFixtureResult(),
start: Date.now(),
...fixtureResult,
Expand Down Expand Up @@ -201,7 +208,7 @@ export class ReporterRuntime {
scope.tests.push(uuid);
});

this.state.setTestResult(uuid, testResult);
this.state.setTestResult(uuid, testResult, scopeUuids);
this.notifier.afterTestResultStart(testResult);
return uuid;
};
Expand All @@ -221,13 +228,15 @@ export class ReporterRuntime {
};

stopTest = (uuid: string, opts?: { stop?: number; duration?: number }) => {
const testResult = this.state.getTestResult(uuid);
if (!testResult) {
const wrapped = this.state.getWrappedTestResult(uuid);
if (!wrapped) {
// eslint-disable-next-line no-console
console.error(`could not stop test result: no test with uuid ${uuid}) is found`);
return;
}

const testResult = wrapped.value;

this.notifier.beforeTestResultStop(testResult);
testResult.testCaseId ??= getTestResultTestCaseId(testResult);
testResult.historyId ??= getTestResultHistoryId(testResult);
Expand All @@ -236,11 +245,19 @@ export class ReporterRuntime {
testResult.start = startStop.start;
testResult.stop = startStop.stop;

const scopeUuids = wrapped.scopeUuids;
scopeUuids.forEach((scopeUuid) => {
const scope = this.state.getScope(scopeUuid);
if (scope?.labels) {
testResult.labels = [...testResult.labels, ...scope.labels];
}
});

this.notifier.afterTestResultStop(testResult);
};

writeTest = (uuid: string) => {
const testResult = this.state.testResults.get(uuid);
const testResult = this.state.getTestResult(uuid);
if (!testResult) {
// eslint-disable-next-line no-console
console.error(`could not write test result: no test with uuid ${uuid} is found`);
Expand Down Expand Up @@ -430,17 +447,57 @@ export class ReporterRuntime {

#handleMetadataMessage = (rootUuid: string, message: RuntimeMetadataMessage["data"]) => {
// only display name could be set to fixture.
const fixtureResult = this.state.getFixtureResult(rootUuid);
const fixtureResult = this.state.getWrappedFixtureResult(rootUuid);
const { links, labels, parameters, displayName, testCaseId, historyId, description, descriptionHtml } = message;

if (fixtureResult) {
this.updateFixture(rootUuid, (result) => {
if (message.displayName) {
result.name = message.displayName;
if (displayName) {
this.updateFixture(rootUuid, (result) => {
result.name = displayName;
});
}

if (historyId) {
// eslint-disable-next-line no-console
console.error("historyId can't be changed within test fixtures");
}
if (testCaseId) {
// eslint-disable-next-line no-console
console.error("testCaseId can't be changed within test fixtures");
}

if (links || labels || parameters || description || descriptionHtml) {
// in some frameworks, afterEach methods can be executed before test stop event, while
// in others after. To remove the possible undetermined behaviour we only allow
// using runtime metadata API in before hooks.
if (fixtureResult.type === "after") {
// eslint-disable-next-line no-console
console.error("metadata messages isn't supported for after test fixtures");
return;
}
});

this.updateScope(fixtureResult.scopeUuid, (scope) => {
if (links) {
scope.links = [...scope.links, ...(this.linkConfig ? formatLinks(this.linkConfig, links) : links)];
}
if (labels) {
scope.labels = [...scope.labels, ...labels];
}
if (parameters) {
scope.parameters = [...scope.parameters, ...parameters];
}
if (description) {
scope.description = description;
}
if (descriptionHtml) {
scope.descriptionHtml = descriptionHtml;
}
});
}

return;
}

const { links, labels, parameters, displayName, ...rest } = message;
this.updateTest(rootUuid, (result) => {
if (links) {
result.links = [...result.links, ...(this.linkConfig ? formatLinks(this.linkConfig, links) : links)];
Expand All @@ -454,7 +511,18 @@ export class ReporterRuntime {
if (displayName) {
result.name = displayName;
}
Object.assign(result, rest);
if (testCaseId) {
result.testCaseId = testCaseId;
}
if (historyId) {
result.historyId = historyId;
}
if (description) {
result.description = description;
}
if (descriptionHtml) {
result.descriptionHtml = descriptionHtml;
}
});
};

Expand Down Expand Up @@ -549,7 +617,7 @@ export class ReporterRuntime {
}
};

#writeContainer = (tests: string[], wrappedFixture: FixtureWrapper) => {
#writeContainer = (tests: string[], wrappedFixture: FixtureResultWrapper) => {
const fixture = wrappedFixture.value;
const befores = wrappedFixture.type === "before" ? [wrappedFixture.value] : [];
const afters = wrappedFixture.type === "after" ? [wrappedFixture.value] : [];
Expand Down
26 changes: 23 additions & 3 deletions packages/allure-js-commons/src/sdk/reporter/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import type { FixtureResult, LinkType, StepResult, TestResult, TestResultContainer } from "../../model.js";
import type {
FixtureResult,
Label,
Link,
LinkType,
Parameter,
StepResult,
TestResult,
TestResultContainer,
} from "../../model.js";
import type { Category, EnvironmentInfo } from "../types.js";

export const ALLURE_METADATA_CONTENT_TYPE = "application/vnd.allure.metadata+json";
Expand Down Expand Up @@ -67,13 +76,24 @@ export interface Writer {
export type TestScope = {
uuid: string;
tests: string[];
fixtures: FixtureWrapper[];
fixtures: FixtureResultWrapper[];
labels: Label[];
links: Link[];
parameters: Parameter[];
description?: string;
descriptionHtml?: string;
};

export type FixtureType = "before" | "after";

export type FixtureWrapper = {
export type FixtureResultWrapper = {
uuid: string;
value: FixtureResult;
type: FixtureType;
scopeUuid: string;
};

export type TestResultWrapper = {
value: TestResult;
scopeUuids: string[];
};
Loading

0 comments on commit 995c10e

Please sign in to comment.