X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/5dd1c07e56d32ae4c569ceffdbd4549bceb92de4..ae92d144610446849eb568247a44f02ae985c281:/apps/workbench/test/integration_helper.rb diff --git a/apps/workbench/test/integration_helper.rb b/apps/workbench/test/integration_helper.rb index 30643bc722..7209f2b6c9 100644 --- a/apps/workbench/test/integration_helper.rb +++ b/apps/workbench/test/integration_helper.rb @@ -1,23 +1,170 @@ +# Copyright (C) The Arvados Authors. All rights reserved. +# +# SPDX-License-Identifier: AGPL-3.0 + require 'test_helper' require 'capybara/rails' require 'capybara/poltergeist' require 'uri' require 'yaml' -$ARV_API_SERVER_DIR = File.expand_path('../../../../services/api', __FILE__) -SERVER_PID_PATH = 'tmp/pids/server.pid' +def available_port for_what + begin + Addrinfo.tcp("0.0.0.0", 0).listen do |srv| + port = srv.connect_address.ip_port + # Selenium needs an additional locking port, check if it's available + # and retry if necessary. + if for_what == 'selenium' + locking_port = port - 1 + Addrinfo.tcp("0.0.0.0", locking_port).listen.close + end + STDERR.puts "Using port #{port} for #{for_what}" + return port + end + rescue Errno::EADDRINUSE, Errno::EACCES + retry + end +end + +def selenium_opts + { + port: available_port('selenium'), + desired_capabilities: Selenium::WebDriver::Remote::Capabilities.firefox( + acceptInsecureCerts: true, + ), + } +end + +def poltergeist_opts + { + phantomjs_options: ['--ignore-ssl-errors=true'], + port: available_port('poltergeist'), + window_size: [1200, 800], + } +end + +Capybara.register_driver :poltergeist do |app| + Capybara::Poltergeist::Driver.new app, poltergeist_opts +end + +Capybara.register_driver :poltergeist_debug do |app| + Capybara::Poltergeist::Driver.new app, poltergeist_opts.merge(inspector: true) +end + +Capybara.register_driver :poltergeist_with_fake_websocket do |app| + js = File.expand_path '../support/fake_websocket.js', __FILE__ + Capybara::Poltergeist::Driver.new app, poltergeist_opts.merge(extensions: [js]) +end + +Capybara.register_driver :poltergeist_without_file_api do |app| + js = File.expand_path '../support/remove_file_api.js', __FILE__ + Capybara::Poltergeist::Driver.new app, poltergeist_opts.merge(extensions: [js]) +end + +Capybara.register_driver :selenium do |app| + Capybara::Selenium::Driver.new app, selenium_opts +end + +Capybara.register_driver :selenium_with_download do |app| + profile = Selenium::WebDriver::Firefox::Profile.new + profile['browser.download.dir'] = DownloadHelper.path.to_s + profile['browser.download.downloadDir'] = DownloadHelper.path.to_s + profile['browser.download.defaultFolder'] = DownloadHelper.path.to_s + profile['browser.download.folderList'] = 2 # "save to user-defined location" + profile['browser.download.manager.showWhenStarting'] = false + profile['browser.helperApps.alwaysAsk.force'] = false + profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/plain,application/octet-stream' + Capybara::Selenium::Driver.new app, selenium_opts.merge(profile: profile) +end + +module WaitForAjax + # FIXME: Huge side effect here + # The following line changes the global default Capybara wait time, affecting + # every test which follows this one. This should be removed and the failing tests + # should have their individual wait times increased, if appropriate, using + # the using_wait_time(N) construct to temporarily change the wait time. + # Note: the below is especially bad because there are places that increase wait + # times using a multiplier e.g. using_wait_time(3 * Capybara.default_max_wait_time) + Capybara.default_max_wait_time = 10 + def wait_for_ajax + timeout = 10 + count = 0 + while page.evaluate_script("jQuery.active").to_i > 0 + count += 1 + raise "AJAX request took more than #{timeout} seconds" if count > timeout * 10 + sleep(0.1) + end + end + +end + +module AssertDomEvent + # Yield the supplied block, then wait for an event to arrive at a + # DOM element. + def assert_triggers_dom_event events, target='body' + magic = 'received-dom-event-' + rand(2**30).to_s(36) + page.execute_script < api_token) - "#{path}#{sep}#{q_string}" + path_parts.insert(1, "#{sep}#{q_string}") + path_parts.join("") + end + + # Find a page element, but return false instead of raising an + # exception if not found. Use this with assertions to explain that + # the error signifies a failed test rather than an unexpected error + # during a testing procedure. + def find?(*args) + begin + find(*args) + rescue Capybara::ElementNotFound + false + end + end + + @@screenshot_count = 1 + def screenshot + image_file = "./tmp/workbench-fail-#{@@screenshot_count}.png" + begin + page.save_screenshot image_file + rescue Capybara::NotSupportedByDriverError + # C'est la vie. + else + puts "Saved #{image_file}" + @@screenshot_count += 1 + end end -end -class IntegrationTestRunner < MiniTest::Unit - # Make a hash that unsets Bundle's environment variables. - # We'll use this environment when we launch Bundle commands in the API - # server. Otherwise, those commands will try to use Workbench's gems, etc. - @@APIENV = Hash[ENV.map { |key, val| - (key =~ /^BUNDLE_/) ? [key, nil] : nil - }.compact] - - def _system(*cmd) - if not system(@@APIENV, *cmd) - raise RuntimeError, "#{cmd[0]} returned exit code #{$?.exitstatus}" + teardown do + if !passed? && !skipped? + screenshot + end + if Capybara.current_driver == :selenium + # Clearing localStorage crashes on a page where JS isn't + # executed. We also need to make sure we're clearing + # localStorage for the test server's origin, even if we finished + # the test on a different origin. + host = Capybara.current_session.server.host + port = Capybara.current_session.server.port + base = "http://#{host}:#{port}" + if page.evaluate_script("window.document.contentType") != "text/html" || + !page.evaluate_script("window.location.toString()").start_with?(base) + visit "#{base}/404" + end + page.execute_script("window.localStorage.clear()") + else + page.driver.restart if defined?(page.driver.restart) end + Capybara.reset_sessions! end - def _run(args=[]) - Capybara.javascript_driver = :poltergeist - server_pid = Dir.chdir($ARV_API_SERVER_DIR) do |apidir| - _system('bundle', 'exec', 'rake', 'db:test:load') - _system('bundle', 'exec', 'rake', 'db:fixtures:load') - _system('bundle', 'exec', 'rails', 'server', '-d') - timeout = Time.now.tv_sec + 10 - begin - sleep 0.2 + def accept_alert + if Capybara.current_driver == :selenium + (0..9).each do begin - server_pid = IO.read(SERVER_PID_PATH).to_i - good_pid = (server_pid > 0) and (Process.kill(0, pid) rescue false) - rescue Errno::ENOENT - good_pid = false + page.driver.browser.switch_to.alert.accept + break + rescue Selenium::WebDriver::Error::NoSuchAlertError + sleep 0.1 end - end while (not good_pid) and (Time.now.tv_sec < timeout) - if not good_pid - raise RuntimeError, "could not find API server Rails pid" end - server_pid - end - begin - super(args) - ensure - Process.kill('TERM', server_pid) + else + # poltergeist returns true for confirm, so no need to accept end end end -MiniTest::Unit.runner = IntegrationTestRunner.new +def upload_data_and_get_collection(data, user, filename, owner_uuid=nil) + token = api_token(user) + datablock = `echo -n #{data.shellescape} | ARVADOS_API_TOKEN=#{token.shellescape} arv-put --no-progress --raw -`.strip + assert $?.success?, $? + col = nil + use_token user do + mtxt = ". #{datablock} 0:#{data.length}:#{filename}\n" + if owner_uuid + col = Collection.create(manifest_text: mtxt, owner_uuid: owner_uuid) + else + col = Collection.create(manifest_text: mtxt) + end + end + return col +end