Capybara tests using headless chromium not reading React code - reactjs

Problem
When running e2e tests with Capybara in my Rails/React app, whenever the javascript uses React, it has trouble executing the code. <div id="root"></div> remains empty while the code renders properly locally and in docker. I've duplicated this running the capybara tests locally as well. What is odd is that if I add a document.getElementById("root").innerText = "Foo bar" it runs the javascript and either doesn't know how to execute the ReactDOM.render bit or just doesn't. When running tests against Stimulus code, it renders properly. For funsies, I downgraded to react 16 but had the same issue.
Background:
We use Vite js to bundle the javascript which I don't think is related but definitely could be. The app runs in an alpine docker environment but I can reproduce it locally so I don't think its specific environment related. Rails routes are empty endpoints that serves an empty html page with a #root div for React to hydrate and route accordingly. We're not using the react-rails gem. In the output of the html page, the assets are all pointing at the correct js files and the code does exist in those files.
Code
The main runtime code for capybara tests. I've included the code for the small react snippet I tested with the capybara test and the output of the print page.html.
app/javascript/entrypoints/test.jsx
import React from "react"
import ReactDOM from "react-dom"
// If this is uncommented, this line runs correctly but is not
// replaced by the "Hello World" in the `render` method
// document.getElementById("root").innerText = "Foo bar"
// This never gets run or is run incorrectly
ReactDOM.render(
<div>Hello World</div>,
document.getElementById("root")
)
test.html.haml (yes, I know haml is awful)
!!!
%html{lang: :en}
%head
= vite_client_tag
= vite_react_refresh_tag
= vite_javascript_tag "test.jsx"
%body
#root
react_test_spec.rb
require "rails_helper"
RSpec.describe "Testing react", type: :feature, js: true do
describe "just checking", :with_csrf do
before { visit test_home_path }
subject { page }
it "renders react" do
print page.html
expect(page).to have_content "Hello World"
end
end
end
print page.html output
<html lang="en"><head>
<script src="/vite-test/assets/test.92ee76c9.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite-test/assets/jsx-dev-runtime.ddafb254.js" as="script" crossorigin="anonymous">
</head>
<body>
<div id="root"></div>
</body></html>
Config/setup code. Package versions, capybara/vite configuration, and etc.
package.json
// react related packages
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "6",
// vite related packages
"stimulus-vite-helpers": "^3.0.0",
"vite": "^2.9.1",
"vite-plugin-ruby": "^3.0.9",
"vite-plugin-stimulus-hmr": "^3.0.0",
"#vitejs/plugin-react": "^1.3.2",
// babel related packages
"#babel/core": "^7.0.0-0",
"#babel/preset-react": "^7.16.7",
"#babel/preset-typescript": "^7.17.12",
"#babel/eslint-parser": "^7.17.0",
"#babel/plugin-transform-runtime": "^7.18.2",
"#babel/preset-env": "^7.17.10",
"babel-jest": "^27.5.1",
"babel-plugin-macros": "^3.1.0",
capybara.rb
Capybara.register_driver :chrome_headless do |app|
options = ::Selenium::WebDriver::Chrome::Options.new
options.add_argument("--headless")
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--window-size=1400,1400")
Capybara::Selenium::Driver.new(app, browser: :chrome, capabilities: [options])
end
Capybara.javascript_driver = :chrome_headless
vite.config.ts
export default defineConfig({
build: {
sourcemap: true,
},
plugins: [RubyPlugin(), react(), StimulusHMR()],
})
vite.json
{
"all": {
"sourceCodeDir": "app/javascript",
"watchAdditionalPaths": []
},
"development": {
"autoBuild": true,
"publicOutputDir": "vite-dev",
"port": 3036
},
"test": {
"autoBuild": true,
"publicOutputDir": "vite-test",
"port": 3037
}
}
some packages in Dockerfile.development. Also duplicated this issue locally using chromedriver
RUN apk add \
build-base \
chromium \
chromium-chromedriver \

Your JS assets are likely built differently in dev and test modes - and this sounds like you have a JS bug which is preventing the hydration. Add a pause to your test, run it in non-headless mode and look at the developer console for JS/network errors

Related

unable to use my component library based on Material UI with a nextjs application (both typescript)

