Unverified Commit cad3e74e authored by Hanwen Cheng's avatar Hanwen Cheng Committed by GitHub
Browse files

test: add e2e signing and ethereum tests (#549)

* rebase to master

* fix rebase deletion

* remove console logs and ignore useless warning

* use eslint unused vars

* add ethereum signing test

* re-arrange e2e and unit configs, use common jest config

* update .gitignore

* fix configs

* fix android bug and upgrade detox

* rename unit test scripts

* update docs with yarn unit

* remove debug logs

* update test config
parent e1785af7
Pipeline #81759 failed with stages
in 3 minutes and 47 seconds
...@@ -3,7 +3,6 @@ const commonRules = { ...@@ -3,7 +3,6 @@ const commonRules = {
"comma-dangle": ["error", "never"], "comma-dangle": ["error", "never"],
"object-curly-spacing": ["error", "always"], "object-curly-spacing": ["error", "always"],
"quotes": ["error", "single", { "avoidEscape": true }], "quotes": ["error", "single", { "avoidEscape": true }],
"no-unused-vars": ["error", { "args": "none" }],
"react-native/no-inline-styles": "off", "react-native/no-inline-styles": "off",
"sort-keys": ["error", "asc", {"caseSensitive": true, "natural": false, "minKeys": 2}], "sort-keys": ["error", "asc", {"caseSensitive": true, "natural": false, "minKeys": 2}],
"import/order": ["error", { "import/order": ["error", {
...@@ -22,7 +21,7 @@ module.exports = { ...@@ -22,7 +21,7 @@ module.exports = {
globals: { inTest: "writable" }, globals: { inTest: "writable" },
overrides: [ overrides: [
{ {
files: ["e2e/*.spec.js", "e2e/init.js", "e2e/e2eUtils.js"], files: ["e2e/*.spec.js", "e2e/init.js", "e2e/utils.js"],
rules: { rules: {
"no-undef": "off" "no-undef": "off"
} }
...@@ -83,5 +82,8 @@ module.exports = { ...@@ -83,5 +82,8 @@ module.exports = {
"version": "16.9.0", // React version, default to the latest React stable release "version": "16.9.0", // React version, default to the latest React stable release
}, },
}, },
rules: commonRules rules: {
...commonRules,
"no-unused-vars": ["error", { "args": "none" }],
}
}; };
...@@ -5,7 +5,7 @@ package-lock.json ...@@ -5,7 +5,7 @@ package-lock.json
dist/ dist/
# jest cache # jest cache
.jest/cache test/.jest/
# NDK # NDK
NDK/ NDK/
...@@ -52,6 +52,7 @@ build/ ...@@ -52,6 +52,7 @@ build/
local.properties local.properties
*.iml *.iml
gen/ gen/
*.hprof
# node.js # node.js
# #
......
...@@ -11,7 +11,7 @@ install: ...@@ -11,7 +11,7 @@ install:
script: script:
- yarn run lint - yarn run lint
- yarn run test - yarn run unit
# TODO complete following part to Integrate E2E test # TODO complete following part to Integrate E2E test
......
...@@ -119,12 +119,12 @@ Corresponding data: ...@@ -119,12 +119,12 @@ Corresponding data:
#### Unit Test #### Unit Test
Run `yarn test` for all the units test. Run `yarn unit` for all the units test.
If debugging is needed: If debugging is needed:
1. Insert `debugger;` in the code where you think it fails. 1. Insert `debugger;` in the code where you think it fails.
2. Run `yarn test:debug` 2. Run `yarn unit:debug`
3. Open a new tab in Chrome and go to `chrome://inspect` 3. Open a new tab in Chrome and go to `chrome://inspect`
4. Click the `inspect` button of target under `Remote Target` 4. Click the `inspect` button of target under `Remote Target`
5. Back to the terminal, choose one of the node watch commands to run the tests again. 5. Back to the terminal, choose one of the node watch commands to run the tests again.
......
...@@ -23,7 +23,7 @@ module.exports = { ...@@ -23,7 +23,7 @@ module.exports = {
alias: { alias: {
components: './src/components', components: './src/components',
constants: './src/constants', constants: './src/constants',
e2e: './e2e', e2e: './test/e2e',
res: './res', res: './res',
screens: './src/screens', screens: './src/screens',
stores: './src/stores', stores: './src/stores',
......
{
"setupFilesAfterEnv": ["./init.ts"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
"verbose": true
}
...@@ -1491,7 +1491,7 @@ ...@@ -1491,7 +1491,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "export NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh\n"; shellScript = "export NODE_BINARY=node\nexport NODE_OPTIONS=\"--max_old_space_size=8192\"\n../node_modules/react-native/scripts/react-native-xcode.sh\n";
}; };
/* End PBXShellScriptBuildPhase section */ /* End PBXShellScriptBuildPhase section */
......
This diff is collapsed.
...@@ -20,8 +20,8 @@ ...@@ -20,8 +20,8 @@
"lint:fix": "npx eslint . --ext .js,.jsx,.ts,.tsx --fix --ignore-path .gitignore", "lint:fix": "npx eslint . --ext .js,.jsx,.ts,.tsx --fix --ignore-path .gitignore",
"postinstall": "npx jetify && chmod +x ./scripts/fix-rn-camera-path.sh && ./scripts/fix-rn-camera-path.sh ./node_modules/react-native-camera/ios/RNCamera.xcodeproj/project.pbxproj", "postinstall": "npx jetify && chmod +x ./scripts/fix-rn-camera-path.sh && ./scripts/fix-rn-camera-path.sh ./node_modules/react-native-camera/ios/RNCamera.xcodeproj/project.pbxproj",
"start": "NODE_OPTIONS=--max_old_space_size=8192 react-native start", "start": "NODE_OPTIONS=--max_old_space_size=8192 react-native start",
"test": "jest", "unit": "jest --config ./test/unit/jest.config.js",
"test:debug": "node --inspect node_modules/.bin/jest --watch --runInBand", "unit:debug": "node --inspect node_modules/.bin/jest --watch --runInBand",
"test-rust": "cd ./rust/signer && cargo test && cd ../..", "test-rust": "cd ./rust/signer && cargo test && cd ../..",
"build-e2e:android": "detox build -c android.emu.debug -l info", "build-e2e:android": "detox build -c android.emu.debug -l info",
"test-e2e:android": "detox test -c android.emu.debug -l info --noStackTrace", "test-e2e:android": "detox test -c android.emu.debug -l info --noStackTrace",
...@@ -83,13 +83,13 @@ ...@@ -83,13 +83,13 @@
"@types/jest": "^25.1.3", "@types/jest": "^25.1.3",
"@types/react": "^16.9.19", "@types/react": "^16.9.19",
"@types/react-native": "^0.61.10", "@types/react-native": "^0.61.10",
"@typescript-eslint/eslint-plugin": "^2.15.0", "@typescript-eslint/eslint-plugin": "^2.20.0",
"@typescript-eslint/parser": "^2.15.0", "@typescript-eslint/parser": "^2.15.0",
"babel-eslint": "10.0.3", "babel-eslint": "10.0.3",
"babel-jest": "^25.1.0", "babel-jest": "^25.1.0",
"babel-plugin-module-resolver": "^4.0.0", "babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-rewrite-require": "^1.14.5", "babel-plugin-rewrite-require": "^1.14.5",
"detox": "^14.7.0", "detox": "^15.4.2",
"eslint": "^6.8.0", "eslint": "^6.8.0",
"eslint-config-prettier": "^6.2.0", "eslint-config-prettier": "^6.2.0",
"eslint-import-resolver-typescript": "^2.0.0", "eslint-import-resolver-typescript": "^2.0.0",
...@@ -124,7 +124,7 @@ ...@@ -124,7 +124,7 @@
}, },
"ios.sim.release": { "ios.sim.release": {
"binaryPath": "ios/build/NativeSigner/Build/Products/Release-iphonesimulator/NativeSigner.app", "binaryPath": "ios/build/NativeSigner/Build/Products/Release-iphonesimulator/NativeSigner.app",
"build": "xcodebuild -project ios/NativeSigner.xcodeproj -scheme NativeSigner -configuration Release -sdk iphonesimulator -derivedDataPath ios/build/NativeSigner -UseModernBuildSystem=NO | xcpretty -t && exit ${PIPESTATUS[0]}", "build": "xcodebuild -project ios/NativeSigner.xcodeproj -scheme NativeSigner -configuration Release -sdk iphonesimulator -derivedDataPath ios/build/NativeSigner -UseModernBuildSystem=YES | xcpretty -t && exit ${PIPESTATUS[0]}",
"type": "ios.simulator", "type": "ios.simulator",
"device": { "device": {
"type": "iPhone SE" "type": "iPhone SE"
...@@ -147,7 +147,7 @@ ...@@ -147,7 +147,7 @@
} }
} }
}, },
"runner-config": "e2e/config.json", "runner-config": "test/e2e/jest.config.js",
"test-runner": "jest" "test-runner": "jest"
} }
} }
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
import '../shim'; import '../shim';
import * as React from 'react'; import * as React from 'react';
import { Platform, StatusBar, View, YellowBox } from 'react-native'; import { StatusBar, View, YellowBox } from 'react-native';
import { import {
createAppContainer, createAppContainer,
createSwitchNavigator, createSwitchNavigator,
...@@ -67,38 +67,18 @@ import TermsAndConditions from 'screens/TermsAndConditions'; ...@@ -67,38 +67,18 @@ import TermsAndConditions from 'screens/TermsAndConditions';
import TxDetails from 'screens/TxDetails'; import TxDetails from 'screens/TxDetails';
import LegacyNetworkChooser from 'screens/LegacyNetworkChooser'; import LegacyNetworkChooser from 'screens/LegacyNetworkChooser';
import testIDs from 'e2e/testIDs'; import testIDs from 'e2e/testIDs';
import { AppProps, getLaunchArgs } from 'e2e/injections';
const getLaunchArgs = (props: Props): void => { export default class App extends React.Component<AppProps> {
if (Platform.OS === 'ios') { constructor(props: AppProps) {
if (
Array.isArray(props.launchArgs) &&
props.launchArgs.includes('-detoxServer')
) {
global.inTest = true;
return;
}
} else {
if (props.launchArgs && props.launchArgs.hasOwnProperty('detoxServer')) {
global.inTest = true;
return;
}
}
global.inTest = false;
};
interface Props {
launchArgs?: Array<string> | object;
}
export default class App<Props> extends React.Component<Props> {
constructor(props: Props) {
super(props); super(props);
getLaunchArgs(props); getLaunchArgs(props);
if (__DEV__) { if (__DEV__) {
YellowBox.ignoreWarnings([ YellowBox.ignoreWarnings([
'Warning: componentWillReceiveProps', 'Warning: componentWillReceiveProps',
'Warning: componentWillMount', 'Warning: componentWillMount',
'Warning: componentWillUpdate' 'Warning: componentWillUpdate',
'Warning: Sending `onAnimatedValueUpdate`'
]); ]);
} }
} }
......
...@@ -50,7 +50,7 @@ import { ...@@ -50,7 +50,7 @@ import {
NetworkParams, NetworkParams,
SubstrateNetworkParams, SubstrateNetworkParams,
isSubstrateNetworkParams, isSubstrateNetworkParams,
EthereumNetworkParams isEthereumNetworkParams
} from 'types/networkSpecsTypes'; } from 'types/networkSpecsTypes';
import { NavigationAccountProps } from 'types/props'; import { NavigationAccountProps } from 'types/props';
...@@ -258,21 +258,24 @@ function AccountNetworkChooser({ ...@@ -258,21 +258,24 @@ function AccountNetworkChooser({
<View style={styles.body}> <View style={styles.body}>
{renderScreenHeading()} {renderScreenHeading()}
<ScrollView testID={testIDs.AccountNetworkChooser.chooserScreen}> <ScrollView testID={testIDs.AccountNetworkChooser.chooserScreen}>
{networkList.map(([networkKey, networkParams]) => ( {networkList.map(([networkKey, networkParams]) => {
<NetworkCard const networkIndexSuffix = isEthereumNetworkParams(networkParams)
key={networkKey} ? networkParams.ethereumChainId
testID={ : networkParams.pathId;
testIDs.AccountNetworkChooser.networkButton + return (
(networkParams as SubstrateNetworkParams).pathId || <NetworkCard
(networkParams as EthereumNetworkParams).ethereumChainId key={networkKey}
} testID={
networkKey={networkKey} testIDs.AccountNetworkChooser.networkButton + networkIndexSuffix
onPress={(): Promise<void> => }
onNetworkChosen(networkKey, networkParams) networkKey={networkKey}
} onPress={(): Promise<void> =>
title={networkParams.title} onNetworkChosen(networkKey, networkParams)
/> }
))} title={networkParams.title}
/>
);
})}
{renderAddButton()} {renderAddButton()}
</ScrollView> </ScrollView>
</View> </View>
......
...@@ -20,6 +20,7 @@ import React from 'react'; ...@@ -20,6 +20,7 @@ import React from 'react';
import { ScrollView, StyleSheet, Text } from 'react-native'; import { ScrollView, StyleSheet, Text } from 'react-native';
import { Subscribe } from 'unstated'; import { Subscribe } from 'unstated';
import testIDs from 'e2e/testIDs';
import { NETWORK_LIST } from 'constants/networkSpecs'; import { NETWORK_LIST } from 'constants/networkSpecs';
import { FoundAccount } from 'types/identityTypes'; import { FoundAccount } from 'types/identityTypes';
import { import {
...@@ -150,6 +151,7 @@ export class MessageDetailsView extends React.PureComponent<Props> { ...@@ -150,6 +151,7 @@ export class MessageDetailsView extends React.PureComponent<Props> {
<ScrollView <ScrollView
contentContainerStyle={styles.bodyContent} contentContainerStyle={styles.bodyContent}
style={styles.body} style={styles.body}
testID={testIDs.MessageDetails.scrollScreen}
> >
<Background /> <Background />
<Text style={styles.topTitle}>Sign Message</Text> <Text style={styles.topTitle}>Sign Message</Text>
...@@ -169,6 +171,7 @@ export class MessageDetailsView extends React.PureComponent<Props> { ...@@ -169,6 +171,7 @@ export class MessageDetailsView extends React.PureComponent<Props> {
/> />
<Button <Button
buttonStyles={styles.signButton} buttonStyles={styles.signButton}
testID={testIDs.MessageDetails.signButton}
title="Sign Message" title="Sign Message"
onPress={(): void => { onPress={(): void => {
isHash ? alertMultipart(onNext) : onNext(); isHash ? alertMultipart(onNext) : onNext();
......
...@@ -19,7 +19,7 @@ import { Alert, Button, StyleSheet, Text, View } from 'react-native'; ...@@ -19,7 +19,7 @@ import { Alert, Button, StyleSheet, Text, View } from 'react-native';
import { RNCamera } from 'react-native-camera'; import { RNCamera } from 'react-native-camera';
import { Subscribe } from 'unstated'; import { Subscribe } from 'unstated';
import { createMockSignRequest } from 'e2e/mock'; import { onMockBarCodeRead } from 'e2e/injections';
import { NavigationProps, NavigationScannerProps } from 'types/props'; import { NavigationProps, NavigationScannerProps } from 'types/props';
import colors from 'styles/colors'; import colors from 'styles/colors';
import fonts from 'styles/fonts'; import fonts from 'styles/fonts';
...@@ -148,8 +148,8 @@ function QrScannerView({ ...@@ -148,8 +148,8 @@ function QrScannerView({
scannerStore, scannerStore,
...props ...props
}: ViewProps): React.ReactElement { }: ViewProps): React.ReactElement {
if (global.inTest) { if (global.inTest && global.scanRequest !== undefined) {
props.onBarCodeRead(createMockSignRequest()); onMockBarCodeRead(global.scanRequest, props.onBarCodeRead);
} }
useEffect((): (() => void) => { useEffect((): (() => void) => {
......
...@@ -15,8 +15,9 @@ ...@@ -15,8 +15,9 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { ScrollView, StyleSheet, Text } from 'react-native'; import { ScrollView, StyleSheet, Text, View } from 'react-native';
import testIDs from 'e2e/testIDs';
import { NavigationScannerProps } from 'types/props'; import { NavigationScannerProps } from 'types/props';
import colors from 'styles/colors'; import colors from 'styles/colors';
import QrView from 'components/QrView'; import QrView from 'components/QrView';
...@@ -42,7 +43,9 @@ function SignedMessage({ ...@@ -42,7 +43,9 @@ function SignedMessage({
return ( return (
<ScrollView style={styles.body}> <ScrollView style={styles.body}>
<Text style={styles.topTitle}>Signed Message</Text> <Text style={styles.topTitle}>Signed Message</Text>
<QrView data={data} /> <View testID={testIDs.SignedMessage.qrView}>
<QrView data={data} />
</View>
<MessageDetailsCard <MessageDetailsCard
isHash={isHash} isHash={isHash}
message={message ?? ''} message={message ?? ''}
......
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable @typescript-eslint/no-unused-vars */
import { ScanTestRequest } from 'e2e/mockScanRequests';
export {}; export {};
/*~ If the app has properties exposed on a global variable, /*~ If the app has properties exposed on a global variable,
...@@ -9,6 +27,7 @@ declare global { ...@@ -9,6 +27,7 @@ declare global {
namespace NodeJS { namespace NodeJS {
interface Global { interface Global {
inTest: boolean; inTest: boolean;
scanRequest?: ScanTestRequest;
} }
} }
// declare webpack modules // declare webpack modules
......
...@@ -14,20 +14,42 @@ ...@@ -14,20 +14,42 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { import { Platform } from 'react-native';
SUBSTRATE_NETWORK_LIST,
SubstrateNetworkKeys import { scanRequestDataMap, ScanTestRequest } from 'e2e/mockScanRequests';
} from 'constants/networkSpecs';
import { TxRequestData } from 'types/scannerTypes'; import { TxRequestData } from 'types/scannerTypes';
export const signingTestIdentityPath = `//${ type AndroidAppArgs = {
SUBSTRATE_NETWORK_LIST[SubstrateNetworkKeys.KUSAMA].pathId scanRequest?: number;
}//default`; };
type iOSAppArgs = Array<string>;
const setRemarkExtrinsicKusama = export interface AppProps {
'47900000100005301021e169bcc4cdb062f1c85f971be770b6aea1bd32ac1bf7877aa54ccd73309014a20180000010c11111145030000fe030000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafeaf2df518f7017e0442a1d01b0f175e0fa5d427470014c1c3ab2131e6250072a70ec'; launchArgs?: AndroidAppArgs | iOSAppArgs;
}
export const createMockSignRequest = (): TxRequestData => ({ export const getLaunchArgs = (props: AppProps): void => {
const { launchArgs } = props;
if (Platform.OS === 'ios') {
if (Array.isArray(launchArgs) && launchArgs.includes('-detoxServer')) {
global.inTest = true;
const argsIndex = launchArgs.indexOf('-scanRequest');
if (argsIndex !== -1)
global.scanRequest = parseInt(launchArgs[argsIndex + 1], 10);
return;
}
} else {
if (launchArgs && launchArgs.hasOwnProperty('detoxServer')) {
global.inTest = true;
global.scanRequest = (launchArgs as AndroidAppArgs)?.scanRequest;
return;
}
}
global.inTest = false;
};
const buildSignRequest = (rawData: string, data = ''): TxRequestData => ({
bounds: { bounds: {
bounds: [ bounds: [
{ x: '50', y: '50' }, { x: '50', y: '50' },
...@@ -36,8 +58,24 @@ export const createMockSignRequest = (): TxRequestData => ({ ...@@ -36,8 +58,24 @@ export const createMockSignRequest = (): TxRequestData => ({
height: 1440, height: 1440,
width: 1920 width: 1920
}, },
data: '', data,
rawData: setRemarkExtrinsicKusama, rawData,
target: 319, target: 319,
type: 'qr' type: 'qr'
}); });
export const onMockBarCodeRead = (
txRequest: ScanTestRequest,
onBarCodeRead: (tx: any) => void
): void => {
const scanRequest = scanRequestDataMap[txRequest];
if (typeof scanRequest === 'string') {
onBarCodeRead(buildSignRequest(scanRequest));
} else if (Array.isArray(scanRequest)) {
(scanRequest as string[]).forEach((rawData: string): void => {
onBarCodeRead(buildSignRequest(rawData));
});
} else if (typeof scanRequest === 'object') {
onBarCodeRead(buildSignRequest(scanRequest.rawData, scanRequest.data));
}
};
const commonConfig = require('../jestCommonConfig');
module.exports = {
...commonConfig,
cacheDirectory: '<rootDir>/test/.jest/e2e/cache',
reporters: ['detox/runners/jest/streamlineReporter'],
roots: ['<rootDir>/test/e2e/specs'],
setupFilesAfterEnv: ['<rootDir>/test/e2e/setup.ts']
};
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
export enum ScanTestRequest {
SetRemarkExtrinsic,
TransferExtrinsic,
SetRemarkMultiPart,
EthereumTransaction,
EthereumMessage
}
export const scanRequestDataMap = {
[ScanTestRequest.SetRemarkExtrinsic]:
'47500000100005301025a4a03f84a19cf8ebda40e62358c592870691a9cf456138bb4829969d10fe96910000104116503000015040000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe6f3e4192d8a58ee728ed1f2edf28bb7a78bc62a9b112c190aae5f3eb7de6c5af0ec11ec11ec',
[ScanTestRequest.TransferExtrinsic]:
'49800000100005301025a4a03f84a19cf8ebda40e62358c592870691a9cf456138bb4829969d10fe9699c0400ff3c36776005aec2f32a34c109dc791a82edef980eec3be80da938ac9bcc68217202286bee7503000015040000b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe19d84dae3af90c5c78ff8eaed58b0db1ff8600c4426d4e0969a905ab5fe2d2e50',
[ScanTestRequest.SetRemarkMultiPart]: [