Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 25.2k
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
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
2d7b8d2c9183a7d45231d83a9cb01cc5c9cae7efd4566bcbd698a0d6e82b77bf884711320dc827fb9ec00a186ee253ac0bfaff18a1602579192ae42ee7fbb978a70a4a304fc9d3e745bda78d7c1b0d0f182e47865e2a54ea87021df52dbfe1d6c99bf9cce01f2ef9162789ed03ebe200de0531297d6045be4415d6e655c3fe819d1b3d21beFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
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(); | ||
Uh oh!
There was an error while loading. Please reload this page.
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.