首页 > 解决方案 > Selenium webdriver 不适用于 docker-compose

问题描述

我有一个如下所示的 docker-compose.yml,其中使用selenium/standalone-chrome-debug图像为硒定义了服务。

# docker-compose.yml

version: '3'

services:
  webapp:
    tty: true
    stdin_open: true
    container_name: webapp
    depends_on:
      - postgres
      - elasticsearch
      - redis
      - selenium
    build: .
    volumes:
      - .:/webapp
    ports:
      - "3000:3000"
    entrypoint: sh /webapp/setup.sh
    environment:
      - REDISTOGO_URL=redis://redis:6379
      - ELASTICSEARCH_URL=http://elasticsearch:9200
      - SELENIUM_HOST=selenium
      - SELENIUM_PORT=4444

  postgres:
    container_name: postgres
    image: postgres:9.5.17
    ports:
      - "5432:5432"
    volumes:
      - ./postgres:/var/lib/postgresql
    environment:
      - POSTGRES_PASSWORD=test
      - POSTGRES_USER=test
      - POSTGRES_DB=test

  redis:
    container_name: redis
    image: redis:5.0.5-alpine
    command: redis-server
    hostname: redis
    ports:
      - "6379:6379"
    volumes:
      - redis:/data

  sidekiq:
    build: .
    command: bundle exec sidekiq
    volumes:
      - .:/webapp
    depends_on:
      - postgres
      - redis
    environment:
      - REDISTOGO_URL=redis://redis:6379

  elasticsearch:
    image: elasticsearch:6.8.0
    container_name: elasticsearch
    ports:
      - "9200:9200"
    depends_on:
      - postgres
    volumes:
      - esdata:/usr/share/elasticsearch/data

  selenium:
    image: selenium/standalone-chrome-debug
    ports:
      - "4444:4444"

volumes:
  redis:
  postgres:
  esdata:

和 rails_helper.rb

# rails_helper.rb

require 'database_cleaner'
require 'simplecov'
SimpleCov.start('rails') do
  coverage_dir 'coverage'
  add_group 'Modules', 'app/modules'
  add_filter "lib/api_constraints.rb"
  add_filter "app/uploaders/"
  add_filter "app/models/redactor_rails/"
  add_filter "app/controllers/application_controller.rb"
  add_filter "app/models/application_record.rb"
  add_filter "app/workers/"
end

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
# Add additional requires below this line. Rails is not loaded until this point!

require 'capybara/rspec'
require 'net_http_ssl_fix'
require 'selenium-webdriver'
require 'webdrivers/chromedriver'
require 'spree/testing_support/capybara_ext'
require 'rack_session_access/capybara'
require 'capybara-screenshot/rspec'
require 'rspec/retry'

# Add rake task example group
require 'support/tasks'

# Force lock local timezone for test environment
ENV['TZ'] = 'UTC'

Webdrivers.cache_time = 86_400
selenium_host = "http://127.0.0.1:4444/wd/hub"

unless ENV['SELENIUM_HOST'].nil?
  selenium_host = "http://#{ ENV["SELENIUM_HOST"] }:4444/wd/hub"
end

Capybara.register_driver :selenium_chrome do |app|
  caps = Selenium::WebDriver::Remote::Capabilities.chrome(
    browserName: 'chrome',
    "chromeOptions" => {
      args: ['headless','no-sandbox','disable-gpu','window-size=1920x1080']
    }
  )
  Capybara::Selenium::Driver.new(
    app,
    browser: :chrome,
    url: selenium_host,
    desired_capabilities: caps
  )
end

Capybara.server = :puma, { Silent: true }
Capybara.javascript_driver = :selenium_chrome
Capybara.save_path = "#{ Rails.root }/tmp/screenshots/"

Capybara.raise_server_errors = false
Capybara.default_max_wait_time = 10
Capybara.asset_host = 'http://localhost:3000'
Capybara.configure do |config|
  config.match = :prefer_exact
  config.ignore_hidden_elements = false
  config.visible_text_only = true
  # accept clicking of associated label for checkboxes/radio buttons (css psuedo elements)
  config.automatic_label_click = true
end
Capybara.always_include_port = true

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

# Checks for pending migration and applies them before tests are run.
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.maintain_test_schema!

RSpec.configure do |config|

  config.expect_with :rspec do |c|
    # enable both should and expect
    c.syntax = [:should, :expect]
  end
  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  config.append_after(:each) do
    Capybara.reset_sessions!
  end

  config.include Capybara::DSL

  config.order = "random"
  config.use_transactional_fixtures = false

  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
    # compile front end
    WebpackerHelper.compile_once
    # disable searchkick callbacks, now enabled by using search hook
    Searchkick.disable_callbacks
  end

  # hook for enabling searchkick callbacks
  config.around(:each, search: true) do |example|
    Searchkick.callbacks(true) do
      example.run
    end
  end

  config.before(:each) do
    DatabaseCleaner.strategy = Capybara.current_driver == :rack_test ? :transaction : :truncation
    DatabaseCleaner.clean
    DatabaseCleaner.start
    DownloadHelper.clear_downloads
    Factory.seed_data
  end

  config.after(:each) do
    Capybara.app_host = nil # don't change me, explicitly set host in each spec appropriately
    DatabaseCleaner.clean
    Timecop.return
  end

  config.include DeviseHelpers
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include CommonHelper
  config.include ImageHelper
  config.include CommonSpecHelper
  config.include ReactComponentHelper
  config.include BraintreeHelper
  config.include BookingSpecHelper
  config.include CapybaraRspecExt
  config.include DownloadHelper
  config.include ActionView::Helpers::NumberHelper
  config.include ActionView::Helpers::DateHelper

  config.infer_spec_type_from_file_location!

  # Filter lines from Rails gems in backtraces.
  config.filter_rails_from_backtrace!
  # arbitrary gems may also be filtered via:
  # config.filter_gems_from_backtrace("gem name")

  # Suppress Braintree noise
  null_logger = Logger.new("/dev/null")
  null_logger.level = Logger::INFO
  Braintree::Configuration.logger = null_logger

  config.after(:example, :on_fail => :screenshot) do |example|
    full_screenshot if example.exception
  end
  config.after(:example, :on_fail => :open_page) do |example|
    save_and_open_page if example.exception
  end

  # set parallel env for searchkick
  Searchkick.index_suffix = ENV['TEST_ENV_NUMBER']

  # show retry status in spec process
  config.verbose_retry = true
  # default number of retries
  config.default_retry_count = 0
  # sleep for 1 seconds before retry
  config.default_sleep_interval = 1
  # Retry failing specs (conditions to retry are set in config.retry_count_condition)
  config.around :each do |ex|
      ex.run_with_retry
  end
  # Retry failing JS specs or failing specs with specific exception
  config.retry_count_condition = proc do |ex|
    if (ex.metadata[:js] || [Net::ReadTimeout].include?(ex.exception.class)) && !ex.metadata[:on_fail]
      nil # will fallback to config.default_retry_count
    else
      0 # no retries if conditions not matched
    end
  end
  # callback to be run between retries
  config.retry_callback = proc do |ex|
    Capybara.reset!
  end
