teamcapybara / capybara

Acceptance test framework for web applications
http://teamcapybara.github.io/capybara/
MIT License
9.98k stars 1.44k forks source link

Capybara throwing error "undefined method `needs_server?' for :selenium_headless_in_container:Symbol" #2752

Closed nathanhamilton closed 3 months ago

nathanhamilton commented 3 months ago

Error Summary

I have a Docker container where I am trying to run Capybara using Selenium and when I run the tests, it throws the following error:

 NoMethodError:
            undefined method `needs_server?' for :selenium_headless_in_container:Symbol
          # /usr/local/rvm/gems/ruby-3.2.2/gems/capybara-3.40.0/lib/capybara/session.rb:92:in `initialize'
          # /usr/local/rvm/gems/ruby-3.2.2/gems/capybara-3.40.0/lib/capybara.rb:422:in `new'
          # /usr/local/rvm/gems/ruby-3.2.2/gems/capybara-3.40.0/lib/capybara.rb:422:in `block in session_pool'
          # /usr/local/rvm/gems/ruby-3.2.2/gems/capybara-3.40.0/lib/capybara.rb:317:in `current_session'
          # /usr/local/rvm/gems/ruby-3.2.2/gems/capybara-3.40.0/lib/capybara/dsl.rb:46:in `page'
          # /usr/local/rvm/gems/ruby-3.2.2/gems/capybara-3.40.0/lib/capybara/dsl.rb:52:in `visit'
          # ./spec/system/degree_management_spec.rb:104:in `block (2 levels) in <main>'

Current System Specs

Here are the current specs of what I'm running: Ruby Version: 3.2.2 Rails Version: 6.1.7.7 Puma Version: 6.4.2 capybara Version: 3.40.0 capybara-lockstep version: 2.2.0 selenium-webdriver version: 4.17.0 Selenium Docker Image: selenium/standalone-chrome:120.0.6099.224-chromedriver-120.0.6099.109-grid-4.17.0-20240123

Local System Setup

Here is the configuration I have:

spec/support/capybara.rb

require_relative './ip'

Capybara.register_driver :selenium_headless_in_container do |app|

  options = Selenium::WebDriver::Options.chrome
  options.add_argument('--headless=new')
  options.add_argument('--no-sandbox')
  options.add_argument('--disable-gpu')
  options.add_argument('--disable-dev-shm-usage')
  options.add_argument('--window-size=1400,1400')
  options.add_argument('--ignore-certificate-errors')

  desired_capabilities = Selenium::WebDriver::Remote::Capabilities.new
  desired_capabilities.browser_name = 'chrome'
  desired_capabilities.accept_insecure_certs = true

  url = 'http://selenium-chrome:4444/wd/hub'

  Selenium::WebDriver.logger.level = :debug # Use this if you need to debug the selenium driver

  Capybara::Selenium::Driver.new(
    app,
    browser: :remote,
    options: options,
    caps: desired_capabilities,
    url: url
  )

  Capybara.javascript_driver = :selenium_headless_in_container
end

spec/rails_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'capybara/rspec'
require 'rspec/rails'
require 'selenium-webdriver'
require 'support/capybara'
require 'factory_bot'
require 'spec_helper'

Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}

# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?

FactoryBot::SyntaxRunner.class_eval do
  include ActionDispatch::TestProcess
  include ActiveSupport::Testing::FileFixtures
end

RSpec.configure do |config|
  config.include ActionDispatch::TestProcess
  config.include Capybara::DSL
  config.mock_with :rspec
  config.use_transactional_fixtures = false
  config.infer_base_class_for_anonymous_controllers = false
  config.order = 'random'
  config.include FactoryBot::Syntax::Methods
  config.raise_errors_for_deprecations!
  config.filter_run focus: true
  config.run_all_when_everything_filtered = true
  config.file_fixture_path = File.expand_path('./fixtures', __dir__)

  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    Capybara.default_max_wait_time = 300
    Capybara.server_host = '0.0.0.0'
    Capybara.server_port = 4000
    Capybara.app_host = "http://test:4000"
    driven_by :selenium_headless_in_container
  end
end

def test_element(name)
  find test_selector(name)
end

def test_selector(name)
  "[data-test=\"#{name}\"]"
end

def clear_input(selector)
  first("#{selector} svg").click
end

build/docker-compose.yml

version: '3.5'
services:
  test:
    stdin_open: true
    tty: true
    build:
      context: ../.
      args:
        environment: 'test'
      dockerfile: build/ubuntu.Dockerfile
      target: build-test
    command: bin/bundle exec bin/rspec --format documentation
    depends_on:
      - postgres
      - selenium
      - selenium-chrome
    env_file:
      - ../.env
    environment:
      DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL: 'true'
      DATABASE_HOST: postgres
      DATABASE_URL: postgres://root:@postgres:5432/app_test
      EXECJS_RUNTIME: Node
      RAILS_ENV: test
    restart: "no"
    networks:
      dev_testing_net:
        ipv4_address: 172.20.1.208
      internal:
    extra_hosts:
      - rabbitmq:172.20.1.2
      - passport-test.lumerit.com:172.20.1.3
      - dashboard.test:172.20.1.4
      - app.test:172.20.1.208
      - selenium:172.20.1.178
      - selenium_chrome:172.20.1.220
    volumes:
      - ..:/app:cached
      - '../certs/app.crt:/certs/app.crt:ro'
      - '../certs/app.key:/certs/app.key:ro'
    ports:
      - "4000:4000"
  "selenium-chrome":
    image: selenium/standalone-chrome:120.0.6099.224-chromedriver-120.0.6099.109-grid-4.17.0-20240123
    container_name: selenium-chrome
    networks:
      dev_testing_net:
        ipv4_address: 172.20.1.220
      internal:
    volumes:
      - /dev/shm:/dev/shm
    ports:
      - "4444:4444"
  selenium:
    build:
      context: ../.
      dockerfile: build/selenium.Dockerfile
    container_name: selenium
    volumes:
      - /dev/shm:/dev/shm
      - '../.ca-certificates/certificate.crt:/usr/local/share/ca-certificates/certificate.crt'
    networks:
      dev_testing_net:
        ipv4_address: 172.20.1.178
      internal:
    # ports:
    #   - "4444:4444"
  postgres:
    image: postgres:9.6
    environment:
      POSTGRES_DB: 'app_test'
      POSTGRES_USER: 'root'
      POSTGRES_HOST_AUTH_METHOD: trust
    networks:
      internal:
networks:
  internal:
  dev_testing_net:
    external: true
    name: dev_testing

Steps to reproduce

This is the command that I am running to execute the javascript tests using capybara docker-compose run --rm --service-ports --use-aliases -e RAILS_ENV=test test /bin/bash -l -c "sleep 1 && bundle exec rails db:migrate && bundle exec rspec --tag js --fail-fast --format documentation"

However, when the tests are being executed, I get the above error. From my perspective, everything has been setup as Capybara has noted in the documentation, but It seems Capybara isn't recognizing the driver.

nathanhamilton commented 3 months ago

Turns out I had the config Capybara.javascript_driver = :selenium_headless_in_container inside the Capybara.register_driver block. Once outside, it executed successfully.