1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @format
8 */
9
10// Used in run-ci-e2e-test.js and executed in Circle CI.
11//
12// E2e test that verifies that init app can be installed, compiled, started and
13// Hot Module reloading and Chrome debugging work.
14//
15// For other examples of appium refer to:
16// https://github.com/appium/sample-code/tree/master/sample-code/examples/node and
17// https://www.npmjs.com/package/wd-android
18//
19// To set up:
20// - npm install --save-dev [email protected] [email protected] [email protected] [email protected] [email protected]
21// - cp <this file> <to app installation path>
22// - keytool -genkey -v -keystore android/app/debug.keystore -storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000 -dname "CN=Android Debug,O=Android,C=US"
23//
24// To run this test:
25// - npm start
26// - node node_modules/.bin/appium
27// - (cd android && ./gradlew :app:copyDownloadableDepsToLibs)
28// - react-native run-android
29// - node ../node_modules/.bin/_mocha ../android-e2e-test.js
30
31/* eslint-env mocha */
32
33'use strict';
34
35const wd = require('wd');
36const path = require('path');
37const fs = require('fs');
38const pd = require('pretty-data2').pd;
39
40// value in ms to print out screen contents, set this value in CI to debug if tests are failing
41const appiumDebugInterval = process.env.APPIUM_DEBUG_INTERVAL;
42
43describe('Android Test App', function () {
44  this.timeout(600000);
45  let driver;
46  let debugIntervalId;
47
48  before(function () {
49    driver = wd.promiseChainRemote({
50      host: 'localhost',
51      port: 4723,
52    });
53    driver.on('status', function (info) {
54      console.log(info.cyan);
55    });
56    driver.on('command', function (method, command, data) {
57      if (command === 'source()' && data) {
58        console.log(
59          ' > ' + method.yellow,
60          'Screen contents'.grey,
61          '\n',
62          pd.xml(data).yellow,
63        );
64      } else {
65        console.log(' > ' + method.yellow, command.grey, data || '');
66      }
67    });
68    driver.on('http', function (method, urlPath, data) {
69      console.log(' > ' + method.magenta, urlPath, (data || '').grey);
70    });
71
72    // every interval print what is on the screen
73    if (appiumDebugInterval) {
74      debugIntervalId = setInterval(() => {
75        // it driver.on('command') will log the screen contents
76        driver.source();
77      }, appiumDebugInterval);
78    }
79
80    const desired = {
81      platformName: 'Android',
82      deviceName: 'Android Emulator',
83      app: path.resolve('android/app/build/outputs/apk/debug/app-debug.apk'),
84    };
85
86    // React Native in dev mode often starts with Red Box "Can't fibd variable __fbBatchedBridge..."
87    // This is fixed by clicking Reload JS which will trigger a request to Metro
88    return driver
89      .init(desired)
90      .setImplicitWaitTimeout(5000)
91      .waitForElementByXPath('//android.widget.Button[@text="Reload JS"]')
92      .then(
93        elem => {
94          elem.click();
95          driver.sleep(2000);
96        },
97        // eslint-disable-next-line handle-callback-err
98        err => {
99          // ignoring if Reload JS button can't be located
100        },
101      );
102  });
103
104  after(function () {
105    if (debugIntervalId) {
106      clearInterval(debugIntervalId);
107    }
108    return driver.quit();
109  });
110
111  it('should display new content after a refresh', function () {
112    const androidAppCode = fs.readFileSync('App.js', 'utf-8');
113    let intervalToUpdate;
114    return (
115      driver
116        .waitForElementByXPath(
117          '//android.widget.TextView[starts-with(@text, "Welcome to React")]',
118        )
119        .then(() => {
120          fs.writeFileSync(
121            'App.js',
122            androidAppCode.replace('Step One', 'Step 1'),
123            'utf-8',
124          );
125        })
126        .sleep(1000)
127        // http://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_MENU
128        .pressDeviceKey(46)
129        .pressDeviceKey(46)
130        .sleep(2000)
131        .waitForElementByXPath(
132          '//android.widget.TextView[starts-with(@text, "Step 1")]',
133        )
134        .finally(() => {
135          clearInterval(intervalToUpdate);
136          fs.writeFileSync('App.js', androidAppCode, 'utf-8');
137          driver.pressDeviceKey(46).pressDeviceKey(46).sleep(2000);
138        })
139    );
140  });
141
142  it('should have the menu available', function () {
143    return (
144      driver
145        .waitForElementByXPath(
146          '//android.widget.TextView[starts-with(@text, "Welcome to React")]',
147        )
148        // http://developer.android.com/reference/android/view/KeyEvent.html#KEYCODE_MENU
149        .pressDeviceKey(82)
150        .waitForElementByXPath(
151          '//android.widget.TextView[starts-with(@text, "Toggle Inspector")]',
152        )
153    );
154  });
155});
156