effective_test_bot 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 836a02fd7342588742c06d55d4f64d06382b5db5
4
- data.tar.gz: a136f7e711237d2038d86b64273f9d2d54248183
3
+ metadata.gz: 71be7587c07b84fa1b7e452dc469eeb560a1ce47
4
+ data.tar.gz: 063be9eb1dfc317f6dc01d0a8c199d411953c5f4
5
5
  SHA512:
6
- metadata.gz: a81518d383fabca6561057e0262c33487f8c377b90f7b0569ec18da26cae2910546a983af8afe2614dfa217a7206c95f2e952539d77a0345086a172226483364
7
- data.tar.gz: fe6980136e37b6881cf11ea673340d69af4d3c766c6932e0adb40b8c7db87a978bf27488371d768cfff4b5dada780972a5447241bf3f2c2d320dec4a95166e8e
6
+ metadata.gz: 1318c1e88b31a9d2bba44d2262862206c765c6864e8b9bf5d4b6456f90ae7d5fa552960974d4cdbbf83994c42aa1517cb6ffb31d1e6d86ad8960e0f4e38988d4
7
+ data.tar.gz: 80f617274e4f72d8885926c84fcdf8f19e51d576dcceb3b8914ee9e26a2c54dd32365f8641e04ab7c344f7ba218e25c4375f9a42fdb78f08002c7913a8ee68ae
data/README.md CHANGED
@@ -97,6 +97,9 @@ class PhysiciansTest < ActionDispatch::IntegrationTest
97
97
  end
98
98
 
99
99
 
100
+ , input_html: {'data-test-bot-skip': true}
101
+
102
+
100
103
 
101
104
 
102
105
  ## Fixtures
@@ -4,6 +4,9 @@ require "effective_test_bot/version"
4
4
  module EffectiveTestBot
5
5
  mattr_accessor :except
6
6
  mattr_accessor :only
7
+ mattr_accessor :screenshots
8
+ mattr_accessor :autosave_animated_gif_on_failure
9
+ mattr_accessor :tour_mode
7
10
 
8
11
  def self.setup
9
12
  yield self
@@ -38,6 +41,31 @@ module EffectiveTestBot
38
41
  false # Don't skip this test
39
42
  end
40
43
 
44
+ # If you call rake test:bot TOUR=false, then disable screenshots too
45
+ def self.screenshots?
46
+ screenshots == true
47
+ end
48
+
49
+ def self.autosave_animated_gif_on_failure?
50
+ screenshots && autosave_animated_gif_on_failure
51
+ end
52
+
53
+ def self.tour_mode?
54
+ if ENV['TOUR'].present?
55
+ ['true', 'verbose', 'debug'].include?(ENV['TOUR'].to_s.downcase)
56
+ else
57
+ screenshots && (tour_mode != false)
58
+ end
59
+ end
60
+
61
+ def self.tour_mode_verbose?
62
+ if ENV['TOUR'].present?
63
+ ['verbose', 'debug'].include?(ENV['TOUR'].to_s.downcase)
64
+ else
65
+ screenshots && ['verbose', 'debug'].include?(tour_mode.to_s)
66
+ end
67
+ end
68
+
41
69
  private
42
70
 
43
71
  def self.onlies
@@ -14,6 +14,14 @@ module EffectiveTestBot
14
14
 
15
15
  initializer 'effective_test_bot.test_suite' do |app|
16
16
  Rails.application.config.to_prepare do
17
+ # test/support/
18
+ ActionDispatch::IntegrationTest.include EffectiveTestBotAssertions
19
+ ActionDispatch::IntegrationTest.include EffectiveTestBotFormHelper
20
+ ActionDispatch::IntegrationTest.include EffectiveTestBotFormFiller
21
+ ActionDispatch::IntegrationTest.include EffectiveTestBotLoginHelper
22
+ ActionDispatch::IntegrationTest.include EffectiveTestBotScreenshotsHelper
23
+ ActionDispatch::IntegrationTest.include EffectiveTestBotTestHelper
24
+
17
25
  # test/test_botable/
18
26
  ActionDispatch::IntegrationTest.include BaseTest
19
27
  ActionDispatch::IntegrationTest.include CrudTest
@@ -31,12 +39,6 @@ module EffectiveTestBot
31
39
  ActionDispatch::IntegrationTest.include TestBotable::PageDsl
32
40
  ActionDispatch::IntegrationTest.include TestBotable::RedirectDsl
33
41
  ActionDispatch::IntegrationTest.include TestBotable::WizardDsl
