From 3c5e28a0aa99bb6bb97abf42dcac49bb02b71c12 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Thu, 7 Jan 2021 10:57:10 +0000 Subject: [PATCH 1/6] feat: Add cli option for updating baseline images --- src/cli/build.ts | 4 ++-- src/cli/index.ts | 19 ++++++++++++++++--- src/cli/run.ts | 15 ++++++++++++--- src/cli/types.ts | 8 +++++++- src/take-screenshot.ts | 6 ++++-- 5 files changed, 41 insertions(+), 11 deletions(-) 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.ts b/src/cli/run.ts index aef28929..8852be8d 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()}.`); @@ -71,7 +79,8 @@ export const runHandler = async (args: BuildRunOptions) => { stdio: 'inherit', env: { OWL_PLATFORM: args.platform, - OWL_DEBUG: String(!!config.debug), + OWL_DEBUG: String(config.debug), + OWL_UPDATE_BASELINE: String(args.update), }, }); 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.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 }); From 5efddc6a5b25442d2041f0fa44b4c9e8847d8411 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Thu, 7 Jan 2021 13:06:05 +0000 Subject: [PATCH 2/6] chore: Remove latest images during each run --- src/cli/run.ts | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/cli/run.ts b/src/cli/run.ts index 8852be8d..d1c5adfd 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -1,7 +1,9 @@ +// @ts-nocheck import path from 'path'; import execa from 'execa'; +import { promises as fs } from 'fs'; -import { CliRunOptions, Config } from './types'; +import { CliRunOptions, Config, Platform } from './types'; import { Logger } from '../logger'; import { getConfig } from './config'; @@ -12,6 +14,15 @@ export const getIOSBundleIdentifier = (appPath: string): string => { return stdout; }; +export const cleanLatestImages = async ( + platform: Platform, + logger: Logger +): Promise => { + const dirPath = path.join(process.cwd(), '.owl', 'baseline', platform); + logger.info(`[OWL] Removing latest images at ${dirPath}.`); + await fs.rmdir(dirPath, { recursive: true }); +}; + export const runIOS = async (config: Config, logger: Logger) => { const stdio = config.debug ? 'inherit' : 'ignore'; const DEFAULT_BINARY_DIR = '/ios/build/Build/Products/Debug-iphonesimulator'; @@ -69,9 +80,11 @@ export const runHandler = async (args: CliRunOptions) => { args.update ? '(Update mode) Updating baseline images' : '(Tests mode) Will compare latest images with the baseline' - }` + }.` ); + await cleanLatestImages(args.platform, logger); + logger.info(`[OWL] Will use the jest config localed at ${jestConfigPath}.`); logger.info(`[OWL] Will set the jest root to ${process.cwd()}.`); @@ -85,4 +98,9 @@ export const runHandler = async (args: CliRunOptions) => { }); logger.print(`[OWL] Tests completed on ${args.platform}.`); + if (args.update) { + logger.print( + `[OWL] All baseline images for ${args.platform} have been updated successfully.` + ); + } }; From ef7ed9b2fc23f9a16c8139bcc097b63308c55274 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Thu, 7 Jan 2021 13:36:17 +0000 Subject: [PATCH 3/6] chore: Fix & write more tests --- src/cli/build.test.ts | 4 +- src/cli/run.test.ts | 35 ++++++++++-- src/cli/run.ts | 4 +- src/take-screenshot.test.ts | 108 ++++++++++++++++++++++++++---------- 4 files changed, 115 insertions(+), 36 deletions(-) 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/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 d1c5adfd..d9384a5d 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -92,8 +92,8 @@ export const runHandler = async (args: CliRunOptions) => { stdio: 'inherit', env: { OWL_PLATFORM: args.platform, - OWL_DEBUG: String(config.debug), - OWL_UPDATE_BASELINE: String(args.update), + OWL_DEBUG: String(!!config.debug), + OWL_UPDATE_BASELINE: String(!!args.update), }, }); 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', + } + ); + }); }); }); }); From 921e417afaa4ddecf6949a5055101ac8148d4043 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Thu, 7 Jan 2021 14:05:24 +0000 Subject: [PATCH 4/6] chore: Update README.md --- README.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) 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 From 4e6569ec1e27097429b9abb5a31e1d6a3ca2bc89 Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Thu, 7 Jan 2021 14:07:04 +0000 Subject: [PATCH 5/6] chore: Clean Up --- src/cli/run.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/run.ts b/src/cli/run.ts index d9384a5d..92020f0b 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -1,4 +1,3 @@ -// @ts-nocheck import path from 'path'; import execa from 'execa'; import { promises as fs } from 'fs'; From 2a5f596debdb09f197725004f0776c9e88a4899e Mon Sep 17 00:00:00 2001 From: Emmanouil Konstantinidis Date: Thu, 7 Jan 2021 14:49:54 +0000 Subject: [PATCH 6/6] chore: Remove cleanImages function --- src/cli/run.ts | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/cli/run.ts b/src/cli/run.ts index 92020f0b..31514e5a 100644 --- a/src/cli/run.ts +++ b/src/cli/run.ts @@ -1,8 +1,7 @@ import path from 'path'; import execa from 'execa'; -import { promises as fs } from 'fs'; -import { CliRunOptions, Config, Platform } from './types'; +import { CliRunOptions, Config } from './types'; import { Logger } from '../logger'; import { getConfig } from './config'; @@ -13,15 +12,6 @@ export const getIOSBundleIdentifier = (appPath: string): string => { return stdout; }; -export const cleanLatestImages = async ( - platform: Platform, - logger: Logger -): Promise => { - const dirPath = path.join(process.cwd(), '.owl', 'baseline', platform); - logger.info(`[OWL] Removing latest images at ${dirPath}.`); - await fs.rmdir(dirPath, { recursive: true }); -}; - export const runIOS = async (config: Config, logger: Logger) => { const stdio = config.debug ? 'inherit' : 'ignore'; const DEFAULT_BINARY_DIR = '/ios/build/Build/Products/Debug-iphonesimulator'; @@ -82,8 +72,6 @@ export const runHandler = async (args: CliRunOptions) => { }.` ); - await cleanLatestImages(args.platform, logger); - logger.info(`[OWL] Will use the jest config localed at ${jestConfigPath}.`); logger.info(`[OWL] Will set the jest root to ${process.cwd()}.`);