Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

e2e: add FlashList tests #216

Merged
merged 13 commits into from
Mar 25, 2022
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,7 @@ fixture/ios/**/xcshareddata/WorkspaceSettings.xcsettings
# Android

fixture/android/*.hprof

# e2e

fixture/e2e/diff
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ public class DetoxTest {
@Test
public void runDetoxTests() {
DetoxConfig detoxConfig = new DetoxConfig();
detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60;
detoxConfig.idlePolicyConfig.masterTimeoutSec = 120;
detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 90;
detoxConfig.rnContextLoadTimeoutSec = 180;

Detox.runTests(mActivityRule, detoxConfig);
Expand Down
1 change: 1 addition & 0 deletions fixture/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme"
android:installLocation="preferExternal"
fortmarek marked this conversation as resolved.
Show resolved Hide resolved
android:networkSecurityConfig="@xml/network_security_config">
<activity
android:name=".MainActivity"
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 8 additions & 7 deletions fixture/e2e/config.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
{
"maxWorkers": 1,
"testEnvironment": "./environment",
"testRunner": "jest-circus/runner",
"testTimeout": 120000,
"testRegex": "\\.e2e\\.js$",
"reporters": ["detox/runners/jest/streamlineReporter"],
"verbose": true
"maxWorkers": 1,
"testEnvironment": "./environment",
"testRunner": "jest-circus/runner",
"testTimeout": 120000,
"testRegex": "\\.e2e\\.js$",
"reporters": ["detox/runners/jest/streamlineReporter"],
"verbose": true,
"preset": "react-native"
}
13 changes: 0 additions & 13 deletions fixture/e2e/firstTest.e2e.js

This file was deleted.

56 changes: 56 additions & 0 deletions fixture/e2e/flashList.test.e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as path from "path";
import * as fs from "fs";
import { Platform } from "react-native";
import {
wipeArtifactsLocation,
saveReference,
reference,
} from "../src/Detox/SnapshotLocation";

import {
assertSnapshotsEqual,
assertSnapshot,
} from "../src/Detox/SnapshotAsserts";

describe("FlashList", () => {
const flashListReferenceTestName = "Twitter with FlashList looks the same";

beforeAll(async () => {
await device.launchApp({ newInstance: true });
wipeArtifactsLocation("diffs");
});

beforeEach(async () => {
await device.reloadReactNative();
});

it("Twitter with FlashList looks the same", async () => {
await element(by.id("Twitter Timeline")).tap();

const testRunScreenshotPath = await element(
by.id("FlashList")
).takeScreenshot(flashListReferenceTestName);

assertSnapshot(testRunScreenshotPath, flashListReferenceTestName);
});

it("Twitter with FlatList looks the same as with FlashList", async () => {
const testName = "Twitter with FlatList looks the same as with FlashList";

await element(by.id("Twitter FlatList Timeline")).tap();

const testRunScreenshotPath = await element(
by.id("FlatList")
).takeScreenshot(testName);

// Assert that FlatList reference is the same
assertSnapshot(testRunScreenshotPath, testName);

// Assert that FlatList reference is the same as with FlashList
assertSnapshotsEqual(
reference(flashListReferenceTestName),
reference(testName),
testName
);
});
});
10 changes: 5 additions & 5 deletions fixture/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,9 @@ PODS:
- React-Core
- SDWebImage (~> 5.11.1)
- SDWebImageWebPCoder (~> 0.8.4)
- RNFlashList (0.3.3):
- RNFlashList (0.4.0):
- React-Core
- RNFlashList/Tests (0.3.3):
- RNFlashList/Tests (0.4.0):
- React-Core
- RNGestureHandler (2.3.2):
- React-Core
Expand Down Expand Up @@ -582,11 +582,11 @@ SPEC CHECKSUMS:
Flipper-RSocket: d9d9ade67cbecf6ac10730304bf5607266dd2541
FlipperKit: d8d346844eca5d9120c17d441a2f38596e8ed2b9
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 85ecdd10ee8d8ec362ef519a6a45ff9aa27b2e85
glog: 5337263514dd6f09803962437687240c5dc39aa4
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
libwebp: 98a37e597e40bfdb4c911fc98f2c53d0b12d05fc
OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b
RCT-Folly: 803a9cfd78114b2ec0f140cfa6fa2a6bafb2d685
RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9
RCTRequired: 0aa6c1c27e1d65920df35ceea5341a5fe76bdb79
RCTTypeSafety: d76a59d00632891e11ed7522dba3fd1a995e573a
React: ab8c09da2e7704f4b3ebad4baa6cfdfcc852dcb5
Expand Down Expand Up @@ -614,7 +614,7 @@ SPEC CHECKSUMS:
ReactCommon: 07d0c460b9ba9af3eaf1b8f5abe7daaad28c9c4e
ReactNativePerformanceListsProfiler: 3f9453c24a90c4f77db568d984c48f380968fa0d
RNFastImage: 1f2cab428712a4baaf78d6169eaec7f622556dd7
RNFlashList: 37f03f7d76f8f2058a311a2c1f5d5abe565f0e85
RNFlashList: eff470502cb0819757fa91cf771fb17aaf636787
RNGestureHandler: 6e757e487a4834e7280e98e9bac66d2d9c575e9c
RNReanimated: 32c91e28f5780937b8efc07ddde1bab8d373fe0b
RNScreens: 40a2cb40a02a609938137a1e0acfbf8fc9eebf19
Expand Down
13 changes: 9 additions & 4 deletions fixture/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,24 @@
"react-native-reanimated": "^2.4.1",
"react-native-safe-area-context": "^3.3.2",
"react-native-screens": "^3.13.1",
"recyclerlistview": "3.1.0-alpha.9",
"detox": "^19.5.7"
"recyclerlistview": "3.1.0-alpha.9"
},
"devDependencies": {
"babel-jest": "^27.5.1",
"babel-plugin-module-resolver": "^4.1.0",
"glob": "^7.1.6",
"jest": "^27.5.1",
"metro-config": "^0.69.1",
"metro-react-native-babel-preset": "^0.69.1",
"patch-package": "^6.4.7",
"postinstall-postinstall": "^2.1.0",
"typescript": "^4.6.2",
"jest": "^27.5.1",
"babel-jest": "^27.5.1"
"@types/pixelmatch": "^5.2.4",
"@types/pngjs": "^6.0.1",
"detox": "^19.5.7",
"pixelmatch": "^5.2.1",
"pngjs": "^6.0.0",
"@types/jest": "^27.4.1"
},
"jest": {
"preset": "react-native"
Expand Down
1 change: 1 addition & 0 deletions fixture/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const App = () => {
<Stack.Screen
name={NavigationKeys.TWITTER_FLAT_LIST}
component={TwitterFlatList}
options={{ title: "Twitter" }}
/>
</Stack.Navigator>
</NavigationContainer>
Expand Down
24 changes: 24 additions & 0 deletions fixture/src/Detox/PixelDifference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as fs from "fs";

import pixelmatch from "pixelmatch";
import { PNG } from "pngjs";

export const pixelDifference = (
referencePath: string,
toMatchPath: string
): PNG | null => {
const reference = PNG.sync.read(fs.readFileSync(referencePath));
const toMatch = PNG.sync.read(fs.readFileSync(toMatchPath));
const { width, height } = reference;
const diff = new PNG({ width, height });

const numDiffPixels = pixelmatch(
reference.data,
toMatch.data,
diff.data,
width,
height
);

return numDiffPixels > 0 ? diff : null;
};
56 changes: 56 additions & 0 deletions fixture/src/Detox/SnapshotAsserts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { saveDiff, reference, saveReference } from "./SnapshotLocation";
import { pixelDifference } from "./PixelDifference";

export const assertSnapshot = (snapshotPath: string, testName: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd probably change the snapshotPath to element and take the snapshot inside the file. I don't see a need to enforce users of the API to do so or?

const referencePath = reference(testName);

if (referencePath) {
const diffPNG = pixelDifference(snapshotPath, referencePath);

if (diffPNG !== null) {
const diffPath = saveDiff(diffPNG, `${testName}_diff`);

throw Error(
`There is difference between reference screenshot and test run screenshot.
See diff: ${diffPath}`
);
}
} else {
saveReference(snapshotPath, testName);

throw Error(
`There is no reference screenshot present.
New reference screenshot was just created for test name "${testName}".
Please run the test again.`
);
}
};

export const assertSnapshotsEqual = (
firstPath: string | null,
secondPath: string | null,
testName: string
) => {
if (!firstPath) {
throw new Error(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throwing an error here, because Jest's fail function is not available - jestjs/jest#11698

`Invalid path: ${firstPath}. Please make sure that you have a screenshot before running this assertion.`
);
}

if (!secondPath) {
throw new Error(
`Invalid path: ${secondPath}. Please make sure that you have a screenshot before running this assertion.`
);
}

const diffPNG = pixelDifference(firstPath, secondPath);

if (diffPNG !== null) {
const diffPath = saveDiff(diffPNG, `${testName}_diff.png`);

throw Error(
`There is difference between reference screenshot and test run screenshot.
See diff: ${diffPath}`
);
}
};
59 changes: 59 additions & 0 deletions fixture/src/Detox/SnapshotLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as path from "path";
import * as fs from "fs";

import detox from "detox";
import { PNG } from "pngjs";

const ROOT_PATH = path.resolve(__dirname, "..");

const artifactsLocation = (testCaseName: string): string => {
const platform = detox.device.getPlatform();
const location = path.resolve(
ROOT_PATH,
`../e2e/artifacts/${platform}`,
testCaseName
);

return location;
};

export const ensureArtifactsLocation = (name: string): string => {
const location = artifactsLocation(name);

if (!fs.existsSync(location)) {
fs.mkdirSync(location, { recursive: true });
}

return location;
};

export const wipeArtifactsLocation = (name: string) => {
const location = artifactsLocation(name);

if (fs.existsSync(location)) {
fs.rmdirSync(location, { recursive: true });
}
};

export const saveDiff = (diff: PNG, testName: string): string => {
const diffsLocation = ensureArtifactsLocation("diff");
const diffPath = path.resolve(diffsLocation, testName);
fs.writeFileSync(diffPath, PNG.sync.write(diff));

return diffPath;
};

export const saveReference = (referencePath: string, testName: string) => {
const testArtifactsLocation = ensureArtifactsLocation(testName);
const referenceName = path.resolve(testArtifactsLocation, `${testName}.png`);

fs.renameSync(referencePath, referenceName);

console.log(`Reference screenshot for test name "${testName}" was created`);
};

export const reference = (testName: string): string | null => {
const testArtifactsLocation = ensureArtifactsLocation(testName);
const referenceName = path.resolve(testArtifactsLocation, `${testName}.png`);
return fs.existsSync(referenceName) ? referenceName : null;
};
1 change: 1 addition & 0 deletions fixture/src/ExamplesScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const ExamplesScreen = () => {
onPress={() => {
navigate(item.destination);
}}
testID={item.title}
>
<Text style={styles.rowTitle}>{item.title}</Text>
</TouchableOpacity>
Expand Down
1 change: 1 addition & 0 deletions fixture/src/Twitter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const Twitter = () => {
return (
<FlashListPerformanceView listName="Twitter">
<FlashList
testID="FlashList"
keyExtractor={(item) => {
return item.id;
}}
Expand Down
30 changes: 16 additions & 14 deletions fixture/src/TwitterFlatList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { FlatList } from "react-native";
import { FlatList, View } from "react-native";
import { FlatListPerformanceView } from "@shopify/react-native-performance-lists-profiler";

import { tweets } from "./data/tweets";
Expand All @@ -9,19 +9,21 @@ import { Header, Footer, Divider } from "./Twitter";
const TwitterFlatList = () => {
return (
<FlatListPerformanceView listName="TwitterFlatList">
<FlatList
keyExtractor={(item) => {
return item.id;
}}
renderItem={({ item }) => {
return <TweetCell tweet={item} />;
}}
ListHeaderComponent={Header}
ListHeaderComponentStyle={{ backgroundColor: "#ccc" }}
ListFooterComponent={Footer}
ItemSeparatorComponent={Divider}
data={tweets}
/>
<View testID="FlatList">
<FlatList
keyExtractor={(item) => {
return item.id;
}}
renderItem={({ item }) => {
return <TweetCell tweet={item} />;
}}
ListHeaderComponent={Header}
ListHeaderComponentStyle={{ backgroundColor: "#ccc" }}
ListFooterComponent={Footer}
ItemSeparatorComponent={Divider}
data={tweets}
/>
</View>
</FlatListPerformanceView>
);
};
Expand Down
Loading