effective_test_bot 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,141 +1,69 @@
1
1
  require 'timeout'
2
2
 
3
3
  module EffectiveTestBotFormHelper
4
- DIGITS = ('1'..'9').to_a
5
- LETTERS = ('A'..'Z').to_a
6
-
7
- # fill_form(:email => 'somethign@soneone.com', :password => 'blahblah', 'user.last_name' => 'hlwerewr')
4
+ # Intelligently fills a form with Faker based randomish input
5
+ # Delegates the form fill logic to effective_test_bot_form_filler
8
6
  def fill_form(fills = {})
9
- fills = HashWithIndifferentAccess.new(fills)
7
+ fills = HashWithIndifferentAccess.new(fills) unless fills.kind_of?(HashWithIndifferentAccess)
8
+
9
+ bootstrap_tabs = all("a[data-toggle='tab']")
10
10
 
11
- # Support for the cocoon gem
12
- all('a.add_fields[data-association-insertion-template]').each do |cocoon_add_field|
13
- next unless cocoon_add_field.visible?
14
- [1,2].sample.times { cocoon_add_field.click() }
11
+ if bootstrap_tabs.length > 1
12
+ fill_bootstrap_tabs_form(fills, bootstrap_tabs)
13
+ else
14
+ fill_form_fields(fills)
15
15
  end
16
+ true
17
+ end
16
18
 
17
- all('input,select,textarea').each do |field|
18
- next unless field.visible?
19
-
20
- case [field.tag_name, field['type']].compact.join('_')
21
- when 'input_text', 'input_email', 'input_password', 'input_tel', 'input_number', 'textarea'
22
- field.set(fill_value(field, fills))
23
- when 'input_checkbox', 'input_radio'
24
- field.set(fill_value(field, fills)) # TODO
25
- when 'select'
26
- field.select(fill_value(field, fills), match: :first)
27
- when 'input_file'
28
- file_path = fill_value(field, fills)
29
- field['class'].to_s.include?('asset-box-uploader-fileinput') ? upload_effective_asset(field, file_path) : field.set(file_path)
30
- when 'input_submit', 'input_search'
31
- # Do nothing
32
- else
33
- raise "unsupported field type #{[field.tag_name, field['type']].compact.join('_')}"
34
- end
19
+ # This submits the form, while checking for html5 form validation errors and unpermitted params
20
+ def submit_form(label = nil)
21
+ assert_no_html5_form_validation_errors unless test_bot_skip?(:no_html5_form_validation_errors)
22
+
23
+ if test_bot_skip?(:no_unpermitted_params)
24
+ click_submit(label)
25
+ else
26
+ with_raised_unpermitted_params_exceptions { click_submit(label) }
35
27
  end
36
28
 
29
+ assert_no_unpermitted_params unless test_bot_skip?(:no_unpermitted_params)
37
30
  true
38
31
  end
39
32
 
40
- def clear_form
41
- all('input,select,textarea').each { |field| (field.set('') rescue false) }
33
+ # Submit form after disabling any HTML5 validations
34
+ def submit_novalidate_form(label = nil)
35
+ page.execute_script "for(var f=document.forms,i=f.length;i--;)f[i].setAttribute('novalidate','');"
36
+ click_submit(label)
42
37
  true
43
38
  end
44
39
 
