React Native Detox Running Slowly on iOS

55 Views Asked by At

I have a production iPad application that I used to use Jest + @testing-library/react-native to perform e2e testing. I've recently decided to switch to Detox for a number of reasons.

After setting it up and migrating some tests to Detox syntax, I've noticed that actions are painfully slow. The demo on the site showed that each click/typing takes milliseconds, while it's taking way longer than that here, and I'm not sure what could be causing this. For context, I have around 200+ UI tests, and if each one is over a minute to execute, this just doesn't make sense for CI purposes. Below is a link for a demo as to what I'm referring to. I'll past my configs below, although I'm more looking for pointers in the right direction, since I'm not sure what could be causing this.

Link to Demo

Example Screen: Login.tsx

import React, { useState, useCallback } from "react";
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import { View, ActivityIndicator } from "react-native";
import styles from "src/pages/Login/styles.stylesheet";
import {
  PageTitle,
  TextInput,
  Button,
  BasicModal,
  BasicSafeAreaContainer,
} from "src/components";
import { LandingStackParamsList } from "src/routers/LandingStackNavigator";
import { useDispatch } from "react-redux";
import { palette } from "src/common/styles";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";

type NavigationProps = NativeStackScreenProps<LandingStackParamsList, "Login">;

export const Login = ({ navigation }: NavigationProps) => {
  const dispatch = useDispatch();

  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [isLoading, setIsLoading] = useState(false);

  const [error, setError] = useState<string | undefined>(undefined);

  const validateLoginInputs = useCallback(async () => {
    // Logic for logging in
  }, [email, password]);

  return (
    <BasicSafeAreaContainer>
      <View style={styles.container}>
        <KeyboardAwareScrollView
          extraScrollHeight={10}
          showsVerticalScrollIndicator={false}
          testID="login-scroll-view"
        >
          <View>
            <PageTitle
              text="Log In"
              onBackPress={() => navigation.goBack()}
              style={styles.pageTitle}
            />
            <View style={styles.inputs}>
              <TextInput
                testID="login-email-input"
                containerStyle={styles.eachInput}
                label="Email Address"
                value={email}
                onChangeText={setEmail}
                placeholder={"Enter your email address..."}
                autoCorrect={false}
                autoComplete="email"
                autoCapitalize="none"
              />
              <TextInput
                testID="login-password-input"
                containerStyle={styles.eachInput}
                label="Password"
                value={password}
                secureTextEntry={true}
                onChangeText={setPassword}
                placeholder={"Enter your password..."}
                autoCorrect={false}
                autoCapitalize="none"
              />
            </View>
          </View>

          {isLoading ? (
            <ActivityIndicator
              style={styles.createAccountButton}
              size="large"
              color={palette.blue}
            />
          ) : (
            <Button
              testID="login-button"
              style={styles.createAccountButton}
              onPress={async () => {
                await validateLoginInputs();
              }}
              type={"primary"}
              text={"Log In"}
            />
          )}

          <BasicModal
            testID="login-error-modal"
            isVisible={error !== undefined}
            title={"Error"}
            message={error}
            onConfirm={() => setError(undefined)}
          />
        </KeyboardAwareScrollView>
      </View>
    </BasicSafeAreaContainer>
  );
};

Example Test: TestLoginFlow.test.ts

import { clearDatabase } from "src/test/server";
import { by, element, expect, device } from "detox";

afterAll(async () => {
  await clearDatabase();
}, 10000);

beforeEach(async () => {
  await clearDatabase();
  await device.uninstallApp();
  await device.installApp();
  await device.launchApp({
    newInstance: true,
    permissions: {
      notifications: "YES",
    },
  });
}, 50000);

test("Test signing in with valid credentials", async () => {
   const restaurant = await mockCreatingARestaurant();

   const loginButton = element(by.id("landing-login-button"));
   await loginButton.tap();

   const emailInput = element(by.id("login-email-input"));
   const passwordInput = element(by.id("login-password-input"));

   await emailInput.replaceText(restaurant.contactEmailAddress);
   await passwordInput.replaceText("password");

   await element(by.id("login-scroll-view")).tap({ x: 0, y: 0 });

   const signInButton = element(by.id("login-button"));
   await signInButton.tap();

   const categoriesTab = element(by.id("dashboard-tab-empty-state"));
   await expect(categoriesTab).toExist();
});

detoxrc.js

/** @type {Detox.DetoxConfig} */
module.exports = {
  testRunner: {
    args: {
      '$0': 'jest',
      config: './jest.config.js'
    },
    jest: {
      setupTimeout: 120000
    }
  },
  apps: {
    'ios.debug': {
      type: 'ios.app',
      binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/Restaurant.app',
      build: 'react-native start'
    },
  },
  devices: {
    simulator: {
      type: 'ios.simulator',
      device: {
        type: 'iPad (9th generation)'
      }
    },
  },
  configurations: {
    'ios.sim.debug': {
      device: 'simulator',
      app: 'ios.debug'
    },
  }
};

jest.config.js

module.exports = {
  clearMocks: true,
  maxWorkers: 1,
  moduleDirectories: ["node_modules", "<rootDir>"],
  globals: {
    __DEV__: true,
    __BUNDLE_START_TIME__: 10101,
  },
  testMatch: ["<rootDir>/**/*.test.ts"],
  setupFiles: ["./node_modules/react-native-gesture-handler/jestSetup.js"],
  moduleNameMapper: {
    "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
      "<rootDir>/assetsTransformer.js",
    "\\.(css|less)$": "<rootDir>/assetsTransformer.js",
    uuid: require.resolve("uuid"),
  },
  testResultsProcessor: "jest-junit",
  transformIgnorePatterns: [
    "jest-runner",
    "node_modules/(?!(jest-)?@?react-native|@react-native-community|@react-navigation|@rneui|@sentry/react-native)",
  ],
  testTimeout: 120000,
  globalSetup: "detox/runners/jest/globalSetup",
  globalTeardown: "detox/runners/jest/globalTeardown",
  reporters: ["detox/runners/jest/reporter"],
  testEnvironment: "detox/runners/jest/testEnvironment",
  verbose: true,
};
0

There are 0 best solutions below