Skip to content

Commit 30af8fe

Browse files
authored
feat: Improve report layout & add stats (#97)
* feat: Improve report layout & add stats * chore: Fix tests
1 parent c95f3a9 commit 30af8fe

6 files changed

Lines changed: 418 additions & 199 deletions

File tree

lib/cli/run.test.ts

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'path';
2+
import { promises as fs } from 'fs';
23
import execa, { ExecaReturnValue } from 'execa';
34

45
import { CliRunOptions, Config } from '../types';
@@ -12,12 +13,19 @@ describe('run.ts', () => {
1213
const bundleIdIOS = 'org.reactjs.native.example.RNDemo';
1314
const mockBundleIdResponse = { stdout: bundleIdIOS } as ExecaReturnValue<any>;
1415

16+
const mkdirMock = jest.spyOn(fs, 'mkdir');
17+
18+
jest
19+
.spyOn(process, 'cwd')
20+
.mockReturnValue('/Users/johndoe/Projects/my-project');
21+
1522
const execKillMock = {
1623
kill: jest.fn(),
1724
} as unknown as execa.ExecaChildProcess<any>;
1825
const execMock = jest.spyOn(execa, 'command').mockImplementation();
1926

2027
beforeEach(() => {
28+
mkdirMock.mockReset();
2129
execMock.mockReset().mockReturnValue(execKillMock);
2230
});
2331

@@ -208,11 +216,13 @@ describe('run.ts', () => {
208216
},
209217
};
210218

211-
const expectedJestCommand = `jest --config=${path.join(
212-
process.cwd(),
213-
'lib',
214-
'jest-config.json'
215-
)} --roots=${path.join(process.cwd())} --runInBand`;
219+
const jestConfigPath = path.join(__dirname, '..', 'jest-config.json');
220+
221+
const expectedJestCommand = `jest --config=${jestConfigPath} --roots=${path.join(
222+
process.cwd()
223+
)} --runInBand`;
224+
225+
const expectedJestCommandWithReport = `${expectedJestCommand} --json --outputFile=/Users/johndoe/Projects/my-project/.owl/report/jest-report.json`;
216226

217227
const commandSyncMock = jest.spyOn(execa, 'commandSync');
218228
const mockGenerateReport = jest.spyOn(reportHelpers, 'generateReport');
@@ -225,25 +235,33 @@ describe('run.ts', () => {
225235
});
226236

227237
it('runs an iOS project', async () => {
228-
jest.spyOn(configHelpers, 'getConfig').mockResolvedValueOnce(config);
238+
jest
239+
.spyOn(configHelpers, 'getConfig')
240+
.mockResolvedValueOnce({ ...config, report: true });
229241
const mockRunIOS = jest.spyOn(run, 'runIOS').mockResolvedValueOnce();
230242
const mockRestoreIOSUI = jest
231243
.spyOn(run, 'restoreIOSUI')
232244
.mockResolvedValueOnce();
233245

246+
mkdirMock.mockResolvedValue(undefined);
247+
234248
await run.runHandler(args);
235249

250+
await expect(mkdirMock).toHaveBeenCalled();
236251
await expect(mockRunIOS).toHaveBeenCalled();
237252
await expect(commandSyncMock).toHaveBeenCalledTimes(1);
238-
await expect(commandSyncMock).toHaveBeenCalledWith(expectedJestCommand, {
239-
env: {
240-
OWL_DEBUG: 'false',
241-
OWL_IOS_SIMULATOR: 'iPhone Simulator',
242-
OWL_PLATFORM: 'ios',
243-
OWL_UPDATE_BASELINE: 'false',
244-
},
245-
stdio: 'inherit',
246-
});
253+
await expect(commandSyncMock).toHaveBeenCalledWith(
254+
expectedJestCommandWithReport,
255+
{
256+
env: {
257+
OWL_DEBUG: 'false',
258+
OWL_IOS_SIMULATOR: 'iPhone Simulator',
259+
OWL_PLATFORM: 'ios',
260+
OWL_UPDATE_BASELINE: 'false',
261+
},
262+
stdio: 'inherit',
263+
}
264+
);
247265
await expect(mockRestoreIOSUI).toHaveBeenCalled();
248266
});
249267

@@ -329,10 +347,23 @@ describe('run.ts', () => {
329347
commandSyncMock.mockRejectedValueOnce(undefined!);
330348

331349
try {
332-
await run.runHandler({ ...args, update: true });
350+
await run.runHandler({ ...args });
333351
} catch {
352+
await expect(commandSyncMock).toHaveBeenCalledWith(
353+
expectedJestCommand,
354+
{
355+
env: {
356+
OWL_DEBUG: 'false',
357+
OWL_IOS_SIMULATOR: 'iPhone Simulator',
358+
OWL_PLATFORM: 'ios',
359+
OWL_UPDATE_BASELINE: 'false',
360+
},
361+
stdio: 'inherit',
362+
}
363+
);
334364
await expect(mockRunIOS).toHaveBeenCalled();
335365
await expect(commandSyncMock).toHaveBeenCalledTimes(1);
366+
await expect(mkdirMock).not.toHaveBeenCalled();
336367
await expect(mockGenerateReport).not.toHaveBeenCalled();
337368
}
338369
});

lib/cli/run.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from 'path';
22
import execa from 'execa';
3+
import { promises as fs } from 'fs';
34

45
import { cleanupScreenshots } from '../screenshot';
56
import { CliRunOptions, Config } from '../types';
@@ -86,6 +87,7 @@ export const runAndroid = async (config: Config, logger: Logger) => {
8687
};
8788

8889
export const runHandler = async (args: CliRunOptions) => {
90+
const cwd = process.cwd();
8991
const config = await getConfig(args.config);
9092
const logger = new Logger(config.debug);
9193
const runProject = args.platform === 'ios' ? runIOS : runAndroid;
@@ -108,7 +110,23 @@ export const runHandler = async (args: CliRunOptions) => {
108110
await runProject(config, logger);
109111

110112
const jestConfigPath = path.join(__dirname, '..', 'jest-config.json');
111-
const jestCommand = `jest --config=${jestConfigPath} --roots=${process.cwd()} --runInBand`;
113+
const jestCommandArgs = [
114+
'jest',
115+
`--config=${jestConfigPath}`,
116+
`--roots=${cwd}`,
117+
'--runInBand',
118+
];
119+
120+
if (config.report) {
121+
const reportDirPath = path.join(cwd, '.owl', 'report');
122+
const outputFile = path.join(reportDirPath, 'jest-report.json');
123+
124+
await fs.mkdir(reportDirPath, { recursive: true });
125+
126+
jestCommandArgs.push(`--json --outputFile=${outputFile}`);
127+
}
128+
129+
const jestCommand = jestCommandArgs.join(' ');
112130

113131
logger.print(
114132
`[OWL - CLI] ${

lib/report.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ describe('report.ts', () => {
3636
.spyOn(handlebars, 'compile')
3737
.mockImplementationOnce(() => () => '<h1>Hello World Compiled</h1>');
3838

39-
readFileMock.mockResolvedValue(htmlTemplate);
39+
readFileMock
40+
.mockResolvedValueOnce('{}')
41+
.mockResolvedValueOnce(htmlTemplate);
4042
mkdirMock.mockResolvedValue(undefined);
4143
readdirMock.mockResolvedValue([]);
4244

@@ -61,7 +63,9 @@ describe('report.ts', () => {
6163
.spyOn(handlebars, 'compile')
6264
.mockImplementationOnce(() => () => '<h1>Hello World Compiled</h1>');
6365

64-
readFileMock.mockResolvedValue(htmlTemplate);
66+
readFileMock
67+
.mockResolvedValueOnce('{}')
68+
.mockResolvedValueOnce(htmlTemplate);
6569
mkdirMock.mockResolvedValue(undefined);
6670
readdirMock.mockResolvedValue([]);
6771

lib/report.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import handlebars from 'handlebars';
33
import { promises as fs } from 'fs';
44

55
import { Logger } from './logger';
6-
import { Platform } from './types';
6+
import { JestReport, Platform, ReportStats } from './types';
77
import { fileExists } from './utils/file-exists';
88

99
export const cleanupReport = async () => {
@@ -16,6 +16,11 @@ export const cleanupReport = async () => {
1616
export const generateReport = async (logger: Logger, platform: Platform) => {
1717
const cwd = process.cwd();
1818
const reportDirPath = path.join(cwd, '.owl', 'report');
19+
20+
const jestOutputFilepath = path.join(reportDirPath, 'jest-report.json');
21+
const jestOutputText = await fs.readFile(jestOutputFilepath, 'utf8');
22+
const jestOutput = JSON.parse(jestOutputText) as JestReport;
23+
1924
const diffScreenshotsDirPath = path.join(cwd, '.owl', 'diff', platform);
2025
const baselineScreenshotsDirPath = path.join(
2126
cwd,
@@ -44,6 +49,20 @@ export const generateReport = async (logger: Logger, platform: Platform) => {
4449
(screenshot) => !failingScreenshots.includes(screenshot)
4550
);
4651

52+
const duration = (Date.now() - jestOutput.startTime) / 1000;
53+
const durationFormatted = parseFloat(`${duration}`).toFixed(2);
54+
55+
const stats: ReportStats = {
56+
totalTestSuites: jestOutput.numTotalTestSuites,
57+
totalTests: jestOutput.numTotalTests,
58+
failedTestSuites: jestOutput.numFailedTestSuites,
59+
failedTests: jestOutput.numFailedTests,
60+
passedTestSuites: jestOutput.numPassedTestSuites,
61+
passedTests: jestOutput.numPassedTests,
62+
duration: durationFormatted,
63+
success: jestOutput.success,
64+
};
65+
4766
logger.info(`[OWL - CLI] Generating Report`);
4867

4968
const reportFilename = 'index.html';
@@ -52,10 +71,11 @@ export const generateReport = async (logger: Logger, platform: Platform) => {
5271
const templateScript = handlebars.compile(htmlTemplate);
5372
const htmlContent = templateScript({
5473
currentYear: new Date().getFullYear(),
55-
currentDateTime: new Date().toISOString(),
74+
currentDateTime: new Date().toUTCString(),
5675
platform,
5776
failingScreenshots,
5877
passingScreenshots,
78+
stats,
5979
});
6080

6181
await fs.mkdir(reportDirPath, { recursive: true });

0 commit comments

Comments
 (0)