Skip to content

Commit 3999b4f

Browse files
authored
feat: reporting per each test case (#55)
1 parent f4b6098 commit 3999b4f

File tree

8 files changed

+527
-74
lines changed

8 files changed

+527
-74
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
run: npm test
4040
working-directory: e2e
4141
- name: Upload Allure results
42-
uses: actions/upload-artifact@v3
42+
uses: actions/upload-artifact@v4
4343
with:
4444
name: allure-results
4545
path: e2e/allure-results

src/reporter/JestAllure2Reporter.ts

Lines changed: 105 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,30 @@ import type {
99
TestContext,
1010
TestResult,
1111
} from '@jest/reporters';
12+
import type { TestInvocationMetadata } from 'jest-metadata';
1213
import JestMetadataReporter from 'jest-metadata/reporter';
1314
import type {
14-
AllureTestRunMetadata,
1515
AllureTestCaseMetadata,
1616
AllureTestFileMetadata,
17+
AllureTestRunMetadata,
18+
GlobalExtractorContext,
1719
Helpers,
1820
PropertyExtractorContext,
1921
ReporterOptions,
2022
TestCaseExtractorContext,
2123
TestFileExtractorContext,
2224
TestRunExtractorContext,
23-
GlobalExtractorContext,
2425
} from 'jest-allure2-reporter';
2526

2627
import { type ReporterConfig, resolveOptions } from '../options';
2728
import { AllureMetadataProxy, MetadataSquasher } from '../metadata';
28-
import { compactArray, FileNavigatorCache, stringifyValues } from '../utils';
29+
import {
30+
compactArray,
31+
DeferredTaskQueue,
32+
FileNavigatorCache,
33+
memoize,
34+
stringifyValues,
35+
} from '../utils';
2936
import { type AllureWriter, FileAllureWriter } from '../serialization';
3037
import { log, optimizeForTracing } from '../logger';
3138

@@ -45,13 +52,29 @@ const __TID_NAME = optimizeForTracing((test: Test, testCaseResult: TestCaseResul
4552
fullName: testCaseResult.fullName,
4653
}));
4754