34
-
35
- # test/support/
36
- ActionDispatch::IntegrationTest.include EffectiveTestBotAssertions
37
- ActionDispatch::IntegrationTest.include EffectiveTestBotFormHelper
38
- ActionDispatch::IntegrationTest.include EffectiveTestBotLoginHelper
39
- ActionDispatch::IntegrationTest.include EffectiveTestBotTestHelper
40
42
  end
41
43
  end
42
44
 
@@ -53,7 +55,7 @@ module EffectiveTestBot
53
55
  assign_test_bot_unpermitted_params_header(exception)
54
56
  end
55
57
 
56
- rescue_from Exception do |exception| # Not sure if I should rescue Exception or StandardError
58
+ rescue_from StandardError do |exception|
57
59
  assign_test_bot_exceptions_header(exception)
58
60
  render status: 500, text: "<html><body><h1>Uncaught Exception</h1><p>#{exception.message}</p><p>#{exception.backtrace.first(20).join('<br>')}</p></body></html>"
59
61
  end
@@ -1,3 +1,3 @@
1
1
  module EffectiveTestBot
2
- VERSION = '0.4.3'.freeze
2
+ VERSION = '0.4.4'.freeze
3
3
  end
@@ -41,7 +41,8 @@ module EffectiveTestBot
41
41
 
42
42
  def thank_you
43
43
  puts "Thanks for using EffectiveTestBot"
44
- puts "Run tests by typing 'rake test:bot'"
44
+ puts "First make sure your test environment is correctly configured by running 'rake test:bot:environment'"
45
+ puts "Run tests with 'rake test:bot'"
45
46
  end
46
47
  end
47
48
  end
@@ -2,5 +2,35 @@
2
2
 
3
3
  if Rails.env.test?
4
4
  EffectiveTestBot.setup do |config|
5
+
6
+ # Exclude the following tests or assertions from being run.
7
+ # config.except = [
8
+ # 'widgets'
9
+ # 'posts#create_invalid'
10
+ # 'posts#index page_title'
11
+ # 'no_unpermitted_params'
12
+ # ]
13
+
14
+ # Run only the following tests. Doesn't work with individual assertions>
15
+ # config.only = [
16
+ # 'posts', 'events#index'
17
+ # ]
18
+
19
+ # Should capybara generate a series of *.png screenshots as it goes through the test?
20
+ # Disabling screenshots will also disable animated_gifs and touring
21
+ config.screenshots = true
22
+
23
+ # Save on failure to /tmp/ directory
24
+ config.autosave_animated_gif_on_failure = true
25
+
26
+ # Take the tour!
27
+ # Generate an animated gif for each test
28
+ # Saved to an appropriate /test/tour/* directory
29
+ # You can override this default by setting an ENV or calling
30
+ # `rake test:bot TOUR=true` or `rake test:bot TEST=posts TOUR=verbose`
31
+ #
32
+ # Valid values are true / false / :verbose
33
+ config.tour_mode = false
34
+
5
35
  end
6
36
  end
@@ -48,6 +48,11 @@ Capybara::Webkit.configure { |config| config.allow_unknown_urls }
48
48
 
49
49
  Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
50
50
 
51
+ # These two lines are needed as of minitest-reporters 1.1.2
52
+ Rails.backtrace_cleaner.remove_silencers!
53
+ Rails.backtrace_cleaner.add_silencer { |line| line =~ /minitest/ }
54
+
55
+
51
56
  ### So this is EffectiveTestBot 'code' here
52
57
  ### That gets run just once before the whole test suite loads
53
58
 
@@ -4,25 +4,34 @@ require 'rails/test_unit/sub_test_task'
4
4
  # rake test:bot
5
5
  # rake test:bot TEST=documents#new
6
6
  # rake test:bot TEST=documents#new,documents#show
7
- # rake test:bot TEST=documents#new path,documents#show,documents#update_valid no_unpermitted_params
7
+ # rake test:bot TOUR=true
8
+ # rake test:bot TOUR=verbose
8
9
 
9
10
  # rake test:bot:environment
11
+ # rake test:bot:purge
10
12
 
11
13
  namespace :test do
12
- desc 'Runs Effective Test Bot'
14
+ desc 'Runs the effective_test_bot'
13
15
  task :bot do
14
16
  if ENV['TEST'].present?
15
17
  ENV['TEST_BOT_TEST'] = ENV['TEST']
16
18
  ENV['TEST'] = nil
17
19
  end
