process.env empty object in Cypress - reactjs

I am working on a React app bootstraped from create-react-app few years ago.
The app has a .env.dev file with many variables.
The start script is "start": "env-cmd -f .env.dev --use-shell \"react-scripts start\"",
React script version: "react-scripts": "^4.0.1",
When I console.log("gggg", process.env); I get all the variables.
When I:
describe('Login via API', () => {
it('Test login', () => {
console.log('teeest', process.env)
cy.login()
})
})
instead I get an empty object.
I tried to read the question How to use process.env variables in browser running by Cypress
however this question does not answer my question on how to make the process.env variables available to Cypress test files.
Also this question says to install dotenv. Dotenv comes with react-scripts, so no need to install it if the app was create by create-react-app.
I also tried this:
in cypress.config.js I added:
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
config.env = process.env
return config
}
}
})
And in the spec I try to get the variable defined in .env.dev file:
it('Test login', () => {
console.log('new', Cypress.env('REACT_APP_USERNAME'))
cy.login()
})
Still getting undefined.
Can anyone please help me to understand what's wrong? How can I make it work?
Edit:
According to an answer here I tried to install dotenv:
npm install dotenv --save
imported in the test:
import 'dotenv/config'
describe('Login via API', () => {
it('Test login', () => {
console.log('newwwww', Cypress.env('REACT_APP_USERNAME'))
console.log('teeest', process.env)
cy.login()
})
})
Npm start
npm run cypress:open
Result:
newwwww undefined
login-test.cy.js:7 teeest {}
Thanks

When you use "start": "env-cmd -f .env.dev --use-shell \"react-scripts start\"", the env-cmd command is specific to the process for the react app.
You would need the same to run before cypress opens it's process
package.json
{
...
"dependencies": {
...
},
"scripts": {
"cy:open": "env-cmd -f .env.dev cypress open",
...
}
}
Avoiding conflict with other env setting
I also recommend using the spread operator as shown below, otherwise you would lose any env var added in other ways, e.g command line additions.
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
config.env = {
...process.env, // add all process env var here
...config.env // plus any command line overrides
}
return config // return the altered config
},
},
env: {
login_url: '/login', // define some specific env var here
products_url: '/products'
}
});
Avoiding pollution of Cypress settings
If you take a look at Settings/Project Settings in the Cypress runner, you'll see a huge number of unnecessary settings which come from the general machine env var.
To pick just those with prefix REACT_,
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
const reactEnv = Object.keys(process.env).reduce((obj, key) => {
if (key.startsWith('REACT_')) {
obj[key] = process.env[key];
}
return obj;
}, {});
config.env = {
...reactEnv, // add REACT_ process env var here
...config.env // plus any command line overrides
}
return config // return the altered config
},
},
env: {
login_url: '/login', // define some specific env var here
products_url: '/products'
}
});

You're right about dotenv being included with react-scripts, but to access it in your test files you'll have to explicitly import it.
npm install dotenv
then at the top of your cypress code
import 'dotenv/config'
See usage instructions here
https://www.npmjs.com/package/dotenv

Related

Having trouble deploying React-Express app to Heroku

I'm trying to deploy a MERN app (built with create react app) to Heroku, but whenever I try to access the app URL, it returns with a 404 error.
I've checked the Heroku error log, which has returned the following errors:
app[web.1]: ls: cannot access '/app/build/static/js/*.js': No such file or directory
Error injecting runtime env: bundle not found '/app/build/static/js/*.js'. See: https://github.com/mars/create-react-app-buildpack/blob/master/README.md#user-content-custom-bundle-location
I've structured my project so that it runs on two different servers: client side on localhost:3000, which proxies requests to express at localhost:5000.
I've run npm run build, set up static middleware, and tried to configure my api calls/routes correctly, but it still isn't working. Any suggestions as to why, and how I can fix it? Details as follows:
Project Structure
+client
|
+-build
+-static
+-css
+-js
+-media
+-node_modules
+-public
+-src
|
+-components
+-App.js
+-index.js
//server
+-models
+-node-modules
+-package-lock.json
+-package.json
+-server.js
Proxy (in package.json):
"proxy": "http://localhost:5000"
Heroku build scripts (in client/package.json):
"scripts": {
"start": "react-scripts start",
"heroku-postbuild": "cd client && npm install --only=dev && npm install && npm run build",
Server config:
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));
//Middleware
app.use(express.static(path.join(__dirname, 'client/build')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.urlencoded())
app.use(cors())
app.get('*', (req,res) =>{
res.sendFile(path.join(__dirname+'/client/build/index.html'));
});
Here's how I;ve structured my APIs. Note: I've removed the 'localhost:5000' from the URL of my axios requests:
API call from React component:
useEffect(() => {
axios.get('/api/all-reviews')
.then(review => {
setReviews(review.data)
})
.catch(err => {
console.log(err)
})
},[])
Corresponding express route
app.get('/api/all-reviews', (req,res) => {
Review.find()
.then((result) => {
res.send(result)
})
.catch(err => {
console.log(err)
})
})
You have two options,
#1 - make all urls relative, e.g. fetch('/api/all-reviews'), and have both the frontend and backend running on the same server. Serve the static build files from your backend (found in the build folder after running npm run build, assuming you are using create-react-app) using the express.static middleware.
Note that you can do this in production while still relying on a proxy in development using process.env.NODE_ENV. An example implementation would be
// put this at the end of your server.js file
if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, '../client/build')));
}
#2 - run the backend and frontend on different servers, and just adjust the path based on whether the code is running in development or production
Just as an example:
const prefix = process.env.NODE_ENV === 'production' ? "http://heroku_app_address" : "http://localhost:5000"
function getUrl(relativeUrl) {
return prefix + "/" + relativeUrl;
}
fetch(getUrl('api/all-reviews'));

