You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Capybara使用无头Chromium运行e2e测试时无法渲染React代码

问题描述

在Rails/React应用中使用Capybara运行端到端(e2e)测试时,所有基于React编写的JavaScript代码均无法正常执行:页面中<div id="root"></div>节点始终为空,但相同代码在本地开发环境、Docker环境下均可正常渲染,本地直接运行Capybara测试也可稳定复现该问题。
异常现象具体表现为:

  • 若手动添加document.getElementById("root").innerText = "Foo bar"原生JS代码,该行逻辑可正常执行
  • ReactDOM.render相关逻辑要么无法被识别执行、要么完全不运行
  • 针对Stimulus框架编写的测试代码可正常渲染
  • 为排查问题尝试将React版本降级至16后,问题仍未解决
背景信息
  • 项目使用Vite.js进行JavaScript代码打包,初步判断该问题与Vite无关但不排除其关联性
  • 应用运行于Alpine Docker环境,因本地环境可直接复现问题,排除环境特异性导致故障的可能
  • 项目中Rails路由对应空白端点,仅返回包含#root节点的空白HTML页面,供React完成水合与路由跳转逻辑,项目未使用react-rails gem
  • 测试运行时返回的HTML页面中,静态资源路径均指向正确的JS文件,且React业务代码确实存在于对应加载的JS文件中
复现代码

以下为Capybara测试相关核心运行代码,包含测试用最小React片段、Capybara测试用例、print page.html输出结果:

  • 入口文件 app/javascript/entrypoints/test.jsx
import React from "react"
import ReactDOM from "react-dom"

// 取消注释后该行可正常执行,但不会被render方法中的"Hello World"替换
// document.getElementById("root").innerText = "Foo bar"

// 以下逻辑始终无法正常执行
ReactDOM.render(
  <div>Hello World</div>,
  document.getElementById("root")
)
  • 视图模板 test.html.haml(原作者备注:已知晓HAML存在使用缺陷)
!!!
%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 输出结果
<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>
相关配置

以下为项目配置与依赖信息,包含包版本、Capybara/Vite配置等内容:

  • package.json 相关依赖片段
{
  // react 相关依赖
  "react": "^17.0.2",
  "react-dom": "^17.0.2",
  "react-router-dom": "6",

  // vite 相关依赖
  "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 相关依赖
  "@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配置 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配置 vite.config.ts
export default defineConfig({
  build: {
    sourcemap: true,
  },
  plugins: [RubyPlugin(), react(), StimulusHMR()],
})
  • Vite环境配置 vite.json
{
  "all": {
    "sourceCodeDir": "app/javascript",
    "watchAdditionalPaths": []
  },
  "development": {
    "autoBuild": true,
    "publicOutputDir": "vite-dev",
    "port": 3036
  },
  "test": {
    "autoBuild": true,
    "publicOutputDir": "vite-test",
    "port": 3037
  }
}
  • Dockerfile.development 相关依赖安装片段(本地使用chromedriver运行测试时同样可复现该问题)
RUN apk add \
  build-base \
  chromium \
  chromium-chromedriver \

问题原因

核心问题是测试环境下错误加载了仅开发环境需要的Vite热更新相关标签,导致React代码执行中断:

  1. vite_client_tag和vite_react_refresh_tag是Vite开发服务器运行时才需要的标签,用于热更新、React快速刷新功能,测试环境下Vite不会启动开发服务器,仅通过autoBuild构建静态资源,这两个标签指向的资源会加载失败。
  2. @vitejs/plugin-react@1.3.2在非production模式(包括test模式)下构建React代码时,会自动注入React Refresh相关的运行时调用($RefreshReg$、$RefreshSig$全局函数),但测试环境下没有加载对应的刷新运行时脚本,会直接抛出$RefreshReg$ is not defined的JS错误。
  3. 错误发生在React相关代码初始化阶段,因此写在React逻辑前的原生JS可以正常执行,不依赖React Refresh的Stimulus代码也可正常运行,但ReactDOM.render逻辑因前面的报错中断,始终无法执行,降级React版本也无法解决该问题。
解决方案
  1. 修正视图模板,仅在Vite开发服务器实际运行时加载热更新相关标签,修改test.html.haml:
!!!
%html{lang: :en}
  %head
    -# 仅开发环境热更新需要以下两个标签,测试/生产环境不加载
    - if ViteRuby.instance.dev_server_running?
      = vite_client_tag
      = vite_react_refresh_tag
    = vite_javascript_tag "test.jsx"

  %body
    #root
  1. (可选)优化Vite测试环境配置,避免构建时注入开发环境专属代码,修改vite.config.ts:
export default defineConfig(({ mode }) => ({
  build: {
    sourcemap: true,
  },
  plugins: [RubyPlugin(), react(), StimulusHMR()],
  // 测试环境下模拟React Refresh全局变量,避免极端场景下的未定义报错
  define: mode === 'test' ? {
    $RefreshReg$: '() => {}',
    $RefreshSig$: '() => () => {}'
  } : {}
}))
  1. (可选)测试执行前预构建Vite测试环境资源,避免autoBuild延迟导致的资源404问题,在测试启动命令前增加执行:
bin/vite build --mode test
  1. (排查辅助)如果修改后仍有问题,在RSpec配置中增加浏览器日志打印,可直接看到具体JS报错:
RSpec.configure do |config|
  config.after(type: :feature, js: true) do |example|
    if example.exception
      puts "=== 浏览器控制台日志 ==="
      page.driver.browser.manage.logs.get(:browser).each do |log|
        puts "[#{log.level}] #{log.message}"
      end
    end
  end
end

内容的提问来源于stack exchange,提问作者Robby Kim

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 01:12:46