end

当我使用 js: true 为功能规范运行 docker-compose exec webapp rspec spec/feature/test_spec.rb 时,它会失败并显示以下堆栈跟踪:

  Failure/Error: ex.run_with_retry

          Selenium::WebDriver::Error::WebDriverError:
            <unknown>: Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document.
              (Session info: chrome=75.0.3770.100)

 # #0 0x5651e686d7a9 <unknown>
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/response.rb:72:in `assert_ok'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/response.rb:34:in `initialize'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/http/common.rb:88:in `new'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/http/common.rb:88:in `create_response'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/http/default.rb:114:in `request'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/http/common.rb:64:in `call'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/bridge.rb:167:in `execute'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/w3c/bridge.rb:567:in `execute'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/w3c/bridge.rb:305:in `execute_script'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/remote/w3c/bridge.rb:277:in `clear_session_storage'
          # /usr/local/bundle/gems/selenium-webdriver-3.142.3/lib/selenium/webdriver/common/html5/session_storage.rb:40:in `clear'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara/selenium/driver.rb:325:in `clear_session_storage'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara/selenium/driver.rb:317:in `clear_storage'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara/selenium/driver_specializations/chrome_driver.rb:45:in `clear_storage'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara/selenium/driver.rb:291:in `clear_browser_state'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara/selenium/driver.rb:446:in `reset_browser_state'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara/selenium/driver.rb:125:in `reset!'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara/selenium/driver_specializations/chrome_driver.rb:36:in `reset!'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara/session.rb:128:in `reset!'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara.rb:315:in `block in reset_sessions!'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara.rb:315:in `reverse_each'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara.rb:315:in `reset_sessions!'
          # /usr/local/bundle/gems/capybara-3.20.0/lib/capybara/rspec.rb:18:in `block (2 levels) in <top (required)>'
          # /usr/local/bundle/gems/rspec-retry-0.6.1/lib/rspec/retry.rb:123:in `block in run'
          # /usr/local/bundle/gems/rspec-retry-0.6.1/lib/rspec/retry.rb:110:in `loop'
          # /usr/local/bundle/gems/rspec-retry-0.6.1/lib/rspec/retry.rb:110:in `run'
          # /usr/local/bundle/gems/rspec-retry-0.6.1/lib/rspec_ext/rspec_ext.rb:12:in `run_with_retry'
          # ./spec/rails_helper.rb:224:in `block (2 levels) in <top (required)>'
          # /usr/local/bundle/gems/rspec-retry-0.6.1/lib/rspec/retry.rb:123:in `block in run'
          # /usr/local/bundle/gems/rspec-retry-0.6.1/lib/rspec/retry.rb:110:in `loop'
          # /usr/local/bundle/gems/rspec-retry-0.6.1/lib/rspec/retry.rb:110:in `run'
          # /usr/local/bundle/gems/rspec-retry-0.6.1/lib/rspec_ext/rspec_ext.rb:12:in `run_with_retry'
          # /usr/local/bundle/gems/rspec-retry-0.6.1/lib/rspec/retry.rb:37:in `block (2 levels) in setup'

有什么我想念的吗?使用 docker-compose 设置 selenium 的正确方法是什么?

标签: ruby-on-railsdockerselenium-webdriverdocker-composecapybara

解决方案


由于您使用的是 selenium 独立服务器设置,因此您需要配置 Capybara selenium 驱动程序以供远程使用。

Capybara.register_driver :selenium_chrome do |app|
  options = Selenium::WebDriver::Chrome::Options.new(args: %w[
    headless no-sandbox disable-gpu window-size=1920x1080
  ])
  Capybara::Selenium::Driver.new(
    app,
    browser: :remote,
    desired_capabilities: :chrome,
    options: options
    url: selenium_host,
  )
end

其他注意事项:

  • 如果您使用的是 Rails 5.1+,您可能可以删除所有数据库清理程序并启用事务测试

  • 在编写测试时设置ignore_hidden_elements = false是一个糟糕的主意,因为您通常只想处理用户实际可以看到的元素

  • :smart 通常是 Capybara.match 更好的默认值(而不是 :prefer_exact),如果您关心确保您的测试引用您期望的元素。

  • 您不应该在每个 RSpec 测试类型中包含 Capybara::DSL


推荐阅读