8784: Fix test for latest firefox.
[arvados.git] / apps / workbench / test / integration_helper.rb
1 require 'test_helper'
2 require 'capybara/rails'
3 require 'capybara/poltergeist'
4 require 'uri'
5 require 'yaml'
6
7 def available_port for_what
8   begin
9     Addrinfo.tcp("0.0.0.0", 0).listen do |srv|
10       port = srv.connect_address.ip_port
11       # Selenium needs an additional locking port, check if it's available
12       # and retry if necessary.
13       if for_what == 'selenium'
14         locking_port = port - 1
15         Addrinfo.tcp("0.0.0.0", locking_port).listen.close
16       end
17       STDERR.puts "Using port #{port} for #{for_what}"
18       return port
19     end
20   rescue Errno::EADDRINUSE, Errno::EACCES
21     retry
22   end
23 end
24
25 def selenium_opts
26   {
27     port: available_port('selenium'),
28   }
29 end
30
31 def poltergeist_opts
32   {
33     phantomjs_options: ['--ignore-ssl-errors=true'],
34     port: available_port('poltergeist'),
35     window_size: [1200, 800],
36   }
37 end
38
39 Capybara.register_driver :poltergeist do |app|
40   Capybara::Poltergeist::Driver.new app, poltergeist_opts
41 end
42
43 Capybara.register_driver :poltergeist_debug do |app|
44   Capybara::Poltergeist::Driver.new app, poltergeist_opts.merge(inspector: true)
45 end
46
47 Capybara.register_driver :poltergeist_with_fake_websocket do |app|
48   js = File.expand_path '../support/fake_websocket.js', __FILE__
49   Capybara::Poltergeist::Driver.new app, poltergeist_opts.merge(extensions: [js])
50 end
51
52 Capybara.register_driver :poltergeist_without_file_api do |app|
53   js = File.expand_path '../support/remove_file_api.js', __FILE__
54   Capybara::Poltergeist::Driver.new app, poltergeist_opts.merge(extensions: [js])
55 end
56
57 Capybara.register_driver :selenium do |app|
58   Capybara::Selenium::Driver.new app, selenium_opts
59 end
60
61 Capybara.register_driver :selenium_with_download do |app|
62   profile = Selenium::WebDriver::Firefox::Profile.new
63   profile['browser.download.dir'] = DownloadHelper.path.to_s
64   profile['browser.download.downloadDir'] = DownloadHelper.path.to_s
65   profile['browser.download.defaultFolder'] = DownloadHelper.path.to_s
66   profile['browser.download.folderList'] = 2 # "save to user-defined location"
67   profile['browser.download.manager.showWhenStarting'] = false
68   profile['browser.helperApps.alwaysAsk.force'] = false
69   profile['browser.helperApps.neverAsk.saveToDisk'] = 'text/plain,application/octet-stream'
70   Capybara::Selenium::Driver.new app, selenium_opts.merge(profile: profile)
71 end
72
73 module WaitForAjax
74   # FIXME: Huge side effect here
75   # The following line changes the global default Capybara wait time, affecting
76   # every test which follows this one. This should be removed and the failing tests
77   # should have their individual wait times increased, if appropriate, using
78   # the using_wait_time(N) construct to temporarily change the wait time.
79   # Note: the below is especially bad because there are places that increase wait
80   # times using a multiplier e.g. using_wait_time(3 * Capybara.default_max_wait_time)
81   Capybara.default_max_wait_time = 10
82   def wait_for_ajax
83     timeout = 10
84     count = 0
85     while page.evaluate_script("jQuery.active").to_i > 0
86       count += 1
87       raise "AJAX request took more than #{timeout} seconds" if count > timeout * 10
88       sleep(0.1)
89     end
90   end
91
92 end
93
94 module AssertDomEvent
95   # Yield the supplied block, then wait for an event to arrive at a
96   # DOM element.
97   def assert_triggers_dom_event events, target='body'
98     magic = 'received-dom-event-' + rand(2**30).to_s(36)
99     page.evaluate_script <<eos
100       $('#{target}').one('#{events}', function() {
101         $('body').addClass('#{magic}');
102       });
103 eos
104     yield
105     assert_selector "body.#{magic}"
106     page.evaluate_script "$('body').removeClass('#{magic}');";
107   end
108 end
109
110 module HeadlessHelper
111   class HeadlessSingleton
112     @display = ENV['ARVADOS_TEST_HEADLESS_DISPLAY'] || rand(400)+100
113     STDERR.puts "Using display :#{@display} for headless tests"
114     def self.get
115       @headless ||= Headless.new reuse: false, display: @display
116     end
117   end
118
119   Capybara.default_driver = :rack_test
120
121   def self.included base
122     base.class_eval do
123       setup do
124         Capybara.use_default_driver
125         @headless = false
126       end
127
128       teardown do
129         if @headless
130           @headless.stop
131           @headless = false
132         end
133       end
134     end
135   end
136
137   def need_selenium reason=nil, driver=:selenium
138     Capybara.current_driver = driver
139     unless ENV['ARVADOS_TEST_HEADFUL'] or @headless
140       @headless = HeadlessSingleton.get
141       @headless.start
142     end
143   end
144
145   def need_javascript reason=nil
146     unless Capybara.current_driver == :selenium
147       Capybara.current_driver = :poltergeist
148     end
149   end
150 end
151
152 module KeepWebConfig
153   def getport service
154     File.read(File.expand_path("../../../../tmp/#{service}.port", __FILE__))
155   end
156
157   def use_keep_web_config
158     @kwport = getport 'keep-web-ssl'
159     @kwdport = getport 'keep-web-dl-ssl'
160     Rails.configuration.keep_web_url = "https://localhost:#{@kwport}/c=%{uuid_or_pdh}"
161     Rails.configuration.keep_web_download_url = "https://localhost:#{@kwdport}/c=%{uuid_or_pdh}"
162     CollectionsController.any_instance.expects(:file_enumerator).never
163   end
164 end
165
166 class ActionDispatch::IntegrationTest
167   # Make the Capybara DSL available in all integration tests
168   include Capybara::DSL
169   include ApiFixtureLoader
170   include WaitForAjax
171   include AssertDomEvent
172   include HeadlessHelper
173
174   @@API_AUTHS = self.api_fixture('api_client_authorizations')
175
176   def page_with_token(token, path='/')
177     # Generate a page path with an embedded API token.
178     # Typical usage: visit page_with_token('token_name', page)
179     # The token can be specified by the name of an api_client_authorizations
180     # fixture, or passed as a raw string.
181     api_token = ((@@API_AUTHS.include? token) ?
182                  @@API_AUTHS[token]['api_token'] : token)
183     path_parts = path.partition("#")
184     sep = (path_parts.first.include? '?') ? '&' : '?'
185     q_string = URI.encode_www_form('api_token' => api_token)
186     path_parts.insert(1, "#{sep}#{q_string}")
187     path_parts.join("")
188   end
189
190   # Find a page element, but return false instead of raising an
191   # exception if not found. Use this with assertions to explain that
192   # the error signifies a failed test rather than an unexpected error
193   # during a testing procedure.
194   def find? *args
195     begin
196       find *args
197     rescue Capybara::ElementNotFound
198       false
199     end
200   end
201
202   @@screenshot_count = 1
203   def screenshot
204     image_file = "./tmp/workbench-fail-#{@@screenshot_count}.png"
205     begin
206       page.save_screenshot image_file
207     rescue Capybara::NotSupportedByDriverError
208       # C'est la vie.
209     else
210       puts "Saved #{image_file}"
211       @@screenshot_count += 1
212     end
213   end
214
215   teardown do
216     if not passed?
217       screenshot
218     end
219     if Capybara.current_driver == :selenium
220       page.execute_script("window.localStorage.clear()")
221     end
222     Capybara.reset_sessions!
223   end
224
225   def accept_alert
226     if Capybara.current_driver == :selenium
227       (0..9).each do
228         begin
229           page.driver.browser.switch_to.alert.accept
230           break
231         rescue Selenium::WebDriver::Error::NoSuchAlertError
232          sleep 0.1
233         end
234       end
235     else
236       # poltergeist returns true for confirm, so no need to accept
237     end
238   end
239 end