diff --git a/README.md b/README.md index 4fbbc7c7..ef6ce040 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,14 @@ The config file - which unless specified in the cli should live in `./owl.config ### Building the app -##### Options +#### Options -| Name | Required | Default | Choices | Description | -| ---------- | -------- | ----------------- | --------------- | --------------------------------------- | -| `config` | false | ./owl.config.json | - | Path to the configuration file | -| `platform` | true | - | `ios`,`android` | The platform the app should be built on | +| Name | Required | Default | Options/Types | Description | +| ---------------- | -------- | ----------------- | --------------- | --------------------------------------- | +| `config`, `-c` | false | ./owl.config.json | String | Path to the configuration file | +| `platform`, `-p` | true | - | `ios`,`android` | The platform the app should be built on | + +#### Examples ``` owl build --platform ios --config ./owl.config.json @@ -63,8 +65,20 @@ owl build --platform ios --config ./owl.config.json ### Running the tests +#### Options + +| Name | Required | Default | Options/Types | Description | +| ---------------- | -------- | ----------------- | --------------- | ----------------------------------------------- | +| `config`, `-c` | false | ./owl.config.json | String | Path to the configuration file | +| `platform`, `-p` | true | - | `ios`,`android` | The platform the app should be built on | +| `update`, `-u` | true | false | Boolean | A flag about rewriting existing baseline images | + +#### Examples + ``` +owl test --platform ios owl test --platform ios --config ./owl.config.json +owl test --platform ios --update ``` [github-image]: https://github.com/FormidableLabs/react-native-owl/workflows/Run%20Tests/badge.svg diff --git a/src/cli/build.test.ts b/src/cli/build.test.ts index 867d7c35..c715d8ea 100644 --- a/src/cli/build.test.ts +++ b/src/cli/build.test.ts @@ -2,7 +2,7 @@ import path from 'path'; import execa from 'execa'; import { buildAndroid, buildHandler, buildIOS } from './build'; -import { BuildRunOptions, Config } from './types'; +import { CliBuildOptions, Config } from './types'; import { Logger } from '../logger'; import * as configHelpers from './config'; @@ -128,7 +128,7 @@ describe('build.ts', () => { const args = { platform: 'ios', config: './owl.config.json', - } as BuildRunOptions; + } as CliBuildOptions; const config: Config = { ios: { diff --git a/src/cli/build.ts b/src/cli/build.ts index fa846a4d..686aba52 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -1,7 +1,7 @@ import path from 'path'; import execa from 'execa'; -import { BuildRunOptions, Config } from './types'; +import { CliBuildOptions, Config } from './types'; import { Logger } from '../logger'; import { getConfig } from './config'; @@ -46,7 +46,7 @@ export const buildAndroid = async ( await execa.command(buildCommand.join(' '), { stdio: 'inherit', cwd }); }; -export const buildHandler = async (args: BuildRunOptions) => { +export const buildHandler = async (args: CliBuildOptions) => { const config = await getConfig(args.config); const logger = new Logger(config.debug); const buildProject = args.platform === 'ios' ? buildIOS : buildAndroid; diff --git a/src/cli/index.ts b/src/cli/index.ts index 58899433..f8e84c5f 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -20,9 +20,22 @@ const configOption: Options = { default: './owl.config.json', }; -const builderOptions = { +const updateOption: Options = { + alias: 'u', + describe: 'Update the baseline screenshots', + type: 'boolean', + default: false, +}; + +const builderOptionsRun = { + config: configOption, + platform: plaformOption, +}; + +const builderOptionsTest = { config: configOption, platform: plaformOption, + update: updateOption, }; argv @@ -30,13 +43,13 @@ argv .command({ command: 'build', describe: 'Build the React Native project', - builder: builderOptions, + builder: builderOptionsRun, handler: buildHandler, }) .command({ command: 'test', describe: 'Runs the test suite', - builder: builderOptions, + builder: builderOptionsTest, handler: runHandler, }) .help('help') diff --git a/src/cli/run.test.ts b/src/cli/run.test.ts index 9438b27f..cdf13788 100644 --- a/src/cli/run.test.ts +++ b/src/cli/run.test.ts @@ -1,7 +1,7 @@ import path from 'path'; import execa, { ExecaSyncReturnValue } from 'execa'; -import { BuildRunOptions, Config } from './types'; +import { CliRunOptions, Config } from './types'; import { Logger } from '../logger'; import * as configHelpers from './config'; import * as run from './run'; @@ -167,7 +167,8 @@ describe('run.ts', () => { const args = { platform: 'ios', config: './owl.config.json', - } as BuildRunOptions; + update: false, + } as CliRunOptions; const config: Config = { ios: { @@ -204,7 +205,11 @@ describe('run.ts', () => { await expect(mockRunIOS).toHaveBeenCalled(); await expect(commandSyncMock).toHaveBeenCalledTimes(1); await expect(commandSyncMock).toHaveBeenCalledWith(expectedJestCommand, { - env: { OWL_DEBUG: 'false', OWL_PLATFORM: 'ios' }, + env: { + OWL_DEBUG: 'false', + OWL_PLATFORM: 'ios', + OWL_UPDATE_BASELINE: 'false', + }, stdio: 'inherit', }); }); @@ -220,7 +225,29 @@ describe('run.ts', () => { await expect(mockRunAndroid).toHaveBeenCalled(); await expect(commandSyncMock).toHaveBeenCalledTimes(1); await expect(commandSyncMock).toHaveBeenCalledWith(expectedJestCommand, { - env: { OWL_DEBUG: 'false', OWL_PLATFORM: 'android' }, + env: { + OWL_DEBUG: 'false', + OWL_PLATFORM: 'android', + OWL_UPDATE_BASELINE: 'false', + }, + stdio: 'inherit', + }); + }); + + it('runs with the the update baseline flag on', async () => { + jest.spyOn(configHelpers, 'getConfig').mockResolvedValueOnce(config); + const mockRunIOS = jest.spyOn(run, 'runIOS').mockResolvedValueOnce(); + + await run.runHandler({ ...args, update: true }); + + await expect(mockRunIOS).toHaveBeenCalled(); + await expect(commandSyncMock).toHaveBeenCalledTimes(1); + await expect(commandSyncMock).toHaveBeenCalledWith(expectedJestCommand, { + env: { + OWL_DEBUG: 'false', + OWL_PLATFORM: 'ios', + OWL_UPDATE_BASELINE: 'true', + }, stdio: 'inherit', }); }); diff --git a/src/cli/run.ts b/src/cli/run.ts index aef28929..31514e5a 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -1,7 +1,7 @@ import path from 'path'; import execa from 'execa'; -import { BuildRunOptions, Config } from './types'; +import { CliRunOptions, Config } from './types'; import { Logger } from '../logger'; import { getConfig } from './config'; @@ -53,7 +53,7 @@ export const runAndroid = async (config: Config, logger: Logger) => { await execa.command(launchCommand, { stdio }); }; -export const runHandler = async (args: BuildRunOptions) => { +export const runHandler = async (args: CliRunOptions) => { const config = await getConfig(args.config); const logger = new Logger(config.debug); const runProject = args.platform === 'ios' ? runIOS : runAndroid; @@ -64,6 +64,14 @@ export const runHandler = async (args: BuildRunOptions) => { const jestConfigPath = path.join(__dirname, '..', 'jest-config.json'); const jestCommand = `jest --config=${jestConfigPath} --roots=${process.cwd()}`; + logger.print( + `[OWL] ${ + args.update + ? '(Update mode) Updating baseline images' + : '(Tests mode) Will compare latest images with the baseline' + }.` + ); + logger.info(`[OWL] Will use the jest config localed at ${jestConfigPath}.`); logger.info(`[OWL] Will set the jest root to ${process.cwd()}.`); @@ -72,8 +80,14 @@ export const runHandler = async (args: BuildRunOptions) => { env: { OWL_PLATFORM: args.platform, OWL_DEBUG: String(!!config.debug), + OWL_UPDATE_BASELINE: String(!!args.update), }, }); logger.print(`[OWL] Tests completed on ${args.platform}.`); + if (args.update) { + logger.print( + `[OWL] All baseline images for ${args.platform} have been updated successfully.` + ); + } }; diff --git a/src/cli/types.ts b/src/cli/types.ts index 3eac219a..f3eae45f 100644 --- a/src/cli/types.ts +++ b/src/cli/types.ts @@ -2,11 +2,17 @@ import { Arguments } from 'yargs'; export type Platform = 'ios' | 'android'; -export interface BuildRunOptions extends Arguments { +export interface CliBuildOptions extends Arguments { platform: Platform; config: string; } +export interface CliRunOptions extends Arguments { + platform: Platform; + config: string; + update: boolean; +} + type ConfigIOS = { /** The workspace to build. */ workspace?: string; diff --git a/src/take-screenshot.test.ts b/src/take-screenshot.test.ts index 14ef1d78..6f541aa1 100644 --- a/src/take-screenshot.test.ts +++ b/src/take-screenshot.test.ts @@ -15,43 +15,95 @@ describe('take-screenshot.ts', () => { commandMock.mockReset(); }); - describe('iOS', () => { + describe('Baseline', () => { beforeAll(() => { - process.env.OWL_PLATFORM = 'ios'; - process.env.OWL_DEBUG = 'false'; + process.env.OWL_UPDATE_BASELINE = 'true'; }); - it('should take a screenshot', async () => { - await takeScreenshot(); - - expect(commandMock).toHaveBeenCalledWith( - 'xcrun simctl io booted screenshot screen.png', - { - cwd: path.join(process.cwd(), '.owl', 'ios'), - shell: false, - stdio: 'ignore', - } - ); + describe('iOS', () => { + beforeAll(() => { + process.env.OWL_PLATFORM = 'ios'; + process.env.OWL_DEBUG = 'false'; + }); + + it('should take a screenshot', async () => { + await takeScreenshot(); + + expect(commandMock).toHaveBeenCalledWith( + 'xcrun simctl io booted screenshot screen.png', + { + cwd: path.join(process.cwd(), '.owl', 'baseline', 'ios'), + shell: false, + stdio: 'ignore', + } + ); + }); + }); + + describe('Android', () => { + beforeAll(() => { + process.env.OWL_PLATFORM = 'android'; + process.env.OWL_DEBUG = 'false'; + }); + + it('should take a screenshot', async () => { + await takeScreenshot(); + + expect(commandMock).toHaveBeenCalledWith( + 'adb exec-out screencap -p > screen.png', + { + cwd: path.join(process.cwd(), '.owl', 'baseline', 'android'), + shell: true, + stdio: 'ignore', + } + ); + }); }); }); - describe('Android', () => { + describe('Latest', () => { beforeAll(() => { - process.env.OWL_PLATFORM = 'android'; - process.env.OWL_DEBUG = 'false'; + process.env.OWL_UPDATE_BASELINE = 'false'; }); - it('should take a screenshot', async () => { - await takeScreenshot(); - - expect(commandMock).toHaveBeenCalledWith( - 'adb exec-out screencap -p > screen.png', - { - cwd: path.join(process.cwd(), '.owl', 'android'), - shell: true, - stdio: 'ignore', - } - ); + describe('iOS', () => { + beforeAll(() => { + process.env.OWL_PLATFORM = 'ios'; + process.env.OWL_DEBUG = 'false'; + }); + + it('should take a screenshot', async () => { + await takeScreenshot(); + + expect(commandMock).toHaveBeenCalledWith( + 'xcrun simctl io booted screenshot screen.png', + { + cwd: path.join(process.cwd(), '.owl', 'latest', 'ios'), + shell: false, + stdio: 'ignore', + } + ); + }); + }); + + describe('Android', () => { + beforeAll(() => { + process.env.OWL_PLATFORM = 'android'; + process.env.OWL_DEBUG = 'false'; + }); + + it('should take a screenshot', async () => { + await takeScreenshot(); + + expect(commandMock).toHaveBeenCalledWith( + 'adb exec-out screencap -p > screen.png', + { + cwd: path.join(process.cwd(), '.owl', 'latest', 'android'), + shell: true, + stdio: 'ignore', + } + ); + }); }); }); }); diff --git a/src/take-screenshot.ts b/src/take-screenshot.ts index 0a009b93..9931ed27 100644 --- a/src/take-screenshot.ts +++ b/src/take-screenshot.ts @@ -8,12 +8,14 @@ import { Logger } from './logger'; export const takeScreenshot = async (): Promise => { const platform = process.env.OWL_PLATFORM as Platform; const debug = process.env.OWL_DEBUG === 'true'; + const updateBaseline = process.env.OWL_UPDATE_BASELINE === 'true'; + const stdio = debug ? 'inherit' : 'ignore'; const logger = new Logger(!!debug); const DEFAULT_FILENAME = 'screen.png'; - const DIR_NAME = '.owl'; - const cwd = path.join(process.cwd(), DIR_NAME, platform); + const DIR_NAME = updateBaseline ? 'baseline' : 'latest'; + const cwd = path.join(process.cwd(), '.owl', DIR_NAME, platform); await fs.mkdir(cwd, { recursive: true });