How to use process.env variables in browser running by Cypress

In the source code of my application (React based on create-react-app) I'm using env variables like so: process.env.REACT_APP_API_URL which are stored in my .env.* files.
But when I run the same application under the Cypress the process.env object is empty. How can I provide these variables to be used in React application when it's running under Cypress?
I know that I have a possibility to set Cypress env variables but it is not what I want - this is a different scope.
You can use the configuration API and do something like this on your plugins file. Set config.env = process.env which will set your entire node env for Cypress.
// cypress/plugins/index.js
module.exports = (on, config) => {
// modify env value
config.env = process.env
// return config
return config
}
You can also selectively assign the values that you want with config.env.YOUR_VAR = process.env.YOUR_VAR.
Updated in Cypress version 10.3.0 and above
In Cypress, environment variables (accessible via Cypress.env) doesn't share the same scope as OS-level environment variables. In order to make process.env variables available in Cypress, you should use a third party library, such as dotenv package, which is very popular.
npm install dotenv
Make sure this line of code sitting on top of your cypress.config.js
require('dotenv').config()
Now you're good to go using process.env, but only under that cypress.config.js file. As mentioned in another answer, you should leverage the Cypress.env() command by passing all process.env properties to Cypress environment variables, so that you can access those variables globally in Cypress
// cypress.config.js
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
config.env = {
...process.env,
...config.env
}
return config
}
}
})
Note that in Cypress version 10.0.0 and above, setupNodeEvents was added to replace the deprecated plugins file.
Now you can retrieve those variables by:
Cypress.env("your_proccess_env_property")
Create cypress.env.json file that contains your environment variables:
{
"api_url": "http://localhost:8080"
}
Then set process.env in your cypress/support/index.js:
...
before(() => {
process.env. REACT_APP_API_URL = Cypress.env("api_url");
})
For my use case, I was simply able to do the following.
// cypress.config.js
require('dotenv').config();
const { defineConfig } = require('cypress');
module.exports = defineConfig({
...,
env: {
...process.env,
},
});
Hopefully this helps anyone else in the future!
If you want to use the same variables, the same way as react app, while running only Cypress without an app (this is the reason why process.env is empty). You could add this in cypress.config.js
const dotenvOutput = require('dotenv').config()
then access the variables as
module.exports = defineConfig({
e2e: {
env: {
api_url: dotenvOutput.parsed.REACT_APP_API_URL,
},
You also need to make sure that the .env file is available where the cypress is ran.

Browserify cannot find module 'react-router-dom'

I try to play with react-router but can't make it working with browserify. I'm stuck with this error:
events.js:182
throw er; // Unhandled 'error' event
^
Error: Cannot find module 'react-router-dom' from '/usr/src/app'
at /usr/src/app/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:46:17
at process (/usr/src/app/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:173:43)
at ondir (/usr/src/app/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:188:17)
at load (/usr/src/app/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:69:43)
at onex (/usr/src/app/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:92:31)
at /usr/src/app/node_modules/browserify/node_modules/browser-resolve/node_modules/resolve/lib/async.js:22:47
at FSReqWrap.oncomplete (fs.js:152:21)
npm info lifecycle irregular_verbs#1.0.0~start: Failed to exec start script
My first js file is server.js which generate a bundle to load app.js.
I removed everything on my app.js file to be sure that no personal components create a conflict. So it is very light now! But still doesn't work.
My app.js file:
var React = require('react');
require('react-router-dom');
React.render(<div><p>Blop</p></div>, document.getElementById('base_ihm'));
Without the require('react-router-dom');, everything works!
My server.js:
var express = require('express');
var browserify = require('browserify');
var React = require('react');
var jsx = require('node-jsx');
var app = express();
// Constants
const PORT = 8080;
jsx.install();
// Enable compression
var compression = require('compression');
app.use(compression());
// Create a path name bundle.js which call app.js and apply browserify
app.use('/bundle.js', function(req, res) {
res.setHeader('content-type', 'application/javascript');
browserify('./app.js', {
debug: true
})
.transform('reactify')
.bundle()
.pipe(res);
});
// static ressources
app.use(express.static(__dirname + '/static/css'));
app.use(express.static(__dirname + '/static/images'));
// Main route
app.use('/', function(req, res) {
res.setHeader('Content-Type', 'text/html');
res.end(React.renderToStaticMarkup(
React.createElement(
'html', null,
// Header
React.createElement(
'head', null,
// Title
React.createElement('title', null, 'Irregular Verbs'),
// Meta
React.createElement('meta', {charSet: 'UTF-8'}, null),
React.createElement('meta', {name: 'viewport', content: 'width=device-width, initial-scale=1'}, null),
// Custom CSS
React.createElement('link', { rel: 'stylesheet', href: 'main.css' }, null)
),
// Body
React.DOM.body(
null,
React.DOM.div({
id: 'base_ihm',
dangerouslySetInnerHTML: {
__html: React.renderToString(React.createElement('div', null))
}
}),
// Use the path create just before
React.DOM.script({
src: '/bundle.js'
})
)
)
));
});
var server = app.listen(PORT, function() {
var addr = server.address();
console.log('Listening # http://%s:%d', addr.address, addr.port);
});
Is react-router well installed?
There is the content of my package.json file:
{
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.13.3",
"body-parser": "^1.15.2",
"node-jsx": "^0.13.3",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-router-dom": "^4.0.0",
"browserify": "^14.4.0",
"reactify": "^1.1.1",
"mysql": "^2.11.1"
}
}
I use docker and docker-compose. On my Dockerfile, I added RUN npm ls and RUN ls node_modules: I can see react-router and react-router-dom. So there are here!
Any cache with docker?
I rename my image to be sure to use the good one and not an old one.
I also restart my container using docker-compose up --force-recreate.
So I presume it is ok.
Erreur with server.js where browserify is used
I think I've made a mistake in my server.js file but I have no idea...
There are what I have tried with no success:
I try to add a global:true then global:false at my transform('reactify')
In my Dockerfile, I try to add npm install react-router-dom -g to be sure it was installed
I finaly upgraded my versions. And also tried react-router-dom in version 4.1.1
I replace the require('react-router-dom') by require('./node_modules/react-router-dom') but get the same error (except it was the new path which wasn't found).
Do you have any idea?
Thank you for your help!
Sorry for the late answer. It was finaly a Docker/Docker-compose problem...
Even using docker-compose up --force-recreate, the old image was still used.
I renamed my container in my docker-compose.yml file so at the next start, it created a new container with the new image.

Separate file for Environment Variable - ReactJS

I have this environment variable file that exports variable config depending on the NODE_END. Currently production and development variable are residing inside a one file, code below. How can I separate the file like development.js and production.js:
if(process.env.NODE_ENV === 'production') {
module.exports = {
API_URL: "https://test.co/api"
}
}
else {
module.exports = {
API_URL: "http://testbeta.co/api"
}
}
You can have a separate file to bridge them and export the intended module. For example, make three separate files index.js, development.js, and production.js on the same folder api.
// production.js
module.exports = {
API_URL: "https://e27.co/api"
}
// development.js
module.exports = {
API_URL: "http://e27beta.co/api"
}
// index.js
let exp
if (process.env.NODE_ENV === 'production') {
exp = require('./production.js')
} else {
exp = require('./development.js')
}
module.exports = exp
Then you can require it elsewhere like
// elsewhere.js
const api = require('path_to_api_folder')
If you using Webpack you can leverage the DefinePlugin plugin for exactly this purpose:
https://webpack.js.org/plugins/define-plugin/#use-case-service-urls
if(isProd) {
config.plugins.push(new webpack.DefinePlugin({
'SERVICE_URL': JSON.stringify("http://prod.example.com")
}));
} else {
config.plugins.push(new webpack.DefinePlugin({
'SERVICE_URL': JSON.stringify("http://dev.example.com")
}));
}
You could have 2 separate webpack configs, each with the appropriate SERVICE_URL'S.
// webpack.config.dev.js
plugins.push(new webpack.DefinePlugin({
'SERVICE_URL': JSON.stringify("http://dev-url.com")
}));
// webpack.config.prod.js
plugins.push(new webpack.DefinePlugin({
'SERVICE_URL': JSON.stringify("http://prod-url.com")
}));
To build, just pass webpack the appropriate config:
webpack --config webpack.config.prod.js
webpack --config webpack.config.dev.js

webpack + gulp + nodemon - configuration fro watch task

I'm stuck in a trouble with my project. I am using gulp + webpack to compile the client side. During the developing stage, I want to use nodemon to watch the file changing in the server directory and I want to find a suitable mode to watch the client side part of the project and re-run the webpack task.
Here is my gulpfile
gulp.task('clean:tmp', (cb) => {
del([paths.tmp]).then(paths => {
plugins.util.log('[clean]', paths);
cb();
});
});
gulp.task('serve', ['clean:tmp'], () => {
const config = require('./webpack.dev.config');
config.entry.app = paths.entry;
return gulp.src(paths.entry)
.pipe(webpack(config))
.pipe(gulp.dest('.tmp'));
});
gulp.task('watch', ['serve'], () => {
return nodemon({
script: `${rootServer}/`,
watch: ['server/*'],
});
});
The problem is that if I run the gulp watch with webpack.config.watch = true, webpack breaks the gulp pipe logic.
I also check out this answer Watch webpack.config.js and re-run webpack command in response to a file change
but I cannot apply the solution.
Any suggestion?

Resources