Home > OS >  Capybara tests using headless chromium not reading React code
Capybara tests using headless chromium not reading React code

Time:06-16

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 \

CodePudding user response:

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