45
- # Operates on just string keys
46
- # This function receives the same fill values that you call fill_form with
47
- def fill_value(field, fills = nil)
48
- attributes = field['name'].to_s.gsub(']', '').split('[') # user[something_attributes][last_name] => ['user', 'something_attributes', 'last_name']
49
- field_name = [field.tag_name, field['type']].compact.join('_')
50
- fill_value = nil
51
-
52
- if fills.present?
53
- key = nil
54
- attributes.reverse_each do |name| # match last_name, then something_attributes.last_name, then user.something_attributes.last_name
55
- key = (key.present? ? "#{name}.#{key}" : name) # builds up the string as we go along
56
-
57
- if fills.key?(key)
58
- fill_value = fills[key]
59
- # select is treated differently, because we want the passed prefill to match both the html text or value (which is implemented below)
60
- ['select'].include?(field_name) ? break : (return fill_value)
61
- end
62
- end
63
- end
64
-
65
- case field_name
66
- when 'input_email'
67
- Faker::Internet.email
68
- when 'input_number'
69
- min = (Float(field['min']) rescue 1)
70
- max = (Float(field['max']) rescue 1000)
71
- number = Random.new.rand(min..max)
72
- number.kind_of?(Float) ? number.round(2) : number
73
- when 'input_password'
74
- @test_bot_password ||= Faker::Internet.password # Use the same password throughout a single test. Allows passwords and password_confirmations to match.
75
- when 'input_tel'
76
- d = 10.times.map { DIGITS.sample }
77
- d[0] + d[1] + d[2] + '-' + d[3] + d[4] + d[5] + '-' + d[6] + d[7] + d[8] + d[9]
78
- when 'input_text'
79
- classes = field['class'].to_s.split(' ')
80
-
81
- if classes.include?('date') # Let's assume this is a date input.
82
- Faker::Date.backward(365).strftime('%Y-%m-%d')
83
- elsif classes.include?('datetime')
84
- Faker::Date.backward(365).strftime('%Y-%m-%d %H:%m')
85
- elsif classes.include?('price')
86
- 4.times.map { DIGITS.sample }.join('') + '.00'
87
- elsif classes.include?('numeric')
88
- min = (Float(field['min']) rescue 1)
89
- max = (Float(field['max']) rescue 1000)
90
- number = Random.new.rand(min..max)
91
- number.kind_of?(Float) ? number.round(2) : number
92
- elsif attributes.last.to_s.include?('first_name')
93
- Faker::Name.first_name
94
- elsif attributes.last.to_s.include?('last_name')
95
- Faker::Name.last_name
96
- elsif attributes.last.to_s.include?('name')
97
- Faker::Name.name
98
- elsif attributes.last.to_s.include?('postal') # Make a Canadian Postal Code
99
- LETTERS.sample + DIGITS.sample + LETTERS.sample + ' ' + DIGITS.sample + LETTERS.sample + DIGITS.sample
100
- else
101
- Faker::Lorem.word
102
- end
103
- when 'select'
104
- if fill_value.present? # accept a value or text
105
- field.all('option').each do |option|
106
- return option.text if option.text == fill_value || option.value.to_s == fill_value.to_s
107
- end
40
+ def clear_form
41
+ all('input,select,textarea').each do |field|
42
+ if field.tag_name == 'select' && field['class'].to_s.include?('select2') # effective_select
43
+ within(field.parent) { first(:css, '.select2-selection__clear').try(:click) }
108
44
  end
109
45
 
110
- field.all('option').select { |option| option.value.present? }.sample.try(:text) || '' # Don't select an empty option
111
- when 'textarea'
112
- Faker::Lorem.sentence
113
- when 'input_checkbox'
114
- [true, false].sample
115
- when 'input_radio'
116
- [true, false].sample
117
- when 'input_file'
118
- "#{File.dirname(__FILE__)}/effective_assets_upload_file._test"
119
- else
120
- raise "fill_value unsupported field type: #{field['type']}"
46
+ begin
47
+ field.set(''); save_test_bot_screenshot
48
+ rescue => e; end
121
49
  end
122
- end
123
50
 
124
- # page.execute_script "$('form#new_#{resource_name}').submit();"
125
- def submit_form(label = nil)
126
- if label.present?
127
- click_on(label)
128
- else
129
- first(:css, "input[type='submit']").click
130
- end
131
- synchronize!
132
51
  true
133
52
  end
134
53
 
135
- # Submit form after disabling any HTML5 validations
136
- def submit_novalidate_form(label = nil)
137
- page.execute_script "for(var f=document.forms,i=f.length;i--;)f[i].setAttribute('novalidate','');"
138
- submit_form(label)
54
+ # So it turns out capybara-webkit has no way to just move the mouse to an element
55
+ # This kind of sucks, as we want to simulate mouse movements with the tour
56
+ # Instead we manually trigger submit buttons and use the data-disable-with to
57
+ # make the 'submit form' step look nice
58
+ def click_submit(label)
59
+ if EffectiveTestBot.screenshots?
60
+ page.execute_script "$('input[data-disable-with]').each(function(i) { $.rails.disableFormElement($(this)); });"
61
+ save_test_bot_screenshot
62
+ page.execute_script "$('input[data-disable-with]').each(function(i) { $.rails.enableFormElement($(this)); });"
63
+ end
64
+
65
+ label.present? ? click_on(label) : first(:css, "input[type='submit']").click
66
+ synchronize!
139
67
  end
