-
Notifications
You must be signed in to change notification settings - Fork 25.1k
RNTester: Setup E2E tests for iOS and Android with Appium, Jest and Cucumber #35824
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
Changes from all commits
2d7b8d2
c9183a7
d45231d
83a9cb0
1cc5c9c
ae7efd4
566bcbd
698a0d6
e82b77b
f884711
320dc82
7fb9ec0
0a186ee
253ac0b
faff18a
1602579
192ae42
ee7fbb9
78a70a4
a304fc9
d3e745b
da78d7c
1b0d0f1
82e4786
5e2a54e
a87021d
f52dbfe
1d6c99b
f9cce01
f2ef916
2789ed0
3ebe200
de05312
97d6045
be4415d
6e655c3
fe819d1
b3d21be
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| # How to add a test | ||
|
|
||
| 1. Create a feature file `(rn-tester-e2e/test/features)` - GivenWhenThen Gherkin syntax | ||
| 2. **OPTIONAL -** Create a screen object or extend the existing one (depends on the test scope) - `rn-tester-e2e/test/screenObjects` - map screen elements for iOS and Android | ||
| 3. **OPTIONAL -** Add another common step in `rn-tester-e2e/test/common_steps/common.steps.js` | ||
| 4. Create a runner file `(rn-tester-e2e/test/runners)` - import steps and screen objects from point 2 and 3. Create test scenarios | ||
| 5. Update `(rn-tester-e2e/e2e-config.js)` with proper capabilities of your emulator | ||
|
|
||
| # How to execute a test | ||
| 1. Open new Terminal -> navigate to the react-native path -> open Metro by typing | ||
| >yarn start | ||
|
|
||
| or | ||
|
|
||
| >npm start | ||
|
|
||
|
|
||
| 2. Open second terminal -> navigate to the react-native/packages/rn-tester-e2e -> MAKE SURE YOUR APPIUM HAS UIAUTOMATOR2 AND XCUITEST INSTALLED! type | ||
| >npm install appium@2.0.0-beta.40 -g | ||
|
|
||
| >appium driver install uiautomator2 | ||
|
|
||
| >appium driver install xcuitest | ||
|
|
||
| >appium --base-path /wd/hub | ||
|
|
||
| 3. Open third terminal -> navigate to the react-native/packages/rn-tester-e2e -> run all tests by typing | ||
| >npm run ios | ||
|
|
||
| or | ||
|
|
||
| >npm run android |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # Description of all folders and files in rn-tester-e2e | ||
|
|
||
| # folders 🗂 | ||
| ## common_steps 🪜 | ||
| Common steps reusable between different features files | ||
|
|
||
| ## features 🥒 | ||
| Cucumber feature files. GivenWhenThen Gherkin syntax. One feature per screen/functionality | ||
|
|
||
| ## helpers 🧑🏻🚒 | ||
| Utils file with generic, simple actions and methods | ||
|
|
||
| ## runners 🏃🏽♀️ | ||
| Runner file which combines feature file and steps file. Runner file imports steps file and declares step functions in the same order as in the feature file | ||
|
|
||
| ## screenObjects 📱 | ||
| Screen object files based on Page Object Pattern. One file defines all necessary elements to interact with. These elements are defined as screen class variables, they are used by the steps file | ||
|
|
||
| # root files 📄 | ||
| ## e2e-config.js | ||
| Android and iOS emulator/physical device configuration, process.env.E2E_device global variable is defined there - it can be used across the whole rn-tester-e2e directory | ||
|
|
||
| ## jest.config.js | ||
| Global jest config setup - such as timeout, test runner path | ||
|
|
||
| ## jest.setup.js | ||
| Jest and wdio setup file | ||
|
|
||
| ## package.json | ||
| all external dependencies and project parameters |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # Building the app | ||
| Bulding manually *.app* and *.apk* is required to run automation tests on local environment. | ||
|
|
||
| Before building app, make sure you ran: | ||
|
|
||
| ```bash | ||
| cd react-native | ||
| yarn install | ||
| ``` | ||
| ## Building for iOS | ||
|
|
||
| If you prevoiusly built RNTester, you may need to clean up build files and Pods: | ||
| ```bash | ||
| rm -rf node_modules && yarn | ||
| cd packages/rn-tester | ||
| yarn clean-ios | ||
| ``` | ||
|
|
||
| Build the app for not M1 mac user: | ||
|
|
||
| 1. Install Bundler `gem install bundler`. We use bundler to install the right version of CocoaPods locally. | ||
| 2. Install Bundler and CocoaPods dependencies: `bundle install && bundle exec pod install` or `yarn setup-ios-hermes`. In order to use JSC instead of Hermes engine, run: `USE_HERMES=0 bundle exec pod install` or `yarn setup-ios-jsc` instead. | ||
| 3. Open the generated `RNTesterPods.xcworkspace`. | ||
| 4. Build the app. | ||
|
|
||
| If you are M1 mac user: | ||
| 1. Install ffi package `gem install ffi -v '1.15.5' --source 'https://rubygems.org/'` | ||
| 2. Install pods with new architecture, e.g. using JSC `USE_HERMES=0 arch -x86_64 pod install` | ||
| 3. Open the generated `RNTesterPods.xcworkspace`. | ||
| 4. Build the app. | ||
|
|
||
| Find the **RNTester.app** in `~/Library/Developer/Xcode/DerivedData/RNTesterPods-{id}/Build/Products/Debug-iphonesimulator` and move the app to the following directory `/react-native/packages/rn-tester-e2e/apps` | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can use |
||
|
|
||
|
|
||
| ## Building for Android | ||
| 1. You'll need to have all the [prerequisites](https://reactnative.dev/contributing/how-to-build-from-source#prerequisites) (SDK, NDK) for Building React Native installed. | ||
|
|
||
| 2. Start an Android emulator. | ||
| 3. Build the app | ||
| ```sh | ||
| cd packages/rn-tester | ||
| # In order to use Hermes engine, run `yarn install-android-hermes` instead. | ||
| yarn install-android-jsc | ||
| yarn start | ||
| ``` | ||
|
|
||
| _Note: Building for the first time can take a while._ | ||
|
|
||
| Find the **RNTester.app** in `~/AndroidStudioProjects/{ProjectName}/app/build/intermediates/apk/debug/` and move the app to the following directory `/react-native/packages/rn-tester-e2e/apps` | ||
|
|
||
|
|
||
| ## Usage | ||
|
|
||
| In /react-native/packages/rn-tester-e2e | ||
|
|
||
| ```bash | ||
| npm install | ||
| ``` | ||
|
|
||
| Go to | ||
| ```bash | ||
| /react-native/packages/rn-tester-e2e/e2e-config.js | ||
| ``` | ||
| Prepare capabilities for your simulators | ||
|
|
||
| The next step is to run the appium | ||
|
|
||
| ```bash | ||
| appium | ||
| ``` | ||
|
|
||
| After running appium open your simulator and | ||
|
|
||
| ```bash | ||
| npm run android - for android | ||
| npm run ios - for ios | ||
| ``` | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| put app in this folder |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module.exports = { | ||
| presets: [['@babel/preset-env', {targets: {node: 'current'}}]], | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // utility file to extract the config for E2E testing at runtime | ||
| // for appium | ||
| const path = require('path'); | ||
| let capabilities; | ||
|
|
||
| const android = { | ||
| 'platformName': 'Android', | ||
| 'appium:platformVersion': '', | ||
| 'appium:deviceName': '', | ||
| 'appium:app': path.join(process.cwd(), '/apps/RNTester.apk'), | ||
| 'appium:automationName': 'uiautomator2', | ||
| 'appium:newCommandTimeout': 240, | ||
| }; | ||
|
|
||
| const ios = { | ||
| 'platformName': 'iOS', | ||
| 'appium:platformVersion': '', | ||
| 'appium:deviceName': '', | ||
| //bundleId: 'org.reactjs.native.example.TestForE2E', | ||
| 'appium:automationName': 'XCUITest', | ||
| 'appium:app': path.join(process.cwd(), '/apps/RNTester.app'), | ||
| }; | ||
|
|
||
| if (!process.env.E2E_DEVICE) { | ||
| throw new Error('E2E_DEVICE environment variable is not defined'); | ||
| } | ||
|
|
||
| if (!(process.env.E2E_DEVICE.includes('android') || process.env.E2E_DEVICE.includes('ios'))) { | ||
| throw new Error('No e2e device configuration found'); | ||
| } | ||
|
|
||
| if (process.env.E2E_DEVICE === 'android') { | ||
| capabilities = android; | ||
| } | ||
|
|
||
| if (process.env.E2E_DEVICE === 'ios') { | ||
| capabilities = ios; | ||
| } | ||
|
|
||
| export default capabilities; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| module.exports = { | ||
| testTimeout: 60000, | ||
| bail: 0, | ||
| setupFilesAfterEnv: ['./jest.setup.js'], | ||
| testMatch: [ | ||
| '**/runners/*.js', | ||
| ], | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||||
| const wdio = require('webdriverio'); | ||||||
| import capabilities from './e2e-config.js'; | ||||||
| import { beforeEach, afterEach, jest } from '@jest/globals'; | ||||||
|
|
||||||
|
|
||||||
| jest.setTimeout(40000); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This contradicts the timeout we set in the config? |
||||||
|
|
||||||
| let driver; | ||||||
| const config = { | ||||||
| path: '/wd/hub', | ||||||
| host: 'localhost', | ||||||
| port: 4723, | ||||||
| waitforTimeout: 30000, | ||||||
| logLevel: 'silent', | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| capabilities: { | ||||||
| ...capabilities, | ||||||
| }, | ||||||
| }; | ||||||
|
|
||||||
| beforeEach(async () => { | ||||||
| driver = await wdio.remote(config); | ||||||
| }); | ||||||
|
|
||||||
| afterEach(async () => { | ||||||
| console.info('[afterAll] Done with testing!'); | ||||||
| await driver.deleteSession(); | ||||||
| }); | ||||||
|
|
||||||
| export { driver }; | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
|
|
||
| { | ||
| "name": "rn-tester-e2e", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "devDependencies": { | ||
| "appium": "^2.0.0-beta.40", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not seeing reflected in yarn lockfile. Could we yarn install (also so we can see how many deps added) |
||
| "appium-uiautomator2-driver": "^2.9.0", | ||
| "appium-xcuitest-driver": "^4.12.2", | ||
| "eslint": "^8.19.0", | ||
| "jest": "^29.2.1", | ||
| "webdriverio": "^7.25.4" | ||
| "jest-cucumber": "^3.0.1", | ||
| }, | ||
| "scripts": { | ||
| "test": "jest --runInBand", | ||
| "android": "E2E_DEVICE=\"android\" npm run test", | ||
| "ios": "E2E_DEVICE=\"ios\" npm run test" | ||
| }, | ||
| "dependencies": { | ||
| "@babel/preset-env": "^7.20.0", | ||
| "@types/jest": "^29.2.1" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. devDependency? |
||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { driver } from '../../jest.setup.js'; | ||
|
|
||
| // Common steps reusable between different features | ||
| export const userIsOnMainScreen = (given) => { | ||
| given('User is on the main screen', async () => { | ||
| await driver.pause(2000); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harcoded waits like this should be avoided |
||
| }); | ||
| }; | ||
|
|
||
| export const clickOkButton = (given) => { | ||
| given('User clicks on the OK button', async () => { | ||
| await driver.pause(2000); | ||
| }); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| Feature: Button component screen | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need cucumber, if we don't use it anywhere else. |
||
|
|
||
| Scenario: Cancel Button | ||
| Given User is on the main screen | ||
| Then Verify that the Button component is displayed | ||
| When User clicks on the Button component | ||
| Then Verify that the "Button" header is displayed | ||
| When User clicks on the Cancel Application button | ||
| Then Verify that the cancel alert box has text: "Your application has been cancelled!" | ||
| And User clicks on the OK button | ||
|
|
||
| Scenario: Submit Button | ||
| Given User is on the main screen | ||
| Then Verify that the Button component is displayed | ||
| When User clicks on the Button component | ||
| Then Verify that the "Button" header is displayed | ||
| When User clicks on the Submit Application button | ||
| Then Verify that the submit alert box has text: "Your application has been submitted!" | ||
| And User clicks on the OK button | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
|
|
||
| import { driver } from '../../jest.setup'; | ||
|
|
||
| class Utils { | ||
|
|
||
| async checkElementExistence(locator) { | ||
| await driver.$(locator).waitForDisplayed(); | ||
| return driver.$(locator).isDisplayed(); | ||
| } | ||
|
|
||
| async clickElement(locator) { | ||
| await driver.$(locator).waitForDisplayed(); | ||
| await driver.$(locator).click(); | ||
| } | ||
|
|
||
| async getElementText(locator) { | ||
| await driver.$(locator).waitForDisplayed(); | ||
| return driver.$(locator).getText(); | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of these should be formatted/linted, have copyright headers. |
||
|
|
||
| platformSelect(platforms) { | ||
| return platforms[process.env.E2E_DEVICE]; | ||
| } | ||
| } | ||
| module.exports = new Utils(); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be required anymore.