18
20
 
19
- Rake::Task["test:effective_test_bot"].invoke
21
+ Rake::Task['test:effective_test_bot'].invoke
20
22
  end
21
23
 
22
24
  namespace :bot do
23
- desc 'Runs Effective Test Bot environment test'
25
+ desc 'Runs effective_test_bot environment test'
24
26
  task :environment do
25
- Rake::Task["test:effective_test_bot_environment"].invoke
27
+ Rake::Task['test:effective_test_bot_environment'].invoke
28
+ end
29
+
30
+ desc 'Deletes all effective_test_bot temporary, failure and tour screenshots'
31
+ task :purge do
32
+ FileUtils.rm_rf(Rails.root + 'test/tour')
33
+ FileUtils.rm_rf(Rails.root + 'tmp/test_bot')
34
+ puts "Successfully purged all effective_test_bot screenshots"
26
35
  end
27
36
  end
28
37
 
@@ -45,9 +45,10 @@ module TestBotable
45
45
  options[:resource_name].pluralize
46
46
  ].compact.join('/') + '#' + test.to_s
47
47
 
48
- method_name = test_bot_method_name('crud_test', label || options_for_method[:current_test])
49
48
  next if EffectiveTestBot.skip?(label || options_for_method[:current_test])
50
49
 
50
+ method_name = test_bot_method_name('crud_test', label || options_for_method[:current_test])
51
+
51
52
  define_method(method_name) { crud_action_test(test, resource, user, options_for_method) }
52
53
  end
53
54
  end
@@ -21,9 +21,9 @@ module TestBotable
21
21
 
22
22
  [:sign_up, :sign_in_valid, :sign_in_invalid].each do |test|
23
23
  options[:current_test] = label || test
24
+ next if EffectiveTestBot.skip?(options[:current_test])
24
25
 
25
26
  method_name = test_bot_method_name('devise_test', options[:current_test])
26
- next if EffectiveTestBot.skip?(options[:current_test])
27
27
 
28
28
  define_method(method_name) { devise_action_test(test, options) }
29
29
  end
@@ -19,9 +19,9 @@ module TestBotable
19
19
 
20
20
  def member_test(controller, action, user, obj_to_param = nil, options = {})
21
21
  options[:current_test] = options.delete(:label) || "#{controller}##{action}"
22
+ return if EffectiveTestBot.skip?(options[:current_test])
22
23
 
23
24
  method_name = test_bot_method_name('member_test', options[:current_test])
24
- return if EffectiveTestBot.skip?(options[:current_test])
25
25
 
26
26
  define_method(method_name) { member_action_test(controller, action, user, obj_to_param, options) }
27
27
  end
@@ -17,9 +17,9 @@ module TestBotable
17
17
 
18
18
  def page_test(path, user, options = {})
19
19
  options[:current_test] = options.delete(:label) || path.to_s
20
+ return if EffectiveTestBot.skip?(options[:current_test])
20
21
 
21
22
  method_name = test_bot_method_name('page_test', options[:current_test])
22
- return if EffectiveTestBot.skip?(options[:current_test])
23
23
 
24
24
  define_method(method_name) { page_action_test(path, user, options) }
25
25
  end
@@ -16,9 +16,9 @@ module TestBotable
16
16
 
17
17
  def redirect_test(from_path, to_path, user, options = {})
18
18
  options[:current_test] = options.delete(:label) || "#{from_path} to #{to_path}"
19
+ return if EffectiveTestBot.skip?(options[:current_test])
19
20
 
20
21
  method_name = test_bot_method_name('redirect_test', options[:current_test])
21
- return if EffectiveTestBot.skip?(options[:current_test])
22
22
 
23
23
  define_method(method_name) { redirect_action_test(from_path, to_path, user, options) }
24
24
  end
@@ -19,9 +19,9 @@ module TestBotable
19
19
 
20
20
  def wizard_test(from_path, to_path, user, options = {})
21
21
  options[:current_test] = options.delete(:label) || "#{from_path} to #{to_path}"
22
+ return if EffectiveTestBot.skip?(options[:current_test])
22
23
 
23
24
  method_name = test_bot_method_name('wizard_test', options[:current_test])
24
- return if EffectiveTestBot.skip?(options[:current_test])
25
25
 
26
26
  define_method(method_name) { wizard_action_test(from_path, to_path, user, options) }
27
27
  end
@@ -51,7 +51,14 @@ module EffectiveTestBotAssertions
51
51
  end
52
52
 