140
68
 
141
69
  def with_raised_unpermitted_params_exceptions(&block)
@@ -145,7 +73,7 @@ module EffectiveTestBotFormHelper
145
73
  action = ActionController::Parameters.action_on_unpermitted_parameters
146
74
  ActionController::Parameters.action_on_unpermitted_parameters = :raise
147
75
  rescue => e
148
- puts 'unable to assign config.action_on_unpermitted_parameters = :raise, unpermitted params assertions disabled.'
76
+ puts 'unable to assign config.action_on_unpermitted_parameters = :raise, (no_unpermitted_params) assertions may not work.'
149
77
  end
150
78
 
151
79
  yield
@@ -153,40 +81,4 @@ module EffectiveTestBotFormHelper
153
81
  ActionController::Parameters.action_on_unpermitted_parameters = action if action.present?
154
82
  end
155
83
 
156
- # The field here is going to be the %input{:type => file}. Files can be one or more pathnames
157
- # http://stackoverflow.com/questions/5188240/using-selenium-to-imitate-dragging-a-file-onto-an-upload-element/11203629#11203629
158
- def upload_effective_asset(field, files)
159
- files = Array(files)
160
- uid = field['id']
161
-
162
- js = "fileList = Array();"
163
-
164
- files.each_with_index do |file, i|
165
- # Generate a fake input selector
166
- page.execute_script("if($('#effectiveAssetsPlaceholder#{i}').length == 0) {effectiveAssetsPlaceholder#{i} = window.$('<input/>').attr({id: 'effectiveAssetsPlaceholder#{i}', type: 'file'}).appendTo('body'); }")
167
-
168
- # Attach file to the fake input selector through Capybara
169
- page.document.attach_file("effectiveAssetsPlaceholder#{i}", files[i])
170
-
171
- # Build up the fake js event
172
- js = "#{js} fileList.push(effectiveAssetsPlaceholder#{i}.get(0).files[0]);"
173
- end
174
-
175
- # Trigger the fake drop event
176
- page.execute_script("#{js} e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : fileList } }; $('#s3_#{uid}').trigger(e);")
177
-
178
- # Wait till the Uploader bar goes away
179
- begin
180
- Timeout.timeout(files.length * 5) do
181
- within("#asset-box-input-#{uid}") do
182
- within('.uploads') do
183
- sleep(0.25) while (first('.upload').present? rescue false)
184
- end
185
- end
186
- end
187
- rescue Timeout::Error
188
- puts "file upload timed out after #{files.length * 5}s"
189
- end
190
-
191
- end
192
84
  end