55+
interface QueuedTestResult {
56+
test: Test;
57+
testCaseResult: TestCaseResult;
58+
testInvocationMetadata: TestInvocationMetadata;
59+
}
60+
4861
export class JestAllure2Reporter extends JestMetadataReporter {
4962
private readonly _globalConfig: Config.GlobalConfig;
5063
private readonly _options: ReporterOptions;
5164
private _globalContext: GlobalExtractorContext = NOT_INITIALIZED;
5265
private _writer: AllureWriter = NOT_INITIALIZED;
5366
private _config: ReporterConfig<void> = NOT_INITIALIZED;
5467
private _globalMetadataProxy: AllureMetadataProxy<AllureTestRunMetadata> = NOT_INITIALIZED;
68+
private readonly _taskQueue = new DeferredTaskQueue<QueuedTestResult, TestInvocationMetadata>({
69+
getThreadName: (item) => item.test.path,
70+
getPayloadKey: (item) => item.testInvocationMetadata,
71+
execute: (item) =>
72+
log.trace.complete(
73+
__TID_NAME(item.test, item.testCaseResult),
74+
item.testCaseResult.title,
75+
() => this.#writeTestResult(item),
76+
),
77+
});
5578

5679
constructor(globalConfig: Config.GlobalConfig, options: ReporterOptions) {
5780
super(globalConfig);
@@ -102,7 +125,7 @@ export class JestAllure2Reporter extends JestMetadataReporter {
102125
await super.onRunStart(aggregatedResult, options);
103126
const attemptRunStart = this.#attempt.bind(this, 'onRunStart()', this.#onRunStart);
104127

105-
await log.trace.begin(__TID(), 'jest-allure2-reporter');
128+
log.trace.begin(__TID(), 'jest-allure2-reporter');
106129
await log.trace.complete(__TID(), 'init', this.#init.bind(this));
107130
await log.trace.complete(__TID(), 'onRunStart', attemptRunStart);
108131
}
@@ -171,24 +194,63 @@ export class JestAllure2Reporter extends JestMetadataReporter {
171194
fallbacks.onTestFileStart(test, testFileMetadata);
172195
}
173196

174-
async onTestCaseResult(test: Test, testCaseResult: TestCaseResult) {
197+
onTestCaseStart(test: Test, testCaseResult: TestCaseResult) {
198+
super.onTestCaseStart(test, testCaseResult);
199+
this._taskQueue.startPending(test.path);
200+
}
201+
202+
onTestCaseResult(test: Test, testCaseResult: TestCaseResult) {
175203
super.onTestCaseResult(test, testCaseResult);
176204

177-
const execute = this.#onTestCaseResult.bind(this, test, testCaseResult);
178-
const attempt = this.#attempt.bind(this, 'onTestCaseResult()', execute);
179-
log.trace.complete(__TID_NAME(test, testCaseResult), testCaseResult.title, attempt);
205+
const testEntryMetadata = JestAllure2Reporter.query.testCaseResult(testCaseResult);
206+
const testInvocationMetadata = testEntryMetadata.lastInvocation!;
207+
fallbacks.onTestCaseResult(
208+
test,
209+
testCaseResult,
210+
new AllureMetadataProxy<AllureTestCaseMetadata>(testInvocationMetadata),
211+
);
212+
213+
this._taskQueue.enqueue({
214+
test,
215+
testCaseResult,
216+
testInvocationMetadata,
217+
});
180218
}
181219

182-
#onTestCaseResult(test: Test, testCaseResult: TestCaseResult) {
183-
const testCaseMetadata = new AllureMetadataProxy<AllureTestCaseMetadata>(
184-
JestAllure2Reporter.query.testCaseResult(testCaseResult).lastInvocation!,
185-
);
220+
async #writeTestResult({ test, testCaseResult, testInvocationMetadata }: QueuedTestResult) {
221+
await this.#scanSourceMaps(test.path);
222+
await postProcessMetadata(this._globalContext, testInvocationMetadata);
186223

187-
fallbacks.onTestCaseResult(test, testCaseResult, testCaseMetadata);
224+
const squasher = new MetadataSquasher();
225+
const testCaseContext: PropertyExtractorContext<TestCaseExtractorContext, void> = {
226+
...this._globalContext,
227+
filePath: path.relative(this._globalConfig.rootDir, test.path).split(path.sep),
228+
result: {},
229+
testCase: testCaseResult,
230+
testCaseMetadata: squasher.testInvocation(testInvocationMetadata),
231+
testFileMetadata: squasher.testFile(testInvocationMetadata.file),
232+
testRunMetadata: this._globalMetadataProxy.get(),
233+
value: undefined,
234+
};
235+
236+
const allureTest = await resolvePromisedTestCase(testCaseContext, this._config.testCase);
237+
if (!allureTest) {
238+
return;
239+
}
240+
241+
const invocationIndex =
242+
testInvocationMetadata.definition.invocations.indexOf(testInvocationMetadata);
243+
244+
await writeTest({
245+
resultsDir: this._config.resultsDir,
246+
containerName: `${testCaseResult.fullName} (${invocationIndex})`,
247+
writer: this._writer,
248+
test: allureTest,
249+
});
188250
}
189251

190252
async onTestFileResult(test: Test, testResult: TestResult, aggregatedResult: AggregatedResult) {
191-
await super.onTestFileResult(test, testResult, aggregatedResult);
253+
super.onTestFileResult(test, testResult, aggregatedResult);
192254

193255
const execute = this.#onTestFileResult.bind(this, test, testResult);
194256
const attempt = this.#attempt.bind(this, 'onTestFileResult()', execute);
@@ -199,25 +261,29 @@ export class JestAllure2Reporter extends JestMetadataReporter {
199261
async #onTestFileResult(test: Test, testResult: TestResult) {
200262
const rawMetadata = JestAllure2Reporter.query.test(test);
201263
const testFileMetadata = new AllureMetadataProxy<AllureTestFileMetadata>(rawMetadata);
202-
const globalMetadataProxy = this._globalMetadataProxy;
264+
fallbacks.onTestFileResult(test, testFileMetadata);
203265

204-
for (const loadedFile of globalMetadataProxy.get('loadedFiles', [])) {
205-
await FileNavigatorCache.instance.scanSourcemap(loadedFile);
266+
for (const testCaseResult of testResult.testResults) {
267+
const invocations =
268+
JestAllure2Reporter.query.testCaseResult(testCaseResult).invocations || [];
269+
270+
for (const testInvocationMetadata of invocations) {
271+
this._taskQueue.enqueue({
272+
test,
273+
testCaseResult,
274+
testInvocationMetadata,
275+
});
276+
}
206277
}
207278

208-
const allureWriter = this._writer;
209-
210-
fallbacks.onTestFileResult(test, testFileMetadata);
211-
await postProcessMetadata(this._globalContext, rawMetadata);
212-
213-
// ---
279+
await this._taskQueue.awaitCompletion();
214280

215281
const config = this._config;
216282
const squasher = new MetadataSquasher();
283+
const globalMetadataProxy = this._globalMetadataProxy;
217284

218285
const testFileContext: PropertyExtractorContext<TestFileExtractorContext, void> = {
219286
...this._globalContext,
220-
221287
filePath: path.relative(this._globalConfig.rootDir, testResult.testFilePath).split(path.sep),
222288
result: {},
223289
testFile: testResult,
@@ -231,39 +297,10 @@ export class JestAllure2Reporter extends JestMetadataReporter {
231297
await writeTest({
232298
resultsDir: config.resultsDir,
233299
containerName: `${testResult.testFilePath}`,
234-
writer: allureWriter,
300+
writer: this._writer,
235301
test: allureFileTest,
236302
});
237303
}
238-
239-
for (const testCaseResult of testResult.testResults) {
240-
const allInvocations =
241-
JestAllure2Reporter.query.testCaseResult(testCaseResult).invocations ?? [];
242-
243-
for (const testInvocationMetadata of allInvocations) {
244-
const testCaseMetadata = squasher.testInvocation(testInvocationMetadata);
245-
246-
const testCaseContext: PropertyExtractorContext<TestCaseExtractorContext, void> = {
247-
...testFileContext,
248-
testCase: testCaseResult,
249-
testCaseMetadata,
250-
};
251-
252-
const allureTest = await resolvePromisedTestCase(testCaseContext, config.testCase);
253-
if (!allureTest) {
254-
continue;
255-
}
256-
257-
const invocationIndex = allInvocations.indexOf(testInvocationMetadata);
258-
259-
await writeTest({
260-
resultsDir: config.resultsDir,
261-
containerName: `${testCaseResult.fullName} (${invocationIndex})`,
262-
writer: allureWriter,
263-
test: allureTest,
264-
});
265-
}
266-
}
267304
}
268305

269306
async onRunComplete(
@@ -305,4 +342,18 @@ export class JestAllure2Reporter extends JestMetadataReporter {
305342

306343
await allureWriter.cleanup?.();
307344
}
345+
346+
// Executing this once per file should be enough to scan source maps of auxiliary files
347+
#scanSourceMaps = memoize(async (_testPath: string) => {
348+
const globalMetadataProxy = this._globalMetadataProxy;
349+
const loadedFiles = globalMetadataProxy
350+
.get<string[]>('loadedFiles', [])
351+
.filter(FileNavigatorCache.instance.hasScannedSourcemap);
352+
353+
if (loadedFiles.length === 0) {
354+
return;
355+
}
356+
357+
return Promise.all(loadedFiles.map(FileNavigatorCache.instance.scanSourcemap));
358+
});
308359
}

src/reporter/postProcessMetadata.ts

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,38 @@ import type {
33
GlobalExtractorContext,
44
DocblockExtractionContext,
55
} from 'jest-allure2-reporter';
6-
import type { TestFileMetadata } from 'jest-metadata';
6+
import type { TestInvocationMetadata } from 'jest-metadata';
77

88
import { AllureMetadataProxy } from '../metadata';
99
import type { ReporterConfig } from '../options';
1010
import { log } from '../logger';
1111
import { compactObject, isEmpty } from '../utils';
1212

13+
const isNotProcessed = (() => {
14+
const set = new WeakSet();
15+
return (metadata: object) => {
16+
if (set.has(metadata)) {
17+
return false;
18+
}
19+
set.add(metadata);
20+
return true;
21+
};
22+
})();
23+
1324
export async function postProcessMetadata(
1425
globalContext: GlobalExtractorContext,
15-
testFile: TestFileMetadata,
26+
testInvocation: TestInvocationMetadata,
1627
) {
1728
const config = globalContext.reporterConfig as ReporterConfig;
1829
if (!config.sourceCode.enabled) {
1930
return;
2031
}
2132

22-
const allDescribeBlocks = [...testFile.allDescribeBlocks()];
23-
const allHookDefinitions = allDescribeBlocks.flatMap((describeBlock) => [
24-
...describeBlock.hookDefinitions(),
25-
]);
26-
2733
const batch = [
28-
testFile,
29-
...allDescribeBlocks,
30-
...allHookDefinitions,
31-
...testFile.allTestEntries(),
32-
...testFile.allTestInvocations(),
33-
...testFile.allInvocations(),
34-
];
34+
...testInvocation.allAncestors(),
35+
testInvocation,
36+
...testInvocation.allInvocations(),
37+
].filter(isNotProcessed);
3538

3639
await Promise.all(
3740
batch.map(async (metadata) => {

0 commit comments

Comments
 (0)