cuprite
cuprite copied to clipboard
How to test file download?
I have a test where something is exported as a PDF file and pushed to the client so that he downloads that generated PDF file.
How can I test that and test that the expected downloaded file name?
Hey @zedtux I think you are looking for https://github.com/teamcapybara/capybara/blob/f7ab0b5cd5da86185816c2d5c30d58145fe654ed/lib/capybara/spec/session/click_link_spec.rb#L219
Thank you @route for that link, I will use it in my tests 👍
So I tried to use that code but it doesn't work as it can't find the download file.
My generated file is done from a JavaScript app. How is Chrome/Cuprite saving the downloads to the Capybara.save_path ?
Is there anything to setup in order to have it working?
You just have to setup save_path dir and that's basically it. Run the test with CUPRITE_DEBUG=true and show the output. Also tell details regarding specs, do you run everything on the same machine or it's docker setup...
Some hints from other repo https://github.com/rubycdp/ferrum/issues/169
Thank you @route for the help, unfortunately it doesn't work, I never have downloaded files appearing.
I'm using a Docker environment with a compose service for my app, and another one for Chrome using the browserless/chrome:1.49-chrome-stable Docker image.
I've basically followed the excellent blog article System of a test: Proper browser testing in Ruby on Rails from Evil Martians.
I'm running a Cucumber feature which downloads a PDF file so I have a features/support/capybara_setup.rb file like this:
# frozen_string_literal: true
# Capybara settings (not covered by Rails system tests)
# Make server listening on all hosts
Capybara.server_host = 'rails'
Capybara.server_port = 3001
Capybara.always_include_port = true
Capybara.server = :puma
Capybara.disable_animation = true
# Replaces the environment action mailer configuration in order to match the
# Capybara's one, making email links pointing to the Capybara server.
Rails.configuration.action_mailer.default_url_options[:host] = [
Capybara.server_host,
Capybara.server_port
].join(':')
# Don't wait too long in `have_xyz` matchers
Capybara.default_max_wait_time = ENV['CI'] == true ? 15 : 2
# Normalizes whitespaces when using `has_text?` and similar matchers
Capybara.default_normalize_ws = true
# Where to store artifacts (e.g. screenshots, downloaded files, etc.)
Capybara.save_path = Rails.root.join(ENV.fetch('CAPYBARA_ARTIFACTS', 'tmp/capybara'))
a features/support/cuprite_setup.rb file like this:
# frozen_string_literal: true
# Cuprite is a modern Capybara driver which uses Chrome CDP API
# instead of Selenium & co.
# See https://github.com/rubycdp/cuprite
#
# Custom Ferrum gem logger in order to make features/support/browser_log.rb
# still working.
#
class FerrumLogger
attr_reader :logs
def initialize
@logs = []
end
def clear
@logs = []
end
def puts(log_str)
log_body = parse(log_str)
return if not_looking_at?(log_body)
@logs << build_selenium_like_log_from(log_body)
end
private
def build_log_message_from(body)
"#{body['params']['entry']['url']} - #{body['params']['entry']['text']}"
end
def build_selenium_like_log_from(body)
OpenStruct.new(message: build_log_message_from(body),
level: body['params']['entry']['level'])
end
def parse(string)
_log_symbol, _log_time, log_body_str = string.strip.split(' ', 3)
JSON.parse(log_body_str)
end
def not_looking_at?(body)
unless %w[
Runtime.exceptionThrown
Log.entryAdded
Runtime.consoleAPICalled
].include?(body['method'])
return true
end
return true unless body['params'].key?('entry')
body['params']['entry'] == 'error'
end
end
REMOTE_CHROME_URL = ENV['CHROME_URL']
REMOTE_CHROME_HOST, REMOTE_CHROME_PORT =
if REMOTE_CHROME_URL
URI.parse(REMOTE_CHROME_URL).yield_self do |uri|
[uri.host, uri.port]
end
end
# Check whether the remote chrome is running and configure the Capybara
# driver for it.
remote_chrome =
begin
if REMOTE_CHROME_URL.nil?
false
else
Socket.tcp(REMOTE_CHROME_HOST, REMOTE_CHROME_PORT, connect_timeout: 1)
.close
true
end
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
false
end
remote_options = remote_chrome ? { url: REMOTE_CHROME_URL } : {}
require 'capybara/cuprite'
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(
app,
**{
browser_options: remote_chrome ? { 'no-sandbox' => nil } : {},
inspector: true,
logger: FerrumLogger.new,
js_errors: true,
save_path: Capybara.save_path,
window_size: [810, 1080]
}.merge(remote_options)
)
end
Capybara.default_driver = Capybara.javascript_driver = :cuprite
# Add shortcuts for cuprite-specific debugging helpers
module CupriteHelpers
def pause
page.driver.pause
end
def debug(binding = nil)
$stdout.puts '🔎 Open Chrome inspector at http://localhost:3333'
return binding.pry if binding
page.driver.pause
end
end
World(CupriteHelpers)
a features/support/download_helpers.rb file:
# frozen_string_literal: true
module System
module DownloadHelpers
TIMEOUT = 10
def downloads
Dir[Capybara.save_path.join('*')].reject { |f| File.directory?(f) }
end
def download
downloads.first
end
def download_content
wait_for_download
File.read(download)
end
def wait_for_download
Timeout.timeout(TIMEOUT, Timeout::Error) do
sleep 0.1 until downloaded?
end
end
def downloaded?
!downloading? && downloads.any?
end
def downloading?
downloads.grep(/\.crdownload$/).any?
end
def clear_downloads
FileUtils.rm_f(downloads)
end
end
end
World(System::DownloadHelpers)
but when I should have a file downloaded (the test clicks a button which send the file to the web browser, which was working with Selenium) nothing appear to be downloaded.
Also using the wait_for_download method, right after having clicked the button, fails with a execution expired (Timeout::Error) error.
I'll take a look tomorrow
Excellent, thank you! If you need anything from me, just let me know.
My first guess is that Cuprite is well saving the file at the given position but in the chrome service, but the tests are running in the app service.
I will try to redo the same I did with Selenium which is to create a volume in compose in order to share the chrome save_path with the app service and see if it works.
You have a great setup! All well thought and tidy. As for download behavior if Chrome runs in a dedicated container then the file is downloaded there definitely because under the hood it uses Download which simply puts the file into downloadPath. So I'm afraid it lies in the docker container for Chrome. I think you can solve it with mounting the same volume for both containers say like to /tmp. Share your docker compose files with us, it can be helpful in the future so I can link to this issue.
Sure I will.