@@ -0,0 +1,145 @@
1
+ require 'RMagick'
2
+
3
+ module EffectiveTestBotScreenshotsHelper
4
+ include Magick
5
+
6
+ # Creates a screenshot based on the current test and the order in this test.
7
+ def save_test_bot_screenshot
8
+ return unless EffectiveTestBot.screenshots? && defined?(current_test)
9
+
10
+ full_path = current_test_temp_path + '/' + "#{current_test_screenshot_id}.png"
11
+ page.save_screenshot(full_path)
12
+
13
+ #i = Magick::Image.read(file).first
14
+ #i.resize_to_fill(100,100).write("#{file}-square-thumb.jpg")
15
+ end
16
+
17
+ # This is run before every test
18
+ # def before_setup
19
+ # super
20
+ # return unless (EffectiveTestBot.screenshots? && defined?(current_test))
21
+ # end
22
+
23
+ # This gets called after every test. Minitest hook for plugin developers
24
+ def after_teardown
25
+ super
26
+ return unless EffectiveTestBot.screenshots? && defined?(current_test) && (@test_bot_screenshot_id || 0) > 0
27
+
28
+ if !passed? && EffectiveTestBot.autosave_animated_gif_on_failure?
29
+ save_test_bot_failure_gif
30
+ end
31
+
32
+ if passed? && EffectiveTestBot.tour_mode?
33
+ save_test_bot_tour_gif
34
+ end
35
+ end
36
+
37
+ def save_test_bot_failure_gif
38
+ Dir.mkdir(current_test_failure_path) unless File.exists?(current_test_failure_path)
39
+ full_path = (current_test_failure_path + '/' + current_test_failure_filename)
40
+
41
+ save_test_bot_gif(full_path)
42
+ puts_yellow(" Animated .gif: #{full_path}")
43
+ end
44
+
45
+ def save_test_bot_tour_gif
46
+ Dir.mkdir(current_test_tour_path) unless File.exists?(current_test_tour_path)
47
+ full_path = (current_test_tour_path + '/' + current_test_tour_filename)
48
+
49
+ save_test_bot_gif(full_path)
50
+ puts_green(" Tour .gif: #{full_path}") if EffectiveTestBot.tour_mode_verbose?
51
+ end
52
+
53
+ def without_screenshots(&block)
54
+ original = EffectiveTestBot.screenshots
55
+
56
+ EffectiveTestBot.screenshots = false
57
+ yield
58
+ EffectiveTestBot.screenshots = original
59
+ end
60
+
61
+ protected
62
+
63
+ def save_test_bot_gif(full_path)
64
+
65
+ png_images = @test_bot_screenshot_id.times.map do |x|
66
+ current_test_temp_path + '/' + format_screenshot_id(x+1) + '.png'
67
+ end
68
+
69
+ images = Magick::ImageList.new(*png_images)
70
+
71
+ # Get max dimensions.
72
+ dimensions = {width: 0, height: 0}
73
+ images.each do |image|
74
+ dimensions[:width] = [image.columns, dimensions[:width]].max
75
+ dimensions[:height] = [image.rows, dimensions[:height]].max
76
+ end
77
+
78
+ # Create a final ImageList
79
+ animation = Magick::ImageList.new()
80
+
81
+ # Remove the PNG's alpha channel, 'cause .gifs dont support it
82
+ # Extend the bottom/right of each image to extend upto dimension
83
+ images.each do |image|
84
+ image.alpha Magick::DeactivateAlphaChannel
85
+ #image.background_color = 'white'
86
+ animation << image.extent(dimensions[:width], dimensions[:height])
87
+ end
88
+
89
+ # Run it through optimize layers.
90
+ # https://rmagick.github.io/ilist.html#optimize_layers
91
+ animation = animation.optimize_layers(Magick::OptimizePlusLayer)
92
+
93
+ # Write the final animated gif
94
+ animation.delay = 100 # 100 is the right setting
95
+ animation.write(full_path)
96
+ end
97
+
98
+ # There are 3 different paths we're working with
99
+ # current_test_temp_path: contains individually numbered .png screenshots produced by capybara
100
+ # current_test_tour_path: destination for .gifs of passing tests
101
+ # current_test_failure_path: destination for .gifs of failing tests
102
+
103
+ def current_test_temp_path
104
+ @_current_test_temp_path ||= File.join(Rails.root, 'tmp', 'test_bot', current_test)
105
+ end
106
+
107
+ def current_test_failure_path
108
+ File.join(Rails.root, 'tmp', 'test_bot')
109
+ end
110
+
111
+ def current_test_failure_filename
112
+ # Match Capybara-screenshots format-ish
113
+ "#{current_test}_failure_#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.gif"
114
+ end
115
+
116
+ # Where the tour animated gif ends up
117
+ def current_test_tour_path
118
+ File.join(Rails.root, 'test', 'tour')
119
+ end
120
+
121
+ def current_test_tour_filename
122
+ current_test + '.gif'
123
+ end
124
+
125
+ private
126
+
127
+ # Auto incrementing counter
128
+ # The very first screenshot will be 01.png (tmp/test_bot/posts#new/01.png)
129
+ def current_test_screenshot_id
130
+ @test_bot_screenshot_id = (@test_bot_screenshot_id || 0) + 1
131
+ format_screenshot_id(@test_bot_screenshot_id)
132
+ end
133
+
134
+ def format_screenshot_id(number)
135
+ number < 10 ? "0#{number}" : number.to_s
136
+ end
137
+
138
+ def puts_yellow(text)
139
+ puts "\e[33m#{text}\e[0m" # 33 is yellow
140
+ end
141
+
142
+ def puts_green(text)
143
+ puts "\e[32m#{text}\e[0m" # 32 is green
144
+ end
145
+ end
@@ -17,6 +17,7 @@ module EffectiveTestBotTestHelper
17
17
  @flash = (JSON.parse(Base64.decode64(session.driver.response_headers['Test-Bot-Flash'])) rescue {})
