Error running tests with Detox in Expo React Native project - react-native

When I try to run my tests using detox in a React Native Expo project, I get the following error:
detox[18834] WARN: [Client.js/PENDING_REQUESTS] App has not responded to the network requests below:
(id = -1000) isReady: {}
That might be the reason why the test "Login workflow should have login screen" has timed out.
detox[18834] INFO: Login workflow: should have login screen [FAIL]
FAIL e2e/firstTest.e2e.js (137.697 s)
Login workflow
✕ should have login screen (120015 ms)
● Login workflow › should have login screen
thrown: "Exceeded timeout of 120000 ms for a hook.
Use jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test."
7 | });
8 |
> 9 | it('should have login screen', async () => {
| ^
10 | await expect(element(by.id('loginFormTitle'))).toBeVisible()
11 | });
12 |
at firstTest.e2e.js:9:3
at Object.<anonymous> (firstTest.e2e.js:8:1)
detox[18833] ERROR: [cli.js] Error: Command failed: node_modules/.bin/jest --config e2e/config.json '--testNamePattern=^((?!:android:).)*$' --maxWorkers 1 e2e
I am running iPhone 11 Pro simulator, and the expo app is already running in a separate server. I also have a Exponent.app in my /bin folder, which I downloaded from Expo's website. The logic in my test case doesn't require any timeout and it involves just a simple login screen.
Any solution for this error?

I had a similar issue using the latest version of Expo (v39).
It seems like the issue is that Detox waits for an app is ready event before running the tests, but this doesn’t get triggered with the most recent versions of the Expo SDK.
The solution I ended up with was to create a standalone build of the app and run Detox against that.
My .detoxrc.json looks like:
{
...,
"configurations": {
"ios": {
"type": "ios.simulator",
"build": "expo build:ios -t simulator",
"binaryPath": "bin/myapp.app",
}
}
}

As of December 2020, I'm using detox 17.14.3 with Expo 39.0.5 and I've managed to solve this issue without needing to use a standalone build. Patch and explanation provided below.
It turns out that detox-expo-helpers is overriding an environment variable (specifically SIMCTL_CHILD_DYLD_INSERT_LIBRARIES) to specify the path to the ExpoDetoxHook framework (provided by the expo-detox-hook package). Well, that update to the environment now happens too late in the cycle. It takes place when running Detox's reloadApp, but by then, Detox has started up Jest which has workers running with their own copy of process.env. Workers get a copy of process.env at the time of creation, and they are not shared with the parent process, so the changes this library makes to environment variables are not reflected inside Jest workers. The hook framework is not available to the running application, and so the Detox is stuck waiting for a ready signal that doesn't come.
Detox's launchApp for iOS simulator uses SIMCTL_CHILD_DYLD_INSERT_LIBRARIES to specify it's own library, but because the environment vars are isolated in the worker, it does not see the change this package makes. To solve this, I modified this library to expose the changes to process.env as a separate exported function, and then I call that much earlier in the test lifecycle to ensure it is set before any workers are started. Here is a patch compatible with patch-package.
# file patches/detox-expo-helpers+0.6.0.patch
diff --git a/node_modules/detox-expo-helpers/index.js b/node_modules/detox-expo-helpers/index.js
index 864493b..3147a55 100644
--- a/node_modules/detox-expo-helpers/index.js
+++ b/node_modules/detox-expo-helpers/index.js
## -45,7 +45,16 ## function resetEnvDyldVar(oldEnvVar) {
}
}
-const reloadApp = async (params) => {
+let initialized = false;
+let detoxVersion;
+let oldEnvVar;
+const init = () => {
+ if (initialized) {
+ return;
+ }
+
+ initialized = true;
+
if (!fs.existsSync(expoDetoxHookPackageJsonPath)) {
throw new Error("expo-detox-hook is not installed in this directory. You should declare it in package.json and run `npm install`");
}
## -56,12 +65,16 ## const reloadApp = async (params) => {
throw new Error ("expo-detox-hook is not installed in your osx Library. Run `npm install -g expo-detox-cli && expotox clean-framework-cache && expotox build-framework-cache` to fix this.");
}
- const detoxVersion = getDetoxVersion();
- const oldEnvVar = process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES;
+ detoxVersion = getDetoxVersion();
+ oldEnvVar = process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES;
if (semver.gte(detoxVersion, '9.0.6')) {
process.env.SIMCTL_CHILD_DYLD_INSERT_LIBRARIES = expoDetoxHookFrameworkPath;
}
+}
+
+const reloadApp = async (params) => {
+ init();
const formattedBlacklistArg = await blacklistCmdlineFormat(params && params.urlBlacklist);
const url = await getAppUrl();
## -121,5 +134,6 ## module.exports = {
getAppUrl,
getAppHttpUrl,
blacklistLiveReloadUrl,
+ init,
reloadApp,
};
With that in place, I modified my e2e/environment.js file to look like the following. The addition is the initExpo call. When it runs this early in the test lifecycle, the environment is modified before any workers are started, and as a result, calls to reloadApp no longer wait indefinitely.
/* eslint-disable import/no-extraneous-dependencies */
const { init: initExpo } = require('detox-expo-helpers');
const { DetoxCircusEnvironment, SpecReporter, WorkerAssignReporter } = require('detox/runners/jest-circus');
class CustomDetoxEnvironment extends DetoxCircusEnvironment {
constructor(config) {
super(config);
initExpo();
// Can be safely removed, if you are content with the default value (=300000ms)
this.initTimeout = 300000;
// This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level.
// This is strictly optional.
this.registerListeners({
SpecReporter,
WorkerAssignReporter,
});
}
}
module.exports = CustomDetoxEnvironment;