53
53
  def assert_no_exceptions(message = nil)
54
- assert exceptions.blank?, message || "(no_exceptions) Unexpected exception:\n#{exceptions.join("\n")}\n========== End Exception ==========\n"
54
+ assert exceptions.blank?, message || "(no_exceptions) Unexpected exception:\n#{exceptions.join("\n")}\n========== End of rails server exception ==========\n"
55
+ end
56
+
57
+ # This must be run after submit_form()
58
+ # It ensures there are no HTML5 validation errors that would prevent the form from being submit
59
+ def assert_no_html5_form_validation_errors(message = nil)
60
+ errors = all(':invalid', visible: false).map { |field| field['name'] }
61
+ assert errors.blank?, message || "(no_html5_form_validation_errors) Unable to submit form, unexpected HTML5 validation error present on the following fields:\n#{errors.join("\n")}"
55
62
  end
56
63
 
57
64
  # assert_flash
@@ -84,17 +91,20 @@ module EffectiveTestBotAssertions
84
91
  # assert_no_assigns_errors :post
85
92
  def assert_no_assigns_errors(key = nil, message = nil)
86
93
  if key.present?
87
- assert_equal [], ((assigns[key.to_s] || {})['errors'] || []), message || "(no_assigns_errors) Expected @#{key}[:errors] to be blank. Instead, it was: #{assigns}"
94
+ errors = (assigns[key.to_s] || {})['errors']
95
+ assert errors.blank?, message || "(no_assigns_errors) Unexpected @#{key} rails validation errors:\n#{errors}"
88
96
  else
89
97
  assigns.each do |key, value|
90
- assert_equal [], (value['errors'] || []), message || "(no_assigns_errors) Expected @#{key}[:errors] to be blank"
98
+ errors = value['errors']
99
+ assert errors.blank?, message || "(no_assigns_errors) Unexpected @#{key} rails validation errors:\n#{errors}"
91
100
  end
92
101
  end
93
102
  end
94
103
 
95
104
  # assert_assigns_errors :post
96
105
  def assert_assigns_errors(key, message = nil)
97
- refute_equal [], ((assigns[key.to_s] || {})['errors'] || []), message || "(assigns_errors) Expected @#{key}[:errors] to be present"
106
+ errors = (assigns[key.to_s] || {})['errors']
107
+ assert errors.present?, message || "(assigns_errors) Expected @#{key}.errors to be present"
98
108
  end
99
109
 
100
110
  end