18
18
  @assigns = (JSON.parse(Base64.decode64(session.driver.response_headers['Test-Bot-Assigns'])) rescue {})
19
19
  @unpermitted_params = (JSON.parse(Base64.decode64(session.driver.response_headers['Test-Bot-Unpermitted-Params'])) rescue [])
20
+ @exceptions = (JSON.parse(Base64.decode64(session.driver.response_headers['Test-Bot-Exceptions'])) rescue [])
20
21
 
21
22
  @visit_delete_page = session
22
23
  end
@@ -35,7 +36,7 @@ module EffectiveTestBotTestHelper
35
36
  end
36
37
 
37
38
  # EffectiveTestBot includes an after_filter on ApplicationController to set an http header
38
- # that encodes the flash message, and some of the assigns
39
+ # These values are 'from the last page submit or refresh'
39
40
  def flash
40
41
  @flash ||= (JSON.parse(Base64.decode64(page.response_headers['Test-Bot-Flash'])) rescue {})
41
42
  end
@@ -65,9 +65,10 @@ module TestBot
65
65
 
66
66
  def is_crud_controller?(route)
67
67
  return false unless CRUD_ACTIONS.include?(route.defaults[:action])
68
+ return false unless route.defaults[:controller].present? && route.app.respond_to?(:controller)
68
69
 
69
- controller_klass = (route.app.controller(route.defaults) rescue nil) if route.defaults[:controller].present? && route.app.respond_to?(:controller)
70
- controller_instance = controller_klass.new() if controller_klass
70
+ controller_klass = (route.app.controller(route.defaults) rescue nil)
71
+ controller_instance = controller_klass.new()
71
72
 
72
73
  # Is this a CRUD capable controller?
73
74
  controller_instance && controller_instance.respond_to?(:new) && controller_instance.respond_to?(:create)
@@ -5,7 +5,7 @@ module TestBot
5
5
  @@original_users_count = User.count
6
6
  let(:original_users_count) { @@original_users_count }
7
7
 
8
- let(:email) { 'unique@testbot.com'}
8
+ let(:email) { 'unique@testbot.com' }
9
9
  let(:password) { '!Password123' }
10
10
  let(:create_user!) { User.new(email: email, password: password, password_confirmation: password).save(validate: false) }
11
11
 
@@ -14,7 +14,7 @@ module TestBot
14
14
  end
15
15
 
16
16
  test '01: seeds and fixtures loaded' do
17
- assert_normal
17
+ assert_environment_normal
18
18
  end
19
19
 
20
20
  test '02: all fixtures and seeds valid' do
@@ -40,7 +40,7 @@ module TestBot
40
40
  end
41
41
 
42
42
  test '05: test database has reset' do
43
- assert_normal
43
+ assert_environment_normal
44
44
  end
45
45
 
46
46
  test '06: capybara can sign up a user' do
@@ -53,7 +53,7 @@ module TestBot
53
53
 
54
54
  test '07: database and session have reset' do
55
55
  assert_signed_out
56
- assert_normal
56
+ assert_environment_normal
57
57
  end
58
58
 
59
59
  test '08: capybara can login_as via warden test helper' do
@@ -64,7 +64,7 @@ module TestBot
64
64
 
65
65
  test '09: database and session have reset' do
66
66
  assert_signed_out
67
- assert_normal
67
+ assert_environment_normal
68
68
  end
69
69
 
70
70
  test '10: capybara can sign in manually' do
@@ -75,21 +75,17 @@ module TestBot
75
75
 
76
76
  test '11: database and session have reset' do
77
77
  assert_signed_out
78
- assert_normal
78
+ assert_environment_normal
79
79
  end
80
80
 
81
81
  private
82
82
 
83
- def assert_normal
83
+ # This is all about seeing if the cookies, session, and database are rolling back properly in between tests
84
+ def assert_environment_normal
84
85
  visit root_path
85
86
  assert_page_status
86
- assert_equal original_users_count, User.count, 'Epected User.count to be back to original'
87
+ assert_equal original_users_count, User.count, 'Expected User.count to be back to original'
87
88
  assert assigns[:current_user].blank?, 'Expected current_user to be blank'
88
-
89
- # Someitmes it's nice to assert your environment...
90
- #assert users(:normal).present?
91
- #assert 2, User.count
92
- #assert 3, Physician.count
93
89
  end
94
90
 
95
91
  end