Related

React-Native Expo Export Web Receiving Error 'Invalid Project Root' While Building Production App

Hi I am receiving the error that the project root is invalid. I will also add that I am using expo alongside my project.
This happens when executing the command npx expo export:web
Also happens when executing the command npx expo build
webpack.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: ["react-native-reanimated/plugin"],
};
};
metro.config.js
(Not sure if metro is relevant as I believe it is more for development purposes...)
const { getDefaultConfig } = require("#expo/metro-config");
const defaultConfig = getDefaultConfig(__dirname);
defaultConfig.resolver.assetExts.push("cjs");
module.exports = defaultConfig;
react-native.config.js
module.exports = {
project: {
ios: {},
android: {}, // grouped into "project"
web: {},
},
assets: ["./assets/fonts"], // stays the same
};
npm start works fine and everything works accordingly in the browser. The goal is to build this for production and begin hosting on a web server.
I am hoping that I am simply missing a location to a directory in a config file but any insight is appreciated.
First, just run it with expo start, after it started press w.

webpack issue with latest Expo (version 46) and React-Native-Elements

A clean-slate creation of an Expo-based React-Native app (npx create-expo-app my-app) and then installing react-native-elements runs fine on IOS, but generates the error below for running on the web. I also tried using the template provided by react-native-elements but wind-up with the same result.
Module parse failed: Unexpected token (7:58)
You may need an appropriate loader to handle this file type, currently no loaders are
configured to process this file. See https://webpack.js.org/concepts#loaders
| import { defaultSpacing } from './theme';
| import { lightColors } from './colors';
> const isClassComponent = (Component) =>
Boolean(Component?.prototype?.isReactComponent);
| const combineByStyles = (propName = '') => {
| if (propName.endsWith('Style') || propName.endsWith('style')) {
at ./node_modules/#rneui/themed/dist/config/withTheme.js```

BDD with Cypress & Vite (Vue 3) & Cucumber

I've currently managed to implement Cucumber BDD tests within a Vitejs + Vue 3 as follows:
I start and run the development server with:
$ yarn dev
And then in a separate window I run the Cypress test runner:
$ yarn cy:run
Which corresponds to:
...,
"scripts": {
...
"cy:run": "cypress run -q",
...
},
...
In my package.json. The output of this, is 1 test passing.
So far, so good. I then came across the #cypress/vite-dev-server package, and implemented it with the cucumber preprocessor inside /cypress/plugins/index.ts as follows:
/// <reference types="cypress" />
const path = require('path')
const { startDevServer } = require('#cypress/vite-dev-server')
const browserify = require('#cypress/browserify-preprocessor')
const cucumber = require('cypress-cucumber-preprocessor').default
/**
* #type {Cypress.PluginConfig}
*/
module.exports = (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => {
on('dev-server:start', options => {
return startDevServer({
options,
viteConfig: {
configFile: path.resolve(__dirname, '..', '..', 'vite.config.ts')
}
})
})
const cucumberOptions = {
...browserify.defaultOptions,
typescript: require.resolve('typescript')
}
on('file:preprocessor', cucumber(cucumberOptions))
return config
}
So, it looks like the #cypress/vite-dev-server package doesn't accept what I am trying to do with Cypress & Cucumber.
Has anyone managed to get Cypress & Cucumber BDD working with Vite in a seamless fashion?
I've also looked at the wait-on module, running the following:
yarn dev & wait-on http://localhost:8099
But it doesn't seem to be waiting, only the Vite server runs? So I can't then run the cypress command I need ...
The #cypress/vite-dev-server is for component testing, not e2e testing. The cypress-cucumber-preprocessor on the other hand is for compiling e2e specs.
In e2e testing, the app runs independently from tests, so you can use vite for running the dev server, but it has nothing to do with tests. If you want to use vite config for compiling tests you can use cypress-vite instead of cypress-cucumber-preprocessor.

_reactNative.Keyboard.removeListener is not a function

I just upgraded from RN 0.63.2 to 0.64.2 then to 0.65.0-rc.3. Now I started to get this exception while trying to navigate between screens: _reactNative.Keyboard.removeListener is not a function.
The problem seems to be in BottomTabBar.js. The code snippet is below.
The React-navigation version is 4.4.0. React-navigation-tabs version is 1.2.0.
I checked the Keyboard class' methods and actually, I CAN see a method called "removeListener". Even the auto-complete shows it.
Also, I can see in the react-native documentation that this method exists:
So, I don't see why I am getting this exception. Any help is much appreciated.
Updating the react-navigation version could be a solution, not sure about that, but I would like to avoid that path if possible since it has been working perfectly till now.
Thanks.
Edit: Just realized this exception is not thrown when navigating to a screen for the first time. It is thrown when I revisit a screen that has been previously visited.
Edit2: I am seeing this behavior on android. Haven't been able to compile the project on iOS yet.
Edit3: npm list react-native-tab-view command shows different versions for react-native-tab-view
├── react-native-tab-view#2.15.1
├─┬ react-navigation-drawer#1.4.0
│ └── react-native-tab-view#1.4.1
└─┬ react-navigation-tabs#1.2.0
└── react-native-tab-view#1.4.1
I did npm install react-native-tab-view#1.4.1 but nothing changed. I mean the output of the above npm list command changed and it all showed version 1.4.1 for react-native-tab-view but the behavior did not change.
removeListener method has been deprecated. It is suggested to call remove on the subscriptionEvent rather than calling removeListener.
Replace those lines as follows and use patch-package to commit your changes -
componentDidMount() {
if (Platform.OS === 'ios') {
this.keyboardWillShow = Keyboard.addListener('keyboardWillShow', this._handleKeyboardShow);
this.keyboardWillHide = Keyboard.addListener('keyboardWillHide', this._handleKeyboardHide);
} else {
this.keyboardDidShow = Keyboard.addListener('keyboardDidShow', this._handleKeyboardShow);
this.keyboardDidHide = Keyboard.addListener('keyboardDidHide', this._handleKeyboardHide);
}
}
componentWillUnmount() {
if (Platform.OS === 'ios') {
this.keyboardWillShow?.remove();
this.keyboardWillHide?.remove();
} else {
this.keyboardDidShow?.remove();
this.keyboardDidHide?.remove();
}
}
This fixed my issue:
modify node_modules/react-native-gifted-chat/lib/MessageContainer.js
this.attachKeyboardListeners = () => {
const { invertibleScrollViewProps: invertibleProps } = this.props;
if (invertibleProps) {
- Keyboard.addListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
- Keyboard.addListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
- Keyboard.addListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
- Keyboard.addListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
+ this.willShowSub = Keyboard.addListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
+ this.didShowSub = Keyboard.addListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
+ this.willHideSub = Keyboard.addListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
+ this.didHideSub = Keyboard.addListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
}
};
this.detachKeyboardListeners = () => {
const { invertibleScrollViewProps: invertibleProps } = this.props;
- Keyboard.removeListener('keyboardWillShow', invertibleProps.onKeyboardWillShow);
- Keyboard.removeListener('keyboardDidShow', invertibleProps.onKeyboardDidShow);
- Keyboard.removeListener('keyboardWillHide', invertibleProps.onKeyboardWillHide);
- Keyboard.removeListener('keyboardDidHide', invertibleProps.onKeyboardDidHide);
+ this.willShowSub?.remove();
+ this.didShowSub?.remove();
+ this.willHideSub?.remove();
+ this.didHideSub?.remove();
};
then, run:
# run patch-package to create a .patch file
npx patch-package react-native-gifted-chat
# commit the patch file to share the fix with your team
git add patches/some-package+0.16.3.patch
git commit -m "fix MessageContainer.js in react-native-gifted-chat"
Based on this response:
https://github.com/FaridSafi/react-native-gifted-chat/issues/2090#issuecomment-901812607
I solved this modifying the file in node_modules/react-native/Libraries/Components/Keyboard/Keyboard.js and persisting the change using the patch-package library
You need rename the removeEventListener function to removeListener
Then you run in the project folder:
yarn patch-package react-native
This will create a patches folder in the root and a file with the changes. That's it.
I had similar issue while I used "#react-navigation/material-bottom-tabs": "^6.2.4". I found that issue file was in react-native-paper#3.12.0 package. I have just updated it to react-native-paper#4.12.5 and it solved the issue.

CASL is not working properly in Vue Production Mode

I have defined some rules for specific roles, they all are working fine. But when I build vue project in production mode, all the rules gives false. Details are here below:
I have this file ability.js, which is giving me rules:
export const getRules = (role, userId) => {
const { can, cannot, rules } = AbilityBuilder.extract()
switch(role) {
case 'TENANT_ADMIN':
can('manage', 'all')
break
case 'TENANT_AGENT':
can('view', 'ConversationView')
break
case 'TENANT_AGENT_LIMITED':
can('view', 'ConversationView', { userId: userId })
break
}
return rules
}
I'm updating rules like this in App.vue (all values are valid)
this.$ability.update(getRules(role, userId))
I'm checking permissions using below code.
class ConversationView {
constructor(props) {
Object.assign(this, props)
}
}
this.$can('view', new ConversationView({ userId: Id }))
Now, when I run this code in local/development mode. It is working fine (giving me true where it needs to), but when I generate a production build it is not working as expected (always gives me false)
Development Build Command:
vue-cli-service build --mode local --modern
Development Build .env.local
VUE_APP_STAGE=development
NODE_ENV=development
Production Build Command:
vue-cli-service build --mode prod --modern
Production Build .env.prod
VUE_APP_STAGE=production
NODE_ENV=production
Let me know why this is happening.
Replicated the steps here.
Follow below link to view running and expected version:
LINK 01
Output:
Checking for '1' => true
Checking for 1 => false
Checking for '2' => false
Clone the same project in your local, or download it from [github 2
After running, we're getting this output:
Checking for '1' => false
Checking for 1 => false
Checking for '2' => false
Got the solution. Because of minification for production build code was not working as expected. Had to define modelName function to return proper name.
Follow the link for more info.
https://stalniy.github.io/casl/abilities/2017/07/21/check-abilities.html#instance-checks