Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,31 @@ owl test --platform ios --update

[github-image]: https://github.com/FormidableLabs/react-native-owl/workflows/Run%20Tests/badge.svg
[github-url]: https://github.com/FormidableLabs/react-native-owl/actions

## Test Suite

### Example

```js
import { takeScreenshot } from 'react-native-owl';

describe('App.tsx', () => {
it('takes a screenshot of the first screen', async () => {
const screen = await takeScreenshot();

expect(screen).toMatchBaseline();
});
});
```

### Methods

#### `takeScreenshot()`

Grabs a screenshot from the simulator and stores it under `latest` screenshots(ie. `./owl/latest/ios/`). If running the tests using the `--update` or `-u` flag, this will store the screenshot under the `baseline` directory. See example above.

### Jest Matchers

#### `.toMatchBaseline()`

This custom matcher will try to find and compare the baseline screenshot by using the path of the _latest_ screenshot (returned by `takeScreenshot()`). You will have to take a screenshot before using and pass the path of that screenshot to the `expect` method.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@
"dependencies": {
"ajv": "^7.0.3",
"execa": "^5.0.0",
"native-image-diff": "^0.1.11",
"pngjs": "^6.0.0",
"yargs": "^16.2.0"
},
"devDependencies": {
"@types/jest": "^26.0.19",
"@types/pngjs": "^3.4.2",
"@types/yargs": "^15.0.12",
"jest": "^26.6.3",
"prettier": "^2.2.1",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
import './matchers';

export { takeScreenshot } from './take-screenshot';
77 changes: 77 additions & 0 deletions src/matchers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import fs from 'fs';
import * as nativeImageDiff from 'native-image-diff';

import { toMatchBaseline } from './matchers';

describe('matchers.ts', () => {
const imageHello1Data = `iVBORw0KGgoAAAANSUhEUgAAACUAAAALCAYAAAD4OERFAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAJaADAAQAAAABAAAACwAAAADN8bJQAAABcElEQVQ4Ec2UvytGURjHr1/lHd5JlF9leMNAiQzKbqCMBt5ikUEWMhkMDGZ/gEHZDMqCsr6JQel9yW8Dg4lBJvL5ck7dTudw3UG+9el5nvM859znnnPujaIoKoMq+Ffqo5uTFB1dMacLuuEixXxNaYRNd26lO/BHcZbnjEEecu4zy82AjnARbuEIOsBqEqdkmLGDAav1luAB7mEBtLYrNdUKd27Cxjq+d1iFTtiALZCG4QYGQLlTGAXJd3zTjB9CM7SB6schpF4Sj76kmnoCu2v9+OemcBc7Z3yZKbAN+5o6Jj+hQiM1uWMDj/U2Ze+Utlu7Jb1A5tP7Om9NnDexvtIz4/tMC4MHscQ1fm0sTuTa3XkLVD8zrretM9RjByGkIommWFJHWIjFiVzbVKh4n8QIVBvWsLMQ0jaJPKheF3wI9uBX+qmpZVarAV12fSnyVyCkdRI9cAm65K/w3Z0inU56Y/1LRBJVUNQODUmKfTUfKJc7FJ+heOgAAAAASUVORK5CYII=`;
const imageHello1Buffer = Buffer.from(imageHello1Data, 'base64');

const imageHello2Data = `iVBORw0KGgoAAAANSUhEUgAAACUAAAALCAYAAAD4OERFAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAJaADAAQAAAABAAAACwAAAADN8bJQAAABsklEQVQ4Ec2UOyhFcRzHj1d5lEEhogyKQqEMyioDxYJBCoMMsngslDvYDQyUYlJsHimURZJQSi6RV15ZxGD0+Hxv///pdDq3bnfhW5/7e/3/5/4fv3Mcx3ESIAX+lWpZzWkcK7pmTiVUw1Uc83uYswZL0ALuwSQT/IXa+dNxgzY3DbkwC06ifpCuMAR3cATlYNWLEzYM2GQUq+dNwDM8wRjo2X7Vk5iEBdiFOWgDV7q+b5iCCliEFZCa4RYaQLVz6AAp6Pr6yR9CIZSAxneBX/kkMjzJZfyQJ3a0qHewp1aHf2kGbGGHjC/TB3bBQYs6od6tgUZa5KYNAmw6uRm4B20kIttTOm6dlvQJaRHPcYqxNTBiYjXjhfGDTBHJA0/hBj/bE3vdMoJ1ULtUwRtEZE/nyyZ89oNYu80x5GEbIZrOKBR4irrCfU9sXW12FYahFdwF4btXJj9IOyT1pqQa5rGDEE3aeSdovBq8CbbBr1ES+hRsgK5QaI4r9ZT3O6WjfDDVLOwevMArqMcyQQrqKZ1SGB5B16xF2lbAdaXaj49jtxqDox3ruEUsSmJQKegNi0u/XtRShUjycDoAAAAASUVORK5CYII=`;
const imageHello2Buffer = Buffer.from(imageHello2Data, 'base64');

const imageHelloDiffData = `iVBORw0KGgoAAAANSUhEUgAAACUAAAALCAYAAAD4OERFAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAJaADAAQAAAABAAAACwAAAADN8bJQAAAA0ElEQVQ4Ee2SsQsBYRiHH0qJGJSSMiib2Xir2WSWxb9hMhrMym5ktJrEIIuwSEmEuoUynN8N19Ht9xlc39P3696v3qf3++D/+RNwoLmBiQUH5ZqI+VUDaQeNLlxzsJXJXUJL0fJUol4Ic39BdQyrE8yVteiLepgOgV6aSl4kVaiIs/JQtAMHTfwogpWCh4T2omDC4aunJMpTOKbhqZz5LBp5U5IoSWI0g54NdgRun1JGsqQGouNen4QuygkRNyLjNZXAWjialJMVbhYLr/6T+xvERzxV3g04QAAAAABJRU5ErkJggg==`;
const imageHelloDiffBuffer = Buffer.from(imageHelloDiffData, 'base64');

const readFileMock = jest.spyOn(fs, 'readFileSync');
const writeFileMock = jest.spyOn(fs, 'writeFileSync');

const diffImagesMock = jest.spyOn(nativeImageDiff, 'diffImages');

beforeAll(() => {
process.env.OWL_PLATFORM = 'ios';
});

afterAll(() => {
delete process.env.OWL_PLATFORM;
});

describe('toMatchBaseline.ts', () => {
beforeEach(() => {
readFileMock.mockReset();
writeFileMock.mockReset();
});

it('should compare two identical images', () => {
readFileMock
.mockReturnValueOnce(imageHello1Buffer)
.mockReturnValueOnce(imageHello1Buffer);

diffImagesMock.mockReturnValueOnce({
image: { data: Buffer.alloc(100), width: 37, height: 11 },
pixels: 0,
} as nativeImageDiff.DiffResult);

const latestPath = 'latest/ios/screen.png';

const result = toMatchBaseline(latestPath);

expect(result.message()).toBe(
'Compared screenshot to match baseline. 0 were different.'
);
expect(result.pass).toBe(true);
expect(writeFileMock).toHaveBeenCalledTimes(1);
});

it('should compare two different images', () => {
readFileMock
.mockReturnValueOnce(imageHello1Buffer)
.mockReturnValueOnce(imageHello2Buffer);

diffImagesMock.mockReturnValueOnce({
image: { data: imageHelloDiffBuffer, width: 37, height: 11 },
pixels: 55,
} as nativeImageDiff.DiffResult);

const latestPath = 'latest/ios/screen.png';

const result = toMatchBaseline(latestPath);

expect(result.message()).toBe(
'Compared screenshot to match baseline. 55 were different.'
);
expect(result.pass).toBe(false);
expect(writeFileMock).toHaveBeenCalledTimes(1);
});
});
});
59 changes: 59 additions & 0 deletions src/matchers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import fs from 'fs';
import path from 'path';
import { PNG } from 'pngjs';
import { diffImages } from 'native-image-diff';

import { Platform } from './cli/types';

declare global {
namespace jest {
interface Matchers<R> {
/** Compares the image passed to the baseline one */
toMatchBaseline: () => CustomMatcherResult;
}
}
}

export const toMatchBaseline = (latestPath: string) => {
const platform = process.env.OWL_PLATFORM as Platform;
const screenshotsDir = path.join(path.dirname(latestPath), '..', '..');
const baselinePath = path.join(
screenshotsDir,
'baseline',
platform,
path.basename(latestPath)
);

const diffPath = path.join(
screenshotsDir,
'diff',
platform,
path.basename(latestPath)
);

const baselineData = fs.readFileSync(baselinePath);
const baselineImage = PNG.sync.read(baselineData);

const latestData = fs.readFileSync(latestPath);
const latestImage = PNG.sync.read(latestData);

const { image: diff, pixels } = diffImages({
image1: baselineImage,
image2: latestImage,
colorThreshold: 0.1,
});

fs.mkdirSync(path.dirname(diffPath), { recursive: true });

const diffPng = { ...diff! } as PNG;
const diffImage = PNG.sync.write(diffPng);
fs.writeFileSync(diffPath, diffImage);

return {
message: () =>
`Compared screenshot to match baseline. ${pixels} were different.`,
pass: pixels === 0,
};
};

expect.extend({ toMatchBaseline });
6 changes: 4 additions & 2 deletions src/take-screenshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from 'path';
import { Platform } from './cli/types';
import { Logger } from './logger';

export const takeScreenshot = async (): Promise<void> => {
export const takeScreenshot = async (): Promise<string> => {
const platform = process.env.OWL_PLATFORM as Platform;
const debug = process.env.OWL_DEBUG === 'true';
const updateBaseline = process.env.OWL_UPDATE_BASELINE === 'true';
Expand All @@ -31,5 +31,7 @@ export const takeScreenshot = async (): Promise<void> => {
shell: platform === 'android',
});

logger.info(`[OWL] Screenshot saved to ${cwd}/${DEFAULT_FILENAME}.`);
const screenshotPath = `${cwd}/${DEFAULT_FILENAME}`;
logger.info(`[OWL] Screenshot saved to ${screenshotPath}.`);
return screenshotPath;
};
21 changes: 20 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,13 @@
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==

"@types/pngjs@^3.4.2":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.4.2.tgz#8dc49b45fbcf18a5873179e3664f049388e39ecf"
integrity sha512-LJVPDraJ5YFEnMHnzxTN4psdWz1M61MtaAAWPn3qnDk5fvs7BAmmQ9pd3KPlrdrvozMyne4ktanD4pg0L7x1Pw==
dependencies:
"@types/node" "*"

"@types/prettier@^2.0.0":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00"
Expand Down Expand Up @@ -2568,6 +2575,13 @@ nanomatch@^1.2.9:
snapdragon "^0.8.1"
to-regex "^3.0.1"

native-image-diff@^0.1.11:
version "0.1.11"
resolved "https://registry.yarnpkg.com/native-image-diff/-/native-image-diff-0.1.11.tgz#978d7107fe2f37f2d5417dd9510e6ea2c462d6dd"
integrity sha512-/81SQYlMj8BjC3I0jko8CHwNDl6BAVAOXGA03ZQdw8LGc7x2xbyQ5qDfC86JRTb45DrZa1U4LjcgIJ8cVkvqNg==
dependencies:
request "^2.88.0"

natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
Expand Down Expand Up @@ -2793,6 +2807,11 @@ pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"

pngjs@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821"
integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==

posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
Expand Down Expand Up @@ -2912,7 +2931,7 @@ request-promise-native@^1.0.8:
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"

request@^2.88.2:
request@^2.88.0, request@^2.88.2:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
Expand Down