On one side I have a component library using react, mui and emotion as peerDep.
package.json
"peerDependencies": {
"#emotion/react": "^11.10.5",
"#emotion/styled": "^11.10.5",
"#mui/icons-material": "^5.10.15",
"#mui/material": "^5.10.15",
"react": "18.2.0",
"react-dom": "18.2.0"
}
rollup config (note the regexp for #emotion and #mui)
external: [
'react',
'react-dom',
/#emotion\/.*/,
/#mui\/.*/,
],
output: [
{
file: 'dist/index.cjs.min.js',
format: 'cjs',
sourcemap: true,
plugins: [terser()],
},
{
file: 'dist/index.esm.min.js',
format: 'esm',
sourcemap: true,
plugins: [terser()],
},
],
plugins: [
resolve({ browser: true }),
...
typescript({ tsconfig: './tsconfig.json' }),
],
},
Lib is published with npm.
On the other side, I have an application (nextjs and storybook, webpack5)
Nothing special on the config on this side.
When I run storybook, it works fine.
But when I run dev, I get this error:
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.
at Bidon (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#msio/common-frontend/dist/index.cjs.min.js:629:18)
at div
at /home/smallet/Dev/sandbox/spike-nextjs/node_modules/#emotion/react/dist/emotion-element-b63ca7c6.cjs.dev.js:51:25
at Stack (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#mui/material/node/Stack/Stack.js:111:49)
at Home (webpack-internal:///./pages/index.tsx:32:81)
at Layout (webpack-internal:///./components/Layout/Layout.tsx:14:19)
at InnerThemeProvider (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#mui/system/ThemeProvider/ThemeProvider.js:19:39)
at ThemeProvider (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#mui/private-theming/node/ThemeProvider/ThemeProvider.js:39:5)
at ThemeProvider (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#mui/system/ThemeProvider/ThemeProvider.js:38:5)
at App (webpack-internal:///./pages/_app.tsx:27:16)
at StyleRegistry (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/styled-jsx/dist/index/index.js:449:36)
at PathnameContextProviderAdapter (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/next/dist/shared/lib/router/adapters.js:60:11)
at AppContainer (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/next/dist/server/render.js:289:29)
at AppContainerWithIsomorphicFiberStructure (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/next/dist/server/render.js:325:57)
at div
at Body (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/next/dist/server/render.js:612:21)
I tried to change the tsconfig file but didn't change anything
I tried to put only #mui and not #emotion as a peer dependency, but it didn't work.
First for the bundle size it is better to have those libs as peer dependency,
but moreover, I have a ThemeProvider initialized with a custom theme and I want it to be shared between lib and app.
I spent a lot of time looking for something wrong in the Theme, but in fact, the error was here because it is the first component imported from my lib.
I added a dummy component with just a div => the app works fine.
But as soon as I add an import on #mui lib, like :
import Paper from '#mui/material/Paper';
I get the crash.
It works with storybook.
I also created a simple react app and it works, so it doesn't seem to be on the side on the library.
I suppose it comes from some config on Nextjs side.

Invalid hook creating Liferay React component

I'm trying to create custom react components based on Liferay's Clay components.
Using e.g. just a ClayButton works, but as soon as i try to use hooks (like React.useState), the browser console tells me:
Minified React error #321; visit https://reactjs.org/docs/error-decoder.html?invariant=321 for the full message
The full message tells me i could be using mismatching versions of react and react-dom. I'm not.
I also don't have 2 different versions of react, according to the test described there.
I created a minimal example module at https://github.com/ReFl3x0r/liferay-react-component-test which can be tested in a Liferay Gradle Workspace.
There's also an older thread in Liferay Forums discussing this error, but with no solution.
(https://liferay.dev/ask/questions/development/re-lr-7-3-react-portlet-invalid-hook-call)
What am i doing wrong?
EDIT:
Trying to point out the main code snippets.
First CustomButtonFail.es.js:
import React from 'react';
import ClayButton from '#clayui/button';
const CustomButton = () => {
const [name, setName] = React.useState('test');
return (
<ClayButton displayStyle='primary'>
TEST
</ClayButton>
);
}
export default CustomButton;
The package.json:
{
"dependencies": {
"#clayui/button": "^3.40.0",
"#clayui/css": "3.x",
"react": "^16.12.0",
"react-dom": "^16.12.0"
},
"devDependencies": {
"#liferay/npm-scripts": "47.0.0",
"react-test-renderer": "^16.12.0"
},
"name": "component-test",
"scripts": {
"build": "liferay-npm-scripts build"
},
"version": "1.0.0"
}
The view.jsp including the component (shortened):
<%#taglib uri="http://liferay.com/tld/react" prefix="react" %>
<div class="react-component-failing">
<react:component
module="js/CustomButtonFail.es"
/>
</div>
I finally got it working. Reducing package.json like this:
{
"devDependencies": {
"#liferay/npm-scripts": "47.0.0"
},
"name": "component-test",
"scripts": {
"build": "liferay-npm-scripts build"
},
"version": "1.0.0"
}
and adding a ".npmbundlerrc" in modules root with content:
{
"config": {
"imports": {
"frontend-taglib-clay": {
"#clayui/button": ">=3.40.0",
"#clayui/css": ">=3.x"
},
"#liferay/frontend-js-react-web": {
"react": ">=16.12.0"
}
}
}
}
did the trick.
Working example is at https://github.com/ReFl3x0r/liferay-react-component-test/tree/working

Sharing components in yarn workspaces (next and tailwindcss)

I've got a frontend (next.js) which has tailwindcss installed (config, postcss, ...) and everything works.
I've made another package (ui) which has the following package.json
{
"name": "ui",
"version": "1.0.0",
"private": true,
"peerDependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"autoprefixer": "^10.3.2",
"postcss": "^8.3.6",
"tailwindcss": "^2.2.7"
}
}
The problem is when is serve the ui locally everything works fine (the UI sees the styles of the component), but when deployed to vercel, the component has no styles in it.
The component (ui):
import React from 'react';
const Example = ({children}) => <button className='bg-blue-500 py-1 px-2'>{children}</button>
export default Example
And my next config (frontend)
const withTM = require('next-transpile-modules')(['bar'])
module.exports = withTM()
Is there a way of sharing the same tailwind.config.js ? Or anything to make it work.
Steps that I have made:
created the workspace
added frontend package (next, and then i installed tailwind with all the steps from their docs)
added the ui package (installed the peerDependencies, see above)
created the component
added the ui package as a dependency in the frontend, yarn install, and then imported the component
yarn dev, and the styles are applied locally.
deployed to vercel, the button has only the children , no styles
UPDATE:
The problem is caused by the purging process at build time.
Is there any way to specify in the tailwind config to purge also the ui package?
UPDATE2:
I've tried to add the package (i've renamed it to "#example/ui") to the purge in next.config.js
module.exports = {
purge: [
'./pages/**/*.{js,ts,jsx,tsx}',
'./components/**/*.{js,ts,jsx,tsx}',
'./node_modules/#example/ui/src/*.{js,ts,jsx,tsx}'
],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
The code in the UI package is inside of the src, has only the index.tsx file. I mention, locally still works fine.
Solved it !
In the purge array , I needed to add the node modules from the root of the project, not of the frontend

react-responsive-accordion -> getting "Uncaught TypeError: Cannot read property 'number' of undefined"

I am new to react js and creating a simple react app to display an accordion by using 'react-responsive-accordion'. followed the link 'https://www.npmjs.com/package/react-responsive-accordion' to do so, but i am getting below the error
Below is the code:
package.json class:
{
"name": "reactapp",
"version": "1.0.0",
"description": "React JS Application",
"scripts": {
"start": "webpack-dev-server"
},
"author": "",
"license": "ISC",
"dependencies": {
"babel-core": "^6.10.4",
"babel-loader": "^6.2.4",
"babel-polyfill": "^6.9.1",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.11.1",
"babel-preset-stage-0": "^6.5.0",
"babel-register": "^6.9.0",
"react": "^16.1.1",
"react-collapsible": "^2.0.3",
"react-dom": "^16.1.1",
"react-responsive-accordion": "^1.0.0",
"webpack": "^1.13.1",
"webpack-dev-middleware": "^1.6.1",
"webpack-dev-server": "^1.14.1",
"webpack-hot-middleware": "^2.11.0"
}
}
main.js class:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(<App />, document.getElementById('app'));
App.jsx class:
import React from 'react';
import Accordion from 'react-responsive-accordion';
class App extends React.Component {
render() {
return (
<div>
<Accordion>
<div data-trigger="A nifty React accordion component">
<p>So this is an Accordion component that used the react-collapsible component. How handy.</p>
</div>
<div data-trigger="What the difference?" data-trigger-when-open="THAT is the difference!">
<p>An Accordion is different to a Collapsible in the sense that only one "tray" will be open at any one time.</p>
</div>
<div data-trigger="I'm responsive and I have a little secret. Look inside.">
<p>And this Accordion component is also completely repsonsive. Hurrah for mobile users!</p>
</div>
</Accordion>
</div>
);
}
}
export default App;
webpack.config.js:
var config = {
entry: './main.js',
output: {
path: '/',
filename: 'index.js'
},
devServer: {
inline: true,
port: 8089
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015','react']
}
},
{
test: /\.scss/,
loader: 'style-loader!css-loader!sass-loader'
},
{
test: /\.css$/,
loader: "style-loader!css-loader"
}
]
}
}
module.exports = config;
Any help to resolve this issue would be appreciated.
Ran into this same problem on another package and decided to downgrade React for now. The problem seems to be caused by a change in React. If you look deeper into the code, it actually complains about:
propTypes: {
transitionTime: _react2.default.PropTypes.number,
This happens because the way to handle PropTypes has changed. Apparently the old way to handle them has been removed in version 16. You can read more here: https://reactjs.org/docs/typechecking-with-proptypes.html
And possible solutions that I can think of:
Use an older version of react (like 15.6.1 in the example given by Nandu Kalidindi).
get the module fixed
Accordion expects an array of children this.props.children with data-trigger according to this https://github.com/glennflanagan/react-responsive-accordion/blob/master/src/Accordion.js#L15.
Modify your render method to something like this so as to provide a valid children props. Or check out this working example WORKING DEMO
render() {
return (
<div>
<Accordion>
<div data-trigger="A nifty React accordion component">
<p>So this is an Accordion component that used the react-collapsible component. How handy.</p>
</div>
<div data-trigger="What is the difference?" data-trigger-when-open={<div><span style={{color: "yellow"}}>THAT</span> is the difference!</div>}>
<p>An Accordion is different to a Collapsible in the sense that only one "tray" will be open at any one time.</p>
</div>
</Accordion>
</div>
);
}
package only works for react 15 per the github repo. Handling of proptypes changed in 16
Even I faced the same issue but it got resolved. The changes were made in package.json only .Below are the changes done. Try to give exact value for all of these below:
"prop-types": "^15.6.2"
"react": "^15.4.0"
"react-dom": "^15.4.0"
Earlier these were 16.x.x .I just changed to a version lower then 15.5.x which I read somewhere is stable.
After this go to the terminal in my case or cmd in windows and go the folder where your react-app is created ie where node_modules, src are and then press
npm install --save
npm start
This may be helpful to some people.
My case was like this, by used React.PropType, I got error 'Uncaught TypeError: Cannot read property 'number' of undefined'
Header.propTypes = {
userid: React.PropType.number
};
Then I changed to use PropTypes from 'prop-types':
import PropTypes from 'prop-types';
...
Header.propTypes = {
userid: PropTypes.number
};
Please refer to https://reactjs.org/docs/typechecking-with-proptypes.html for more details, I am using:
"react": "^16.6.1",
"react-dom": "^16.6.1",
install react-collapsible-react16 for react version 16 and up. This works for me.

Webpack, typescript and angularjs: issue while creating new project

I am new to webpack and typings, i am trying to setup new project.
I have globally installed the webpack.
Issues are
Even it is not allowing me to put the code of entrypoint.ts in a module like
module taxCalculator{
[.....entrypoint.ts code.......]
}
webpack build works fine when i avoid point 1 but i am not able to use the testClass in entrypoint.ts while creating mainPageController. when i load the application get following error in console:
taxCalculator.bundle.js:13307 ReferenceError: taxCalculator is not defined
In my entrypoint.ts vs 2013 shows the red error line below the 'require' text.
I have already referred a lot on internet but could not find anything and wasted 4-5 hours of mine, can someone please guide me what i am doing wrong.
Following are my files for reference.
Package.json
{
"name": "taxcalculator",
"version": "1.0.0",
"description": "To Calculate Tax",
"scripts": {
"build": ""
},
"author": "temp",
"license": "ISC",
"devDependencies": {
"ts-loader": "1.3.3",
"typescript": "2.0.0",
"typings": "2.1.0",
"webpack": "1.14.0"
},
"dependencies": {
"angular": "1.5.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "es5"
}
}
typings.json
{
"dependencies": {
"angular": "registry:dt/angular#1.5.0+20170111164943"
}
}
entrypoint.ts
/// <reference path="../../typings/index.d.ts"/>
/// <reference path="TestClass.ts"/>
var angular = require('angular');
var app = angular.module('taxCalculatorApp', []);
app.controller("mainPageController", ["$scope", function(scope) {
scope.testString = "My String Value";
scope.myObj = new taxCalculator.TestClass("Constructor string");
}])
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8"/>
<title>Tax Calculator</title>
</head>
<body>
<div ng-app="taxCalculatorApp">
<div ng-controller="mainPageController">
<div>{{testString}}</div>
<div>{{myObj.myString}}</div>
</div>
Loading.....!!
</div>
<script src="../dist/taxCalculator.bundle.js"></script>
</body>
</html>
testclass.ts
module taxCalculator {
export class TestClass {
constructor(public myString) {
}
}
}
In the compilerOptions section of your tsConfig file, add the following:
"moduleResolution" : "node", // or "classic"
"module" : "amd",
The moduleResolution section determines how the tsc compiler looks for modules. A full description can be found on the typescript docs.
The module section describes how each of your modules will be written out. By default this will be CommonJS, which is not what you want. It should probably be amd, which is a front-end friendly module system.
It's a good idea to take a deeper look at all the compiler options and see if any more are relevant for your project. In general, starting off new projects with strict null checks and no implicit any will help you in the long run.

Resources