Skip to content

Commit 71409ee

Browse files
authored
fix(report): reporter type not dep order (#961)
1 parent 0359d23 commit 71409ee

File tree

3 files changed

+214
-13
lines changed

3 files changed

+214
-13
lines changed

packages/web-integration/src/playwright/reporter/index.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,20 @@ function logger(...message: any[]) {
2020
}
2121
}
2222

23+
interface MidsceneReporterOptions {
24+
type?: 'merged' | 'separate';
25+
}
26+
2327
class MidsceneReporter implements Reporter {
2428
private mergedFilename?: string;
2529
private testTitleToFilename = new Map<string, string>();
2630
mode?: 'merged' | 'separate';
2731

32+
constructor(options: MidsceneReporterOptions = {}) {
33+
// Set mode from constructor options (official Playwright way)
34+
this.mode = MidsceneReporter.getMode(options.type ?? 'merged');
35+
}
36+
2837
private static getMode(reporterType: string): 'merged' | 'separate' {
2938
if (!reporterType) {
3039
return 'merged';
@@ -72,14 +81,9 @@ class MidsceneReporter implements Reporter {
7281
reportPath && printReportMsg(reportPath);
7382
}
7483

75-
async onBegin(config: FullConfig, suite: Suite) {
76-
const reporterType = config.reporter?.[1]?.[1]?.type;
77-
this.mode = MidsceneReporter.getMode(reporterType);
78-
// const suites = suite.allTests();
79-
// logger(`Starting the run with ${suites.length} tests`);
80-
}
84+
async onBegin(config: FullConfig, suite: Suite) {}
8185

82-
onTestBegin(test: TestCase, _result: TestResult) {
86+
onTestBegin(_test: TestCase, _result: TestResult) {
8387
// logger(`Starting test ${test.title}`);
8488
}
8589

packages/web-integration/tests/playwright.config.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import path from 'node:path';
2-
// import { fileURLToPath } from 'node:url';
32
import { defineConfig, devices } from '@playwright/test';
43
import dotenv from 'dotenv';
54

6-
// add these two lines to get the current file directory (ES module compatible way)
7-
// const __filename = fileURLToPath(import.meta.url);
8-
// const __dirname = path.dirname(__filename);
9-
105
const MIDSCENE_REPORT = process.env.MIDSCENE_REPORT;
116

127
/**
@@ -64,6 +59,6 @@ export default defineConfig({
6459
// { outputFile: 'midscene_run/playwright-reporter/test-results.json' },
6560
// ],
6661
// ['html', { outputFolder: 'midscene_run/playwright-reporter' }],
67-
['../src/playwright/reporter/index.ts'], // separate/merged
62+
['../src/playwright/reporter/index.ts', { type: 'separate' }], // separate/merged
6863
],
6964
});
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import { createRequire } from 'node:module';
2+
import path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
import MidsceneReporter from '@/playwright/reporter';
5+
import * as coreUtils from '@midscene/core/utils';
6+
import type {
7+
FullConfig,
8+
Suite,
9+
TestCase,
10+
TestResult,
11+
} from '@playwright/test/reporter';
12+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
13+
14+
const require = createRequire(import.meta.url);
15+
16+
const __filename_url = fileURLToPath(import.meta.url);
17+
const __dirname = path.dirname(__filename_url);
18+
19+
// Mock core utilities to prevent actual file I/O
20+
vi.mock('@midscene/core/utils', () => ({
21+
writeDumpReport: vi.fn(),
22+
}));
23+
24+
// Resolve path to the actual reporter file from the test file's location
25+
const reporterSrcPath = path.resolve(
26+
__dirname,
27+
'../../src/playwright/reporter/index.ts',
28+
);
29+
const reporterPackageName = '@midscene/web/playwright-reporter';
30+
31+
describe('MidsceneReporter', () => {
32+
const mockSuite = {} as Suite;
33+
let originalResolve: typeof require.resolve;
34+
35+
beforeEach(() => {
36+
// Reset mocks before each test
37+
vi.clearAllMocks();
38+
// Backup original require.resolve
39+
originalResolve = require.resolve;
40+
});
41+
42+
afterEach(() => {
43+
// Restore any global mocks
44+
vi.unstubAllGlobals();
45+
// Restore original require.resolve
46+
require.resolve = originalResolve;
47+
});
48+
49+
describe('constructor', () => {
50+
it('should set mode to separate when type option is provided', () => {
51+
const reporter = new MidsceneReporter({ type: 'separate' });
52+
expect(reporter.mode).toBe('separate');
53+
});
54+
55+
it('should set mode to merged when type option is provided', () => {
56+
const reporter = new MidsceneReporter({ type: 'merged' });
57+
expect(reporter.mode).toBe('merged');
58+
});
59+
60+
it('should default to merged mode when no options are provided', () => {
61+
const reporter = new MidsceneReporter();
62+
expect(reporter.mode).toBe('merged');
63+
});
64+
65+
it('should default to merged mode when options object is empty', () => {
66+
const reporter = new MidsceneReporter({});
67+
expect(reporter.mode).toBe('merged');
68+
});
69+
70+
it('should default to merged mode when type is undefined', () => {
71+
const reporter = new MidsceneReporter({ type: undefined });
72+
expect(reporter.mode).toBe('merged');
73+
});
74+
75+
it('should throw error for invalid type', () => {
76+
expect(() => {
77+
new MidsceneReporter({ type: 'invalid' as any });
78+
}).toThrow(
79+
"Unknown reporter type in playwright config: invalid, only support 'merged' or 'separate'",
80+
);
81+
});
82+
});
83+
84+
describe('onTestEnd', () => {
85+
it('should not write a report if mode is not set', () => {
86+
const reporter = new MidsceneReporter();
87+
// Manually clear mode to simulate the old behavior where mode could be undefined
88+
reporter.mode = undefined;
89+
90+
const mockTest: TestCase = {
91+
id: 'test-id-0',
92+
title: 'Should Not Report',
93+
annotations: [
94+
{
95+
type: 'MIDSCENE_DUMP_ANNOTATION',
96+
description: 'should-not-be-written',
97+
},
98+
],
99+
} as any;
100+
const mockResult: TestResult = { status: 'passed' } as any;
101+
102+
reporter.onTestEnd(mockTest, mockResult);
103+
expect(coreUtils.writeDumpReport).not.toHaveBeenCalled();
104+
});
105+
106+
it('should write a report if dump annotation is present and mode is set', async () => {
107+
const reporter = new MidsceneReporter({ type: 'merged' });
108+
109+
const mockTest: TestCase = {
110+
id: 'test-id-1',
111+
title: 'My Test Case',
112+
annotations: [
113+
{ type: 'some-other-annotation', description: 'some-data' },
114+
{ type: 'MIDSCENE_DUMP_ANNOTATION', description: 'dump-data-string' },
115+
],
116+
} as any;
117+
const mockResult: TestResult = {
118+
status: 'passed',
119+
duration: 123,
120+
} as any;
121+
122+
reporter.onTestEnd(mockTest, mockResult);
123+
124+
expect(coreUtils.writeDumpReport).toHaveBeenCalledTimes(1);
125+
expect(coreUtils.writeDumpReport).toHaveBeenCalledWith(
126+
expect.stringContaining('playwright-merged'),
127+
{
128+
dumpString: 'dump-data-string',
129+
attributes: {
130+
playwright_test_id: 'test-id-1',
131+
playwright_test_title: 'My Test Case',
132+
playwright_test_status: 'passed',
133+
playwright_test_duration: 123,
134+
},
135+
},
136+
true, // merged mode
137+
);
138+
});
139+
140+
it('should remove the dump annotation after processing', async () => {
141+
const reporter = new MidsceneReporter({ type: 'merged' });
142+
143+
const mockTest: TestCase = {
144+
id: 'test-id-2',
145+
title: 'Another Test',
146+
annotations: [
147+
{ type: 'MIDSCENE_DUMP_ANNOTATION', description: 'dump-data' },
148+
],
149+
} as any;
150+
const mockResult: TestResult = { status: 'failed' } as any;
151+
152+
reporter.onTestEnd(mockTest, mockResult);
153+
154+
expect(mockTest.annotations).toEqual([]);
155+
});
156+
157+
it('should not write a report if dump annotation is not present', async () => {
158+
const reporter = new MidsceneReporter({ type: 'merged' });
159+
160+
const mockTest: TestCase = {
161+
id: 'test-id-3',
162+
title: 'No Dump Test',
163+
annotations: [{ type: 'some-other-annotation' }],
164+
} as any;
165+
const mockResult: TestResult = { status: 'passed' } as any;
166+
167+
reporter.onTestEnd(mockTest, mockResult);
168+
169+
expect(coreUtils.writeDumpReport).not.toHaveBeenCalled();
170+
});
171+
172+
it('should handle retry attempts in test title and id', async () => {
173+
const reporter = new MidsceneReporter({ type: 'merged' });
174+
175+
const mockTest: TestCase = {
176+
id: 'test-id-4',
177+
title: 'A Flaky Test',
178+
annotations: [
179+
{ type: 'MIDSCENE_DUMP_ANNOTATION', description: 'flaky-data' },
180+
],
181+
} as any;
182+
const mockResult: TestResult = {
183+
status: 'passed',
184+
duration: 456,
185+
retry: 1,
186+
} as any;
187+
188+
reporter.onTestEnd(mockTest, mockResult);
189+
190+
expect(coreUtils.writeDumpReport).toHaveBeenCalledWith(
191+
expect.any(String),
192+
expect.objectContaining({
193+
attributes: expect.objectContaining({
194+
playwright_test_id: 'test-id-4(retry #1)',
195+
playwright_test_title: 'A Flaky Test(retry #1)',
196+
}),
197+
}),
198+
true,
199+
);
200+
});
201+
});
202+
});

0 commit comments

Comments
 (0)