Error setting up Detox on React-Native/Expo mobile app: "ReferenceError: element is not defined"

Error setting up Detox on React-Native/Expo mobile app: "ReferenceError: element is not defined"

我正在尝试在 React-Native/Expo 移动应用程序上使用 Detox 设置端到端测试。该应用程序和 Jest 目前运行良好,但 Detox 测试给出了 ReferenceError。我已按照 link、https://blog.expo.io/testing-expo-apps-with-detox-and-react-native-testing-library-7fbdbb82ac87 以及 Detox 和 Jest 网站中的说明进行操作。 我通过笑话脚本 (yarn test) 和 detox test.

进行了测试
$ detox test --loglevel trace
detox[51199] INFO:  [test.js] configuration="ios.sim" loglevel="trace" artifactsLocation="artifacts/ios.sim.2019-04-26 12-31-53Z" recordLogs="none" takeScreenshots="manual" recordVideos="none" recordPerformance="none" node_modules/.bin/jest --config=e2e/config.json --maxWorkers=1 '--testNamePattern=^((?!:android:).)*$' "e2e"
● Validation Warning:

  Unknown option "setupFilesAfterEnv" with value ["./init.js"] was found.
  This is probably a typing mistake. Fixing it will remove this message.

  Configuration Documentation:
  https://jestjs.io/docs/configuration.html

 FAIL  e2e/features/login/index.spec.js
  App
    ✕ should have login screen (272ms)
    ✕ should show hello screen after tap (104ms)
    ✕ should show world screen after tap (105ms)

  ● App › should have login screen

    ReferenceError: device is not defined

      at reloadApp (../node_modules/detox-expo-helpers/index.js:68:3)

  ● App › should have login screen

    ReferenceError: element is not defined
...

setupFilesAfterEnv 一个开玩笑的选项。

下载的Expo IPA在目录bin/Exponent.app.

package.json

{
...
  "main": "node_modules/expo/AppEntry.js",
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "test": "jest --watchAll --notify",
    "lint": "eslint .",
    "ci": "yarn lint && jest"
  },
  "dependencies": {
    "expo": "^32.0.0",
    "formik": "^1.5.1",
    "invariant": "^2.2.4",
    "prop-types": "^15.7.2",
    "react": "16.5.0",
    "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
    "react-navigation": "^3.3.2",
    "react-redux": "^6.0.1",
    "redux": "^4.0.1",
    "redux-persist": "^5.10.0",
    "redux-thunk": "^2.3.0",
    "reselect": "^4.0.0"
  },
  "devDependencies": {
    "@babel/core": "^7.3.4",
    "babel-eslint": "^10.0.1",
    "babel-plugin-module-resolver": "^3.2.0",
    "babel-preset-expo": "^5.0.0",
    "detox": "^12.4.1",
    "detox-expo-helpers": "^0.6.0",
    "eslint": "^5.15.0",
    "eslint-config-prettier": "^4.1.0",
    "eslint-plugin-prettier": "^3.0.1",
    "eslint-plugin-react": "^7.12.4",
    "expo-detox-hook": "^1.0.10",
    "jest-expo": "^32.0.0",
    "prettier": "^1.16.4",
    "react-native-testing-library": "^1.7.0",
    "react-test-renderer": "^16.8.6",
    "redux-devtools-extension": "^2.13.8",
    "redux-mock-store": "^1.5.3"
  },
  "jest": {
    "preset": "jest-expo",
    "clearMocks": true
  },
  "detox": {
    "test-runner": "jest",
    "runner-config": "e2e/config.json",
    "configurations": {
      "ios.sim": {
        "binaryPath": "bin/Exponent.app",
        "type": "ios.simulator",
        "name": "iPhone XR"
      }
    }
  }
}

e2e 目录直接来自 Detox,为 Expo 做了一些改动。

e2e/config.json

{
    "setupFilesAfterEnv": ["./init.js"],
    "testEnvironment": "node"
}

e2e/init.js

const detox = require('detox');
const config = require('../package.json').detox;
const adapter = require('detox/runners/jest/adapter');

jest.setTimeout(120000);
jasmine.getEnv().addReporter(adapter);

beforeAll(async () => {
    await detox.init(config);
});

beforeEach(async () => {
    await adapter.beforeEach();
});

afterAll(async () => {
    await adapter.afterAll();
    await detox.cleanup();
});

login.spec.js

import { reloadApp } from 'detox-expo-helpers';

describe('App', () => {
    beforeEach(async () => {
        await reloadApp();
    });

    it('should have login screen', async () => {
        await expect(element(by.id('login'))).toBeVisible();
    });

    it('should show hello screen after tap', async () => {
        await element(by.id('hello_button')).tap();
        await expect(element(by.text('Hello!!!'))).toBeVisible();
    });

    it('should show world screen after tap', async () => {
        await element(by.id('world_button')).tap();
        await expect(element(by.text('World!!!'))).toBeVisible();
    });
});

附带说明一下,我不知道为什么 init.js 中有茉莉花参考。我猜是因为 JestJS 是基于 Jasmine 的。反正好像跟报错没有关系

总结

有个open pull-request for this issue. I quote Yaron Malim

ReferenceError: device is not defined

  at Object.reloadApp (../node_modules/detox-expo-helpers/index.js:68:3)

When I added the following require:

const { device } = require('detox');

The issue was solved. In order to do so, I had to add detox as a dependency. This is the package.json

{
  "name": "detox-expo-helpers",
  "version": "0.6.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {},
  "dependencies": {
    "child-process-promise": "^2.2.1",
    "detox": "^12.2.0",
    "semver": "^5.6.0",
    "xdl": "^51.5.0"
  }
}

详情committed files in the pull request

安装必要的依赖项

detox-expo-helpers github repo includes an example app. Add expo-detox-hook to your package.json 和 运行 npm install

{
  "devDependencies": {
    "detox": "^9.0.6",
    "detox-expo-helpers": "^0.6.0",
    "expo-detox-hook": "^1.0.10",
    "mocha": "^3.5.0"
  },
  "detox": {
    "configurations": {
      "ios.sim": {
        "binaryPath": "bin/Exponent.app",
        "type": "ios.simulator",
        "name": "iPhone 7"
      }
    }
  }
}

official detox-expo-helpers page you should follow every step to set up your detox project, except for the package.json configurations included in this step 中所述,在 expo 中有所不同。

detox-expo-helpers 包括您的模拟器的官方配置 at the step Download the Expo app to some directory in your project and configure in package.json

它还包括一个example of the Detox settings for your package.json

您的第二次测试失败,因为 Detox 在页面上找不到该元素。 您需要将 testID 属性传递给值为 'hello_button'.

的组件
it('should show hello screen after tap', async () => {
    await element(by.id('hello_button')).tap();
    await expect(element(by.text('Hello!!!'))).toBeVisible();
});

documentation on by.id matcher

by.id will match an id that is given to the view via testID prop.

你的组件应该看起来像

<TouchableOpacity testID={'hello_button'}>