Selaa lähdekoodia

WIP - Detox e2e for android (#2673)

* detox android support

* detox android support

* android detox fixes

* android fixes

* more android e2e fixes

* typo fix

* build fix

* remove specific test for ios

* Update DetoxTest.java

* lint fix

* fixed typo

* Update ViewController.java

* added script for android detox e2e

* detox android support

* android detox fixes

* android fixes

* more android e2e fixes

* typo fix

* build fix

* remove specific test for ios

* Update DetoxTest.java

* lint fix

* fixed typo

* Update ViewController.java

* added script for android detox e2e

* e2e test fixes

* Update Orientations.test.js

* Update ScreenStack.test.js

* Update ScreenStyle.test.js

* Update TopLevelApi.test.js
yogevbd 6 vuotta sitten
vanhempi
commit
bf30cf1452
No account linked to committer's email address

+ 4
- 3
e2e/CustomTransition.js Näytä tiedosto

@@ -1,7 +1,8 @@
1 1
 
2 2
 const Utils = require('./Utils');
3
+const testIDs = require('../playground/src/testIDs');
3 4
 
4
-const elementByLabel = Utils.elementByLabel;
5
+const elementById = Utils.elementById;
5 6
 
6 7
 describe('custom transition', () => {
7 8
   beforeEach(async () => {
@@ -9,8 +10,8 @@ describe('custom transition', () => {
9 10
   });
10 11
 
11 12
   it('sanity', async () => {
12
-    await elementByLabel('Push Options Screen').tap();
13
-    await elementByLabel('Custom Transition').tap();
13
+    await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
14
+    await elementById(testIDs.CUSTOM_TRANSITION_BUTTON).tap();
14 15
     await expect(element(by.id('shared_image1'))).toExist();
15 16
     await element(by.id('shared_image1')).tap();
16 17
     await expect(element(by.id('shared_image2'))).toExist();

+ 11
- 16
e2e/Orientations.test.js Näytä tiedosto

@@ -9,41 +9,36 @@ describe('orientation', () => {
9 9
     await device.relaunchApp();
10 10
   });
11 11
 
12
-  afterEach(async () => {
13
-    await device.setOrientation('landscape');
14
-    await device.setOrientation('portrait');
15
-  });
16
-
17 12
   it('default allows all', async () => {
18 13
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
19 14
     await elementById(testIDs.DEFAULT_ORIENTATION_BUTTON).tap();
20
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
15
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
21 16
     await device.setOrientation('landscape');
22
-    await expect(element(by.id('currentOrientation'))).toHaveText('Landscape');
17
+    await expect(elementById(testIDs.LANDSCAPE_ELEMENT)).toBeVisible();
23 18
     await device.setOrientation('portrait');
24
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
19
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
25 20
     await elementById(testIDs.DISMISS_BUTTON).tap();
26 21
   });
27 22
 
28 23
   it('landscape and portrait array', async () => {
29 24
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
30 25
     await elementById(testIDs.LANDSCAPE_PORTRAIT_ORIENTATION_BUTTON).tap();
31
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
26
+    await expect(element(by.id(testIDs.PORTRAIT_ELEMENT))).toBeVisible();
32 27
     await device.setOrientation('landscape');
33
-    await expect(element(by.id('currentOrientation'))).toHaveText('Landscape');
28
+    await expect(element(by.id(testIDs.LANDSCAPE_ELEMENT))).toBeVisible();
34 29
     await device.setOrientation('portrait');
35
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
30
+    await expect(element(by.id(testIDs.PORTRAIT_ELEMENT))).toBeVisible();
36 31
     await elementById(testIDs.DISMISS_BUTTON).tap();
37 32
   });
38 33
 
39 34
   it('portrait only', async () => {
40 35
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
41 36
     await elementById(testIDs.PORTRAIT_ORIENTATION_BUTTON).tap();
42
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
37
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
43 38
     await device.setOrientation('landscape');
44
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
39
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
45 40
     await device.setOrientation('portrait');
46
-    await expect(element(by.id('currentOrientation'))).toHaveText('Portrait');
41
+    await expect(elementById(testIDs.PORTRAIT_ELEMENT)).toBeVisible();
47 42
     await elementById(testIDs.DISMISS_BUTTON).tap();
48 43
   });
49 44
 
@@ -51,9 +46,9 @@ describe('orientation', () => {
51 46
     await elementById(testIDs.ORIENTATION_BUTTON).tap();
52 47
     await elementById(testIDs.LANDSCAPE_ORIENTATION_BUTTON).tap();
53 48
     await device.setOrientation('landscape');
54
-    await expect(element(by.id('currentOrientation'))).toHaveText('Landscape');
49
+    await expect(element(by.id(testIDs.LANDSCAPE_ELEMENT))).toBeVisible();
55 50
     await device.setOrientation('portrait');
56
-    await expect(element(by.id('currentOrientation'))).toHaveText('Landscape');
51
+    await expect(element(by.id(testIDs.LANDSCAPE_ELEMENT))).toBeVisible();
57 52
     await elementById(testIDs.DISMISS_BUTTON).tap();
58 53
   });
59 54
 });

+ 1
- 1
e2e/ScreenStack.test.js Näytä tiedosto

@@ -90,7 +90,7 @@ describe('screen stack', () => {
90 90
     await elementById(testIDs.SHOW_MODAL_BUTTON).tap();
91 91
     await elementById(testIDs.MODAL_WITH_STACK_BUTTON).tap();
92 92
     await expect(elementByLabel('Screen 2')).toBeVisible();
93
-    await Utils.tapBackIos();
93
+    await elementById(testIDs.POP_BUTTON).tap();
94 94
     await expect(elementByLabel('Screen 1')).toBeVisible();
95 95
   });
96 96
 });

+ 3
- 11
e2e/ScreenStyle.test.js Näytä tiedosto

@@ -10,14 +10,14 @@ describe('screen style', () => {
10 10
 
11 11
   it('declare a options on component component', async () => {
12 12
     await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
13
-    await expect(element(by.label('Static Title'))).toBeVisible();
13
+    await expect(elementByLabel('Static Title')).toBeVisible();
14 14
   });
15 15
 
16 16
   it('change title on component component', async () => {
17 17
     await elementById(testIDs.PUSH_OPTIONS_BUTTON).tap();
18
-    await expect(element(by.label('Static Title'))).toBeVisible();
18
+    await expect(elementByLabel('Static Title')).toBeVisible();
19 19
     await elementById(testIDs.DYNAMIC_OPTIONS_BUTTON).tap();
20
-    await expect(element(by.label('Dynamic Title'))).toBeVisible();
20
+    await expect(elementByLabel('Dynamic Title')).toBeVisible();
21 21
   });
22 22
 
23 23
   it('set dynamic options with valid options will do something and not crash', async () => {
@@ -46,14 +46,6 @@ describe('screen style', () => {
46 46
     await expect(elementById(testIDs.TOP_BAR_ELEMENT)).toBeVisible();
47 47
   });
48 48
 
49
-  it('makes topBar transparent and opaque', async () => {
50
-    await elementByLabel('Push Options Screen').tap();
51
-    await elementByLabel('Top Bar Transparent').tap();
52
-    await expect(element(by.type('_UIVisualEffectBackdropView'))).toBeNotVisible();
53
-    await elementByLabel('Top Bar Opaque').tap();
54
-    await expect(element(by.type('_UIVisualEffectBackdropView')).atIndex(1)).toBeVisible();
55
-  });
56
-
57 49
   it('set Tab Bar badge on a current Tab', async () => {
58 50
     await elementById(testIDs.TAB_BASED_APP_BUTTON).tap();
59 51
     await elementById(testIDs.SET_TAB_BADGE_BUTTON).tap();

+ 2
- 2
e2e/TopLevelApi.test.js Näytä tiedosto

@@ -35,9 +35,9 @@ describe('top level api', () => {
35 35
   it('unmount is called on pop', async () => {
36 36
     await elementById(testIDs.PUSH_LIFECYCLE_BUTTON).tap();
37 37
     await expect(elementByLabel('didAppear')).toBeVisible();
38
-    await Utils.tapBackIos();
38
+    await elementById(testIDs.POP_BUTTON).tap();
39 39
     await expect(elementByLabel('componentWillUnmount')).toBeVisible();
40
-    await element(by.traits(['button']).and(by.label('OK'))).atIndex(0).tap();
40
+    await elementByLabel('OK').atIndex(0).tap();
41 41
     await expect(elementByLabel('didDisappear')).toBeVisible();
42 42
   });
43 43
 });

+ 2
- 0
lib/android/app/src/main/java/com/reactnativenavigation/parse/TopBarOptions.java Näytä tiedosto

@@ -27,11 +27,13 @@ public class TopBarOptions implements DEFAULT_VALUES {
27 27
         options.drawBehind = Options.BooleanOptions.parse(json.optString("drawBehind"));
28 28
         options.rightButtons = Button.parseJsonArray(json.optJSONArray("rightButtons"));
29 29
         options.leftButtons = Button.parseJsonArray(json.optJSONArray("leftButtons"));
30
+        options.testId = TextParser.parse(json, "testID");
30 31
 
31 32
         return options;
32 33
     }
33 34
 
34 35
     public Text title = new NullText();
36
+    public Text testId = new NullText();
35 37
     public Color backgroundColor = new NullColor();
36 38
     public Color textColor = new NullColor();
37 39
     public Fraction textFontSize = new NullFraction();

+ 1
- 0
lib/android/app/src/main/java/com/reactnativenavigation/presentation/OptionsPresenter.java Näytä tiedosto

@@ -34,6 +34,7 @@ public class OptionsPresenter {
34 34
         topBar.setBackgroundColor(options.backgroundColor);
35 35
         topBar.setTitleTextColor(options.textColor);
36 36
         topBar.setTitleFontSize(options.textFontSize);
37
+        if (options.testId.hasValue()) topBar.setTestId(options.testId.get());
37 38
 
38 39
         topBar.setTitleTypeface(options.textFontFamily);
39 40
         if (options.hidden == True) {

+ 2
- 2
lib/android/app/src/main/java/com/reactnativenavigation/viewcontrollers/BottomTabsController.java Näytä tiedosto

@@ -69,8 +69,8 @@ public class BottomTabsController extends ParentController implements AHBottomNa
69 69
         if (wasSelected) return false;
70 70
         selectTabAtIndex(index);
71 71
         return true;
72
-    }
73
-
72
+	}
73
+	
74 74
 	public void setTabs(final List<ViewController> tabs) {
75 75
 		if (tabs.size() > 5) {
76 76
 			throw new RuntimeException("Too many tabs!");

+ 4
- 0
lib/android/app/src/main/java/com/reactnativenavigation/views/TopBar.java Näytä tiedosto

@@ -51,6 +51,10 @@ public class TopBar extends AppBarLayout implements ScrollEventListener.ScrollAw
51 51
         return titleBar.getTitle() != null ? titleBar.getTitle().toString() : "";
52 52
     }
53 53
 
54
+    public void setTestId(String testId) {
55
+        setTag(testId);
56
+    }
57
+
54 58
     public void setTitleTextColor(Color color) {
55 59
         if (color.hasValue()) titleBar.setTitleTextColor(color.get());
56 60
     }

+ 13
- 0
package.json Näytä tiedosto

@@ -38,6 +38,7 @@
38 38
     "test-unit-ios": "node ./scripts/test.unit.ios.js",
39 39
     "pretest-e2e-android": "npm run build",
40 40
     "test-e2e-android": "node ./scripts/test.e2e.android.js",
41
+    "test-e2e-android-detox": "node ./scripts/test.e2e.android.detox.js",
41 42
     "pretest-e2e-ios": "npm run build",
42 43
     "test-e2e-ios": "node ./scripts/test.e2e.ios.js",
43 44
     "test-all": "node ./scripts/test.all.js",
@@ -123,6 +124,18 @@
123 124
         "build": "RCT_NO_LAUNCH_PACKAGER=true xcodebuild build -scheme playground_release -project playground/ios/playground.xcodeproj -sdk iphonesimulator -configuration Release -derivedDataPath playground/ios/DerivedData/playground ONLY_ACTIVE_ARCH=YES -quiet",
124 125
         "type": "ios.simulator",
125 126
         "name": "iPhone SE"
127
+      },
128
+      "android.emu.debug": {
129
+        "binaryPath": "playground/android/app/build/outputs/apk/debug/app-debug.apk",
130
+        "build": "cd playground/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ../..",
131
+        "type": "android.emulator",
132
+        "name": "Nexus_5X_API_26"
133
+      },
134
+      "android.emu.release": {
135
+        "binaryPath": "playground/android/app/build/outputs/apk/release/app-release.apk",
136
+        "build": "cd playground/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ../..",
137
+        "type": "android.emulator",
138
+        "name": "Nexus_5X_API_26"
126 139
       }
127 140
     }
128 141
   }

+ 9
- 0
playground/android/app/build.gradle Näytä tiedosto

@@ -25,6 +25,10 @@ android {
25 25
         ndk {
26 26
             abiFilters "armeabi-v7a", "x86"
27 27
         }
28
+
29
+        testBuildType System.getProperty('testBuildType', 'debug')  //this will later be used to control the test apk build type
30
+        missingDimensionStrategy "minReactNative", "minReactNative46" //read note
31
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
28 32
     }
29 33
     signingConfigs {
30 34
         release {
@@ -47,4 +51,9 @@ dependencies {
47 51
     implementation 'com.android.support:design:25.4.0'
48 52
     implementation "com.android.support:appcompat-v7:25.4.0"
49 53
     implementation project(':react-native-navigation')
54
+
55
+    androidTestImplementation(project(path: ":detox"))
56
+    androidTestImplementation 'junit:junit:4.12'
57
+    androidTestImplementation 'com.android.support.test:runner:1.0.1'
58
+    androidTestImplementation 'com.android.support.test:rules:1.0.1'
50 59
 }

+ 24
- 0
playground/android/app/src/androidTest/java/com/reactnativenavigation/playground/DetoxTest.java Näytä tiedosto

@@ -0,0 +1,24 @@
1
+package com.reactnativenavigation.playground;
2
+
3
+import android.support.test.filters.LargeTest;
4
+import android.support.test.rule.ActivityTestRule;
5
+import android.support.test.runner.AndroidJUnit4;
6
+
7
+import com.wix.detox.Detox;
8
+
9
+import org.junit.Rule;
10
+import org.junit.Test;
11
+import org.junit.runner.RunWith;
12
+
13
+@RunWith(AndroidJUnit4.class)
14
+@LargeTest
15
+public class DetoxTest {
16
+
17
+    @Rule
18
+    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
19
+
20
+    @Test
21
+    public void runDetoxTests() throws InterruptedException {
22
+        Detox.runTests(mActivityRule);
23
+    }
24
+}

+ 3
- 0
playground/android/settings.gradle Näytä tiedosto

@@ -3,3 +3,6 @@ rootProject.name = 'playground'
3 3
 include ':app'
4 4
 include ':react-native-navigation'
5 5
 project(':react-native-navigation').projectDir = new File(rootProject.projectDir, '../../lib/android/app/')
6
+
7
+include ':detox'
8
+project(':detox').projectDir = new File(rootProject.projectDir, '../../node_modules/detox/android/detox')

+ 5
- 0
playground/src/screens/LifecycleScreen.js Näytä tiedosto

@@ -37,6 +37,7 @@ class LifecycleScreen extends Component {
37 37
         <Text style={styles.h1}>{`Lifecycle Screen`}</Text>
38 38
         <Text style={styles.h1}>{this.state.text}</Text>
39 39
         <Button title='Push to test didDisappear' testID={testIDs.PUSH_TO_TEST_DID_DISAPPEAR_BUTTON} onPress={this.onClickPush} />
40
+        <Button title='Pop' testID={testIDs.POP_BUTTON} onPress={() => this.onClickPop()} />
40 41
         <Text style={styles.footer}>{`this.props.componentId = ${this.props.componentId}`}</Text>
41 42
       </View>
42 43
     );
@@ -45,6 +46,10 @@ class LifecycleScreen extends Component {
45 46
   onClickPush() {
46 47
     Navigation.push(this.props.componentId, { component: { name: 'navigation.playground.TextScreen' } });
47 48
   }
49
+
50
+  onClickPop() {
51
+    Navigation.pop(this.props.componentId);
52
+  }
48 53
 }
49 54
 module.exports = LifecycleScreen;
50 55
 

+ 1
- 1
playground/src/screens/OptionsScreen.js Näytä tiedosto

@@ -63,7 +63,7 @@ class OptionsScreen extends Component {
63 63
         <Button title='Top Bar Transparent' onPress={this.onClickTopBarTransparent} />
64 64
         <Button title='Top Bar Opaque' onPress={this.onClickTopBarOpaque} />
65 65
         <Button title='scrollView Screen' testID={testIDs.SCROLLVIEW_SCREEN_BUTTON} onPress={this.onClickScrollViewScreen} />
66
-        <Button title='Custom Transition' onPress={this.onClickCustomTranstition} />
66
+        <Button title='Custom Transition' testID={testIDs.CUSTOM_TRANSITION_BUTTON} onPress={this.onClickCustomTranstition} />
67 67
         <Button title='Show custom alert' testID={testIDs.SHOW_CUSTOM_ALERT_BUTTON} onPress={this.onClickAlert} />
68 68
         <Button title='Show snackbar' testID={testIDs.SHOW_SNACKBAR_BUTTON} onPress={this.onClickSnackbar} />
69 69
         <Button title='Show overlay' testID={testIDs.SHOW_OVERLAY_BUTTON} onPress={() => this.onClickShowOverlay(true)} />

+ 3
- 1
playground/src/screens/OrientationDetectScreen.js Näytä tiedosto

@@ -23,7 +23,9 @@ class OrientationDetectScreen extends Component {
23 23
         <Text style={styles.h1}>{`Orientation Screen`}</Text>
24 24
         <Button title='Dismiss' testID={testIDs.DISMISS_BUTTON} onPress={() => Navigation.dismissModal(this.props.componentId)} />
25 25
         <Text style={styles.footer}>{`this.props.componentId = ${this.props.componentId}`}</Text>
26
-        <Text style={styles.footer} testID='currentOrientation'>{this.state.horizontal ? 'Landscape' : 'Portrait'}</Text>
26
+        {this.state.horizontal ?
27
+        <Text style={styles.footer} testID={testIDs.LANDSCAPE_ELEMENT}>Landscape</Text> :
28
+        <Text style={styles.footer} testID={testIDs.PORTRAIT_ELEMENT}>Portrait</Text>}
27 29
       </View>
28 30
     );
29 31
   }

+ 4
- 8
playground/src/screens/OrientationSelectScreen.js Näytä tiedosto

@@ -1,12 +1,12 @@
1 1
 const React = require('react');
2 2
 const { Component } = require('react');
3
+
3 4
 const { View, Text, Button } = require('react-native');
4 5
 
5 6
 const { Navigation } = require('react-native-navigation');
6 7
 const testIDs = require('../testIDs');
7 8
 
8 9
 class OrientationSelectScreen extends Component {
9
-
10 10
   render() {
11 11
     return (
12 12
       <View style={styles.root}>
@@ -37,16 +37,12 @@ const styles = {
37 37
   root: {
38 38
     flexGrow: 1,
39 39
     justifyContent: 'center',
40
-    alignItems: 'center'
40
+    alignItems: 'center',
41
+    backgroundColor: '#f5fcff'
41 42
   },
42 43
   h1: {
43 44
     fontSize: 24,
44 45
     textAlign: 'center',
45
-    margin: 30
46
-  },
47
-  footer: {
48
-    fontSize: 10,
49
-    color: '#888',
50
-    marginTop: 10
46
+    margin: 10
51 47
   }
52 48
 };

+ 4
- 0
playground/src/screens/TextScreen.js Näytä tiedosto

@@ -97,6 +97,10 @@ class TextScreen extends Component {
97 97
       }
98 98
     });
99 99
   }
100
+
101
+  onClickPop() {
102
+    Navigation.pop(this.props.componentId);
103
+  }
100 104
 }
101 105
 
102 106
 module.exports = TextScreen;

+ 3
- 0
playground/src/testIDs.js Näytä tiedosto

@@ -51,11 +51,14 @@ module.exports = {
51 51
   SHOW_TOUCH_THROUGH_OVERLAY_BUTTON: `SHOW_TOUCH_THROUGH_OVERLAY_BUTTON`,
52 52
   OK_BUTTON: `OK_BUTTON`,
53 53
   MODAL_WITH_STACK_BUTTON: `MODAL_WITH_STACK_BUTTON`,
54
+  CUSTOM_TRANSITION_BUTTON: `CUSTOM_TRANSITION_BUTTON`,
54 55
 
55 56
   // Elements
56 57
   SCROLLVIEW_ELEMENT: `SCROLLVIEW_ELEMENT`,
57 58
   BOTTOM_TABS_ELEMENT: `BOTTOM_TABS_ELEMENT`,
58 59
   TOP_BAR_ELEMENT: `TOP_BAR_ELEMENT`,
60
+  LANDSCAPE_ELEMENT: `LANDSCAPE_ELEMENT`,
61
+  PORTRAIT_ELEMENT: `PORTRAIT_ELEMENT`,
59 62
 
60 63
   // Headers
61 64
   WELCOME_SCREEN_HEADER: `WELCOME_SCREEN_HEADER`,

+ 12
- 0
scripts/test.e2e.android.detox.js Näytä tiedosto

@@ -0,0 +1,12 @@
1
+const _ = require('lodash');
2
+const exec = require('shell-utils').exec;
3
+
4
+const release = _.includes(process.argv, '--release');
5
+
6
+run();
7
+
8
+function run() {
9
+  const conf = release ? `release` : `debug`;
10
+  exec.execSync(`detox build --configuration android.emu.${conf}`);
11
+  exec.execSync(`detox test --configuration android.emu.${conf} ${process.env.CI ? '--cleanup' : ''}`);
12
+}