@@ -0,0 +1,239 @@
1
+ require 'timeout'
2
+
3
+ module EffectiveTestBotFormFiller
4
+ DIGITS = ('1'..'9').to_a
5
+ LETTERS = ('A'..'Z').to_a
6
+
7
+ # Fill a boostrap tabs based form
8
+ def fill_bootstrap_tabs_form(fills = {}, boostrap_tab_elements = nil)
9
+ fills = HashWithIndifferentAccess.new(fills) unless fills.kind_of?(HashWithIndifferentAccess)
10
+
11
+ tabs = boostrap_tab_elements || all("a[data-toggle='tab']")
12
+
13
+ # If there's only 1 tab, just fill it out
14
+ (fill_form_fields(fills) and return) unless tabs.length > 1
15
+
16
+ # If there's more than one tab:
17
+ # We first fill in all fields that are outside of the tab-content
18
+ # Then we start at the first, and go left-to-right through all the tabs
19
+ # clicking each one and filling any form fields found within
20
+
21
+ active_tab = find("li.active > a[data-toggle='tab']")
22
+ tab_content = find("div#{active_tab['href']}").find(:xpath, '..')
23
+
24
+ excluding_fields_with_parent(tab_content) { fill_form_fields(fills) }
25
+
26
+ # Refresh the tabs, as they may have changed
27
+ tabs = all("a[data-toggle='tab']")
28
+
29
+ # Click through each tab and fill the form inside it.
30
+ tabs.each do |tab|
31
+ # changing the call to to fill_bootstrap_tabs_form for recursiveness should work
32
+ # but it would be an extra all() lookup, and probably not worth it.
33
+ tab.click()
34
+ synchronize!
35
+ save_test_bot_screenshot
36
+
37
+ within("div#{tab['href']}") { fill_form_fields(fills) }
38
+ end
39
+
40
+ end
41
+
42
+ # Only fills in visible fields
43
+ # fill_form(:email => 'somethign@soneone.com', :password => 'blahblah', 'user.last_name' => 'hlwerewr')
44
+ def fill_form_fields(fills = {})
45
+ fills = HashWithIndifferentAccess.new(fills) unless fills.kind_of?(HashWithIndifferentAccess)
46
+
47
+ # Support for the cocoon gem
48
+ all('a.add_fields[data-association-insertion-template]').each do |field|
49
+ next if skip_form_field?(field)
50
+ 2.times { field.click(); save_test_bot_screenshot }
51
+ end
52
+
53
+ all('input,select,textarea').each do |field|
54
+ next if skip_form_field?(field)
55
+
56
+ case [field.tag_name, field['type']].compact.join('_')
57
+ when 'input_text', 'input_email', 'input_password', 'input_tel', 'input_number', 'input_checkbox', 'input_radio', 'textarea'
58
+ field.set(value_for_field(field, fills))
59
+ when 'select'
60
+ if field['class'].to_s.include?('select2') # effective_select
61
+ page.execute_script("try { $('select##{field['id']}').select2('open'); } catch(e) {};")
62
+ save_test_bot_screenshot
63
+ end
64
+
65
+ field.select(value_for_field(field, fills), match: :first)
66
+ when 'input_file'
67
+ if field['class'].to_s.include?('asset-box-uploader-fileinput')
68
+ upload_effective_asset(field, value_for_field(field, fills))
69
+ else
70
+ field.set(value_for_field(field, fills))
71
+ end
72
+ when 'input_submit', 'input_search'
73
+ # Do nothing
74
+ else
75
+ raise "unsupported field type #{[field.tag_name, field['type']].compact.join('_')}"
76
+ end
77
+
78
+ save_test_bot_screenshot
79
+ end
80
+ end
81
+
82
+ # Generates an appropriately pseudo-random value for the given field
83
+ # Pass in a Hash of fills to define pre-selected values
84
+ #
85
+ # Operates on just string keys, no symbols here
86
+
87
+ def value_for_field(field, fills = nil)
88
+ field_name = [field.tag_name, field['type']].compact.join('_')
89
+ attributes = field['name'].to_s.gsub(']', '').split('[') # user[something_attributes][last_name] => ['user', 'something_attributes', 'last_name']
90
+
91
+ fill_value = fill_value_for_field(fills, attributes)
92
+
93
+ # If there is a predefined fill value for this field, return it here
94
+ # except for select fields which are treated differently, so we can match fill values on both the html text or value
95
+ # this edge case is implemented below
96
+ if fill_value.present? && !['select'].include?(field_name)
97
+ return fill_value
98
+ end
99
+
100
+ case field_name
101
+ when 'input_text'
102
+ classes = field['class'].to_s.split(' ')
103
+
104
+ if classes.include?('date') # Let's assume this is a date input.
105
+ if attributes.last.to_s.include?('end') # Make sure end dates are after start dates
106
+ Faker::Date.forward(365).strftime('%Y-%m-%d')
107
+ else
108
+ Faker::Date.backward(365).strftime('%Y-%m-%d')
109
+ end
110
+ elsif classes.include?('datetime')
111
+ if attributes.last.to_s.include?('end')
112
+ Faker::Date.forward(365).strftime('%Y-%m-%d %H:%m')
113
+ else
114
+ Faker::Date.backward(365).strftime('%Y-%m-%d %H:%m')
115
+ end
116
+ elsif classes.include?('price') # effective_form_inputs price
117
+ 4.times.map { DIGITS.sample }.join('') + '.00'
118
+ elsif classes.include?('numeric')
119
+ min = (Float(field['min']) rescue 1)
120
+ max = (Float(field['max']) rescue 1000)
121
+ number = Random.new.rand(min..max)
122
+ number.kind_of?(Float) ? number.round(2) : number
123
+ elsif attributes.last.to_s.include?('first_name')
124
+ Faker::Name.first_name
125
+ elsif attributes.last.to_s.include?('last_name')
126
+ Faker::Name.last_name
127
+ elsif attributes.last.to_s.include?('name')
128
+ Faker::Name.name
129
+ elsif attributes.last.to_s.include?('postal') # Make a Canadian Postal Code
130
+ LETTERS.sample + DIGITS.sample + LETTERS.sample + ' ' + DIGITS.sample + LETTERS.sample + DIGITS.sample
131
+ else
132
+ Faker::Lorem.word
133
+ end
134
+
135
+ when 'select'
136
+ if fill_value.present? # accept a value or text
137
+ field.all('option:enabled').each do |option|
138
+ return option.text if (option.text == fill_value || option.value.to_s == fill_value)
139
+ end
140
+ end
141
+
142
+ field.all('option:enabled').select { |option| option.value.present? }.sample.try(:text) || '' # Don't select an empty option
143
+ when 'input_number'
144
+ min = (Float(field['min']) rescue 1)
145
+ max = (Float(field['max']) rescue 1000)
146
+ number = Random.new.rand(min..max)
147
+ number.kind_of?(Float) ? number.round(2) : number
148
+ when 'input_email'
149
+ Faker::Internet.email
150
+ when 'input_password'
151
+ # Use the same password throughout a single test. Allows passwords and password_confirmations to match.
152
+ @test_bot_current_password ||= Faker::Internet.password
153
+ when 'input_tel'
154
+ d = 10.times.map { DIGITS.sample }
155
+ d[0] + d[1] + d[2] + '-' + d[3] + d[4] + d[5] + '-' + d[6] + d[7] + d[8] + d[9]
156
+ when 'textarea'
157
+ Faker::Lorem.sentence
158
+ when 'input_checkbox'
159
+ [true, false].sample
160
+ when 'input_radio'
161
+ [true, false].sample
162
+ when 'input_file'
163
+ "#{File.dirname(__FILE__)}/effective_assets_upload_file._test"
164
+ else
165
+ raise "fill_value unsupported field type: #{field['type']}"
166
+ end
167
+ end
168
+
169
+ # The field here is going to be the %input{:type => file}. Files can be one or more pathnames
170
+ # http://stackoverflow.com/questions/5188240/using-selenium-to-imitate-dragging-a-file-onto-an-upload-element/11203629#11203629
171
+ def upload_effective_asset(field, files)
172
+ files = Array(files)
173
+ uid = field['id']
174
+
175
+ js = "fileList = Array();"
176
+
177
+ files.each_with_index do |file, i|
178
+ # Generate a fake input selector
179
+ page.execute_script("if($('#effectiveAssetsPlaceholder#{i}').length == 0) {effectiveAssetsPlaceholder#{i} = window.$('<input/>').attr({id: 'effectiveAssetsPlaceholder#{i}', type: 'file'}).appendTo('body'); }")
180
+
181
+ # Attach file to the fake input selector through Capybara
182
+ page.document.attach_file("effectiveAssetsPlaceholder#{i}", files[i])
183
+
184
+ # Build up the fake js event
185
+ js = "#{js} fileList.push(effectiveAssetsPlaceholder#{i}.get(0).files[0]);"
186
+ end
187
+
188
+ # Trigger the fake drop event
189
+ page.execute_script("#{js} e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : fileList } }; $('#s3_#{uid}').trigger(e);")
190
+
191
+ # Remove the file inputs we created
192
+ page.execute_script("$('input[id^=effectiveAssetsPlaceholder]').remove();")
193
+
194
+ # Wait till the Uploader bar goes away
195
+ begin
196
+ Timeout.timeout(files.length * 5) do
197
+ within("#asset-box-input-#{uid}") do
198
+ within('.uploads') do
199
+ while (first('.upload').present? rescue false) do
200
+ save_test_bot_screenshot
201
+ sleep(0.5)
202
+ end
203
+ end
204
+ end
205
+ end
206
+ rescue Timeout::Error
207
+ puts "file upload timed out after #{files.length * 5}s"
208
+ end
209
+ end
210
+
211
+ private
212
+
213
+ def fill_value_for_field(fills, attributes)
214
+ return if fills.blank? || attributes.blank?
215
+
216
+ key = nil
217
+ attributes.reverse_each do |name| # match last_name, then something_attributes.last_name, then user.something_attributes.last_name
218
+ key = (key.present? ? "#{name}.#{key}" : name) # builds up the string as we go along
219
+ return fills[key].to_s if fills.key?(key)
220
+ end
221
+
222
+ nil
223
+ end
224
+
225
+ # Takes a capybara element
226
+ def excluding_fields_with_parent(element, &block)
227
+ @test_bot_excluded_fields_xpath = element.path
228
+ yield
229
+ @test_bot_excluded_fields_xpath = nil
230
+ end
231
+
232
+ def skip_form_field?(field)
233
+ field.visible? == false ||
234
+ field.disabled? ||
235
+ ['true', true, 1].include?(field['data-test-bot-skip']) ||
236
+ (@test_bot_excluded_fields_xpath.present? && field.path.include?(@test_bot_excluded_fields_xpath))
237
+ end
238
+
239
+ end