capybara-screenshot-diff 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +29 -0
  3. data/capybara-screenshot-diff.gemspec +7 -4
  4. data/gems.rb +8 -2
  5. data/lib/capybara/screenshot/diff/browser_helpers.rb +29 -28
  6. data/lib/capybara/screenshot/diff/cucumber.rb +11 -0
  7. data/lib/capybara/screenshot/diff/difference.rb +63 -0
  8. data/lib/capybara/screenshot/diff/drivers/base_driver.rb +42 -0
  9. data/lib/capybara/screenshot/diff/drivers/chunky_png_driver.rb +188 -260
  10. data/lib/capybara/screenshot/diff/drivers/utils.rb +16 -0
  11. data/lib/capybara/screenshot/diff/drivers/vips_driver.rb +53 -103
  12. data/lib/capybara/screenshot/diff/drivers.rb +16 -0
  13. data/lib/capybara/screenshot/diff/image_compare.rb +125 -154
  14. data/lib/capybara/screenshot/diff/os.rb +1 -1
  15. data/lib/capybara/screenshot/diff/screenshot_matcher.rb +128 -0
  16. data/lib/capybara/screenshot/diff/screenshoter.rb +137 -0
  17. data/lib/capybara/screenshot/diff/stabilization.rb +0 -184
  18. data/lib/capybara/screenshot/diff/stable_screenshoter.rb +106 -0
  19. data/lib/capybara/screenshot/diff/test_methods.rb +51 -90
  20. data/lib/capybara/screenshot/diff/vcs.rb +44 -22
  21. data/lib/capybara/screenshot/diff/version.rb +1 -1
  22. data/lib/capybara/screenshot/diff.rb +13 -17
  23. data/sig/capybara/screenshot/diff/diff.rbs +28 -0
  24. data/sig/capybara/screenshot/diff/difference.rbs +33 -0
  25. data/sig/capybara/screenshot/diff/drivers/base_driver.rbs +63 -0
  26. data/sig/capybara/screenshot/diff/drivers/browser_helpers.rbs +36 -0
  27. data/sig/capybara/screenshot/diff/drivers/chunky_png_driver.rbs +89 -0
  28. data/sig/capybara/screenshot/diff/drivers/utils.rbs +13 -0
  29. data/sig/capybara/screenshot/diff/drivers/vips_driver.rbs +25 -0
  30. data/sig/capybara/screenshot/diff/image_compare.rbs +93 -0
  31. data/sig/capybara/screenshot/diff/os.rbs +11 -0
  32. data/sig/capybara/screenshot/diff/region.rbs +43 -0
  33. data/sig/capybara/screenshot/diff/screenshot_matcher.rbs +60 -0
  34. data/sig/capybara/screenshot/diff/screenshoter.rbs +48 -0
  35. data/sig/capybara/screenshot/diff/stable_screenshoter.rbs +29 -0
  36. data/sig/capybara/screenshot/diff/test_methods.rbs +39 -0
  37. data/sig/capybara/screenshot/diff/vcs.rbs +17 -0
  38. metadata +36 -27
  39. data/.gitattributes +0 -4
  40. data/.github/dependabot.yml +0 -8
  41. data/.github/workflows/lint.yml +0 -25
  42. data/.github/workflows/test.yml +0 -138
  43. data/.gitignore +0 -14
  44. data/.standard.yml +0 -12
  45. data/CONTRIBUTING.md +0 -24
  46. data/Dockerfile +0 -59
  47. data/README.md +0 -567
  48. data/bin/bundle +0 -114
  49. data/bin/console +0 -15
  50. data/bin/install-vips +0 -11
  51. data/bin/rake +0 -27
  52. data/bin/setup +0 -8
  53. data/bin/standardrb +0 -29
  54. data/gemfiles/rails60_gems.rb +0 -8
  55. data/gemfiles/rails61_gems.rb +0 -7
  56. data/gemfiles/rails70_gems.rb +0 -7
  57. data/tmp/.keep +0 -0
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "screenshoter"
4
+ require_relative "stable_screenshoter"
5
+ require_relative "browser_helpers"
6
+ require_relative "vcs"
7
+
8
+ module Capybara
9
+ module Screenshot
10
+ module Diff
11
+ class ScreenshotMatcher
12
+ attr_reader :screenshot_full_name, :driver_options, :screenshot_path, :base_screenshot_path
13
+
14
+ def initialize(screenshot_full_name, options = {})
15
+ @screenshot_full_name = screenshot_full_name
16
+ @driver_options = Diff.default_options.merge(options)
17
+
18
+ @screenshot_path = Screenshot.screenshot_area_abs / Pathname.new(screenshot_full_name).sub_ext(".png")
19
+ @base_screenshot_path = ScreenshotMatcher.base_image_path_from(@screenshot_path)
20
+ end
21
+
22
+ def build_screenshot_matches_job
23
+ # TODO: Move this into screenshot stage, in order to re-evaluate coordinates after page updates
24
+ return if BrowserHelpers.window_size_is_wrong?(Screenshot.window_size)
25
+
26
+ # Stability Screenshoter Options
27
+
28
+ # TODO: Move this into screenshot stage, in order to re-evaluate coordinates after page updates
29
+ crop = calculate_crop_region(driver_options)
30
+
31
+ # Allow nil or single or multiple areas
32
+ # TODO: Move this into screenshot stage, in order to re-evaluate coordinates after page updates
33
+ if driver_options[:skip_area]
34
+ # Cast skip area args to Region and makes relative to crop
35
+ driver_options[:skip_area] = calculate_skip_area(driver_options[:skip_area], crop)
36
+ end
37
+ driver_options[:driver] = Drivers.for(driver_options)
38
+
39
+ create_output_directory_for(screenshot_path) unless screenshot_path.exist?
40
+
41
+ checkout_base_screenshot
42
+
43
+ capture_options = {
44
+ crop: crop,
45
+ stability_time_limit: driver_options.delete(:stability_time_limit),
46
+ wait: driver_options.delete(:wait)
47
+ }
48
+
49
+ take_comparison_screenshot(capture_options, driver_options, screenshot_path)
50
+
51
+ return unless base_screenshot_path.exist?
52
+
53
+ # Add comparison job in the queue
54
+ [
55
+ screenshot_full_name,
56
+ ImageCompare.new(screenshot_path.to_s, base_screenshot_path.to_s, driver_options)
57
+ ]
58
+ end
59
+
60
+ def cleanup
61
+ FileUtils.rm_f(base_screenshot_path)
62
+ end
63
+
64
+ def self.base_image_path_from(screenshot_path)
65
+ screenshot_path.sub_ext(".base.png")
66
+ end
67
+
68
+ private
69
+
70
+ def checkout_base_screenshot
71
+ Vcs.checkout_vcs(screenshot_path, base_screenshot_path)
72
+ end
73
+
74
+ def calculate_crop_region(driver_options)
75
+ crop_coordinates = driver_options.delete(:crop)
76
+ return nil unless crop_coordinates
77
+
78
+ crop_coordinates = BrowserHelpers.bounds_for_css(crop_coordinates).first if crop_coordinates.is_a?(String)
79
+ Region.from_edge_coordinates(*crop_coordinates)
80
+ end
81
+
82
+ def create_output_directory_for(screenshot_path)
83
+ screenshot_path.dirname.mkpath
84
+ end
85
+
86
+ # Try to get screenshot from browser.
87
+ # On `stability_time_limit` it checks that page stop updating by comparison several screenshot attempts
88
+ # On reaching `wait` limit then it has been failed. On failing we annotate screenshot attempts to help to debug
89
+ def take_comparison_screenshot(capture_options, driver_options, screenshot_path)
90
+ screenshoter = build_screenshoter_for(capture_options, driver_options)
91
+ screenshoter.take_comparison_screenshot(screenshot_path)
92
+ end
93
+
94
+ def build_screenshoter_for(capture_options, comparison_options = {})
95
+ if capture_options[:stability_time_limit]
96
+ StableScreenshoter.new(capture_options, comparison_options)
97
+ else
98
+ Diff.screenshoter.new(capture_options, comparison_options[:driver])
99
+ end
100
+ end
101
+
102
+ # Cast skip areas params into Region
103
+ # and if there is crop then makes absolute coordinates to eb relative to crop top left corner
104
+ def calculate_skip_area(skip_area, crop)
105
+ crop_region = crop && Region.new(*crop)
106
+ skip_area = Array(skip_area)
107
+
108
+ css_selectors, regions = skip_area.compact.partition { |region| region.is_a? String }
109
+
110
+ result = []
111
+ unless css_selectors.empty?
112
+ result.concat(build_regions_for(BrowserHelpers.bounds_for_css(*css_selectors)))
113
+ end
114
+ result.concat(build_regions_for(regions.flatten.each_slice(4))) unless regions.empty?
115
+ result.compact!
116
+
117
+ result.map! { |region| crop_region.find_relative_intersect(region) } if crop_region
118
+
119
+ result
120
+ end
121
+
122
+ def build_regions_for(coordinates)
123
+ coordinates.map { |coordinates_entity| Region.from_edge_coordinates(*coordinates_entity) }
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "os"
4
+ require_relative "browser_helpers"
5
+
6
+ module Capybara
7
+ module Screenshot
8
+ class Screenshoter
9
+ attr_reader :capture_options, :comparison_options, :driver
10
+
11
+ def initialize(capture_options, driver)
12
+ @capture_options = capture_options
13
+ @comparison_options = comparison_options
14
+ @driver = driver
15
+ end
16
+
17
+ def crop
18
+ @capture_options[:crop]
19
+ end
20
+
21
+ def wait
22
+ @capture_options[:wait]
23
+ end
24
+
25
+ def self.attempts_screenshot_paths(base_file)
26
+ Dir["#{base_file.to_s.chomp(".png")}.attempt_*.png"].sort
27
+ end
28
+
29
+ def self.cleanup_attempts_screenshots(base_file)
30
+ FileUtils.rm_rf attempts_screenshot_paths(base_file)
31
+ end
32
+
33
+ # Try to get screenshot from browser.
34
+ # On `stability_time_limit` it checks that page stop updating by comparison several screenshot attempts
35
+ # On reaching `wait` limit then it has been failed. On failing we annotate screenshot attempts to help to debug
36
+ def take_comparison_screenshot(screenshot_path)
37
+ new_screenshot_path = Screenshoter.gen_next_attempt_path(screenshot_path, 0)
38
+
39
+ take_screenshot(new_screenshot_path)
40
+
41
+ FileUtils.mv(new_screenshot_path, screenshot_path, force: true)
42
+ Screenshoter.cleanup_attempts_screenshots(screenshot_path)
43
+ end
44
+
45
+ def self.gen_next_attempt_path(screenshot_path, iteration)
46
+ Pathname.new(screenshot_path).sub_ext(format(".attempt_%02i.png", iteration))
47
+ end
48
+
49
+ def take_screenshot(screenshot_path)
50
+ blurred_input = prepare_page_for_screenshot(timeout: wait)
51
+
52
+ # Take browser screenshot and save
53
+ browser_save_screenshot(screenshot_path)
54
+
55
+ # Load saved screenshot and pre-process it
56
+ process_screenshot(screenshot_path)
57
+ ensure
58
+ blurred_input&.click
59
+ end
60
+
61
+ def browser_save_screenshot(screenshot_path)
62
+ BrowserHelpers.session.save_screenshot(screenshot_path)
63
+ end
64
+
65
+ def process_screenshot(screenshot_path)
66
+ # TODO(uwe): Remove when chromedriver takes right size screenshots
67
+ # TODO: Adds tests when this case is true
68
+ if selenium_with_retina_screen?
69
+ reduce_retina_image_size(screenshot_path)
70
+ end
71
+ # ODOT
72
+
73
+ if crop
74
+ image = driver.from_file(screenshot_path)
75
+ cropped_image = driver.crop(crop, image)
76
+ driver.save_image_to(cropped_image, screenshot_path)
77
+ end
78
+ end
79
+
80
+ def reduce_retina_image_size(file_name)
81
+ expected_image_width = Screenshot.window_size[0]
82
+ saved_image = driver.from_file(file_name.to_s)
83
+ return if driver.width_for(saved_image) < expected_image_width * 2
84
+
85
+ notice_how_to_avoid_this
86
+
87
+ new_height = expected_image_width * driver.height_for(saved_image) / driver.width_for(saved_image)
88
+ resized_image = driver.resize_image_to(saved_image, expected_image_width, new_height)
89
+
90
+ driver.save_image_to(resized_image, file_name)
91
+ end
92
+
93
+ def notice_how_to_avoid_this
94
+ unless defined?(@_csd_retina_warned)
95
+ warn "Halving retina screenshot. " \
96
+ 'You should add "force-device-scale-factor=1" to your Chrome chromeOptions args.'
97
+ @_csd_retina_warned = true
98
+ end
99
+ end
100
+
101
+ def prepare_page_for_screenshot(timeout:)
102
+ wait_images_loaded(timeout: timeout)
103
+
104
+ blurred_input = if Screenshot.blur_active_element
105
+ BrowserHelpers.blur_from_focused_element
106
+ end
107
+
108
+ if Screenshot.hide_caret
109
+ BrowserHelpers.hide_caret
110
+ end
111
+
112
+ blurred_input
113
+ end
114
+
115
+ def wait_images_loaded(timeout:)
116
+ start = Time.now
117
+ loop do
118
+ pending_image = BrowserHelpers.pending_image_to_load
119
+ break unless pending_image
120
+
121
+ assert(
122
+ (Time.now - start) < timeout,
123
+ "Images not loaded after #{timeout}s: #{pending_image.inspect}"
124
+ )
125
+
126
+ sleep 0.025
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ def selenium_with_retina_screen?
133
+ Os::ON_MAC && BrowserHelpers.selenium? && Screenshot.window_size
134
+ end
135
+ end
136
+ end
137
+ end
@@ -1,184 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "os"
4
-
5
- module Capybara
6
- module Screenshot
7
- module Diff
8
- module Stabilization
9
- include Os
10
-
11
- def take_stable_screenshot(comparison, stability_time_limit:, wait:, crop:)
12
- previous_file_name = comparison.old_file_name
13
- screenshot_started_at = last_image_change_at = Time.now
14
- clean_stabilization_images(comparison.new_file_name)
15
-
16
- 1.step do |i|
17
- take_right_size_screenshot(comparison, crop: crop)
18
- if comparison.quick_equal?
19
- clean_stabilization_images(comparison.new_file_name)
20
- break
21
- end
22
-
23
- comparison.reset
24
-
25
- if previous_file_name
26
- stabilization_comparison = make_stabilization_comparison_from(
27
- comparison,
28
- comparison.new_file_name,
29
- previous_file_name
30
- )
31
- if stabilization_comparison.quick_equal?
32
- if (Time.now - last_image_change_at) > stability_time_limit
33
- clean_stabilization_images(comparison.new_file_name)
34
- break
35
- end
36
- next
37
- else
38
- last_image_change_at = Time.now
39
- end
40
- end
41
-
42
- previous_file_name = build_snapshot_version_file_name(
43
- comparison,
44
- i,
45
- screenshot_started_at,
46
- stabilization_comparison
47
- )
48
-
49
- FileUtils.mv(comparison.new_file_name, previous_file_name)
50
-
51
- check_max_wait_time(
52
- comparison,
53
- screenshot_started_at,
54
- max_wait_time: max_wait_time(comparison.shift_distance_limit, wait)
55
- )
56
- end
57
- end
58
-
59
- def notice_how_to_avoid_this
60
- unless defined?(@_csd_retina_warned)
61
- warn "Halving retina screenshot. " \
62
- 'You should add "force-device-scale-factor=1" to your Chrome chromeOptions args.'
63
- @_csd_retina_warned = true
64
- end
65
- end
66
-
67
- private
68
-
69
- def build_snapshot_version_file_name(comparison, iteration, screenshot_started_at, stabilization_comparison)
70
- "#{comparison.new_file_name.chomp(".png")}" \
71
- "_x#{format("%02i", iteration)}_#{(Time.now - screenshot_started_at).round(1)}s" \
72
- "_#{stabilization_comparison.difference_coordinates&.to_s&.gsub(", ", "_") || :initial}.png" \
73
- "#{ImageCompare::TMP_FILE_SUFFIX}"
74
- end
75
-
76
- def make_stabilization_comparison_from(comparison, new_file_name, previous_file_name)
77
- ImageCompare.new(new_file_name, previous_file_name, comparison.driver_options)
78
- end
79
-
80
- def reduce_retina_image_size(file_name, driver)
81
- return if !ON_MAC || !selenium? || !Capybara::Screenshot.window_size
82
-
83
- expected_image_width = Capybara::Screenshot.window_size[0]
84
- saved_image = driver.from_file(file_name)
85
- return if driver.width_for(saved_image) < expected_image_width * 2
86
-
87
- notice_how_to_avoid_this
88
-
89
- new_height = expected_image_width * driver.height_for(saved_image) / driver.width_for(saved_image)
90
- resized_image = driver.resize_image_to(saved_image, expected_image_width, new_height)
91
-
92
- driver.save_image_to(resized_image, file_name)
93
- end
94
-
95
- def stabilization_images(base_file)
96
- Dir["#{base_file.chomp(".png")}_x*.png#{ImageCompare::TMP_FILE_SUFFIX}"].sort
97
- end
98
-
99
- def clean_stabilization_images(base_file)
100
- FileUtils.rm stabilization_images(base_file)
101
- end
102
-
103
- def prepare_page_for_screenshot(timeout:)
104
- assert_images_loaded(timeout: timeout)
105
-
106
- if Capybara::Screenshot.blur_active_element
107
- blurred_input = blur_from_focused_element
108
- end
109
-
110
- if Capybara::Screenshot.hide_caret
111
- hide_caret
112
- end
113
-
114
- blurred_input
115
- end
116
-
117
- def take_right_size_screenshot(comparison, crop:)
118
- driver = comparison.driver
119
-
120
- save_screenshot(comparison.new_file_name)
121
-
122
- # TODO(uwe): Remove when chromedriver takes right size screenshots
123
- reduce_retina_image_size(comparison.new_file_name, driver)
124
- # ODOT
125
-
126
- if crop
127
- image = driver.from_file(comparison.new_file_name)
128
- cropped_image = driver.crop(crop, image)
129
- driver.save_image_to(cropped_image, comparison.new_file_name)
130
- end
131
- end
132
-
133
- def check_max_wait_time(comparison, screenshot_started_at, max_wait_time:)
134
- return if (Time.now - screenshot_started_at) < max_wait_time
135
-
136
- annotate_stabilization_images(comparison)
137
- # FIXME(uwe): Change to store the failure and only report if the test succeeds functionally.
138
- fail("Could not get stable screenshot within #{max_wait_time}s\n" \
139
- "#{stabilization_images(comparison.new_file_name).join("\n")}")
140
- end
141
-
142
- def annotate_stabilization_images(comparison)
143
- previous_file = comparison.old_file_name
144
- stabilization_images(comparison.new_file_name).each do |file_name|
145
- if File.exist? previous_file
146
- stabilization_comparison = make_stabilization_comparison_from(
147
- comparison,
148
- file_name,
149
- previous_file
150
- )
151
- if stabilization_comparison.different?
152
- FileUtils.mv stabilization_comparison.annotated_new_file_name, file_name
153
- end
154
- FileUtils.rm stabilization_comparison.annotated_old_file_name
155
- end
156
- previous_file = file_name
157
- end
158
- end
159
-
160
- def max_wait_time(shift_distance_limit, wait)
161
- shift_factor = shift_distance_limit ? (shift_distance_limit * 2 + 1) ^ 2 : 1
162
- wait * shift_factor
163
- end
164
-
165
- def assert_images_loaded(timeout:)
166
- return unless respond_to? :evaluate_script
167
-
168
- start = Time.now
169
- loop do
170
- pending_image = pending_image_to_load
171
- break unless pending_image
172
-
173
- assert(
174
- (Time.now - start) < timeout,
175
- "Images not loaded after #{timeout}s: #{pending_image.inspect}"
176
- )
177
-
178
- sleep 0.1
179
- end
180
- end
181
- end
182
- end
183
- end
184
- end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capybara
4
+ module Screenshot
5
+ module Diff
6
+ class StableScreenshoter
7
+ STABILITY_OPTIONS = [:stability_time_limit, :wait]
8
+
9
+ attr_reader :stability_time_limit, :wait
10
+
11
+ def initialize(capture_options, comparison_options = nil)
12
+ @stability_time_limit, @wait = capture_options.fetch_values(:stability_time_limit, :wait)
13
+ @comparison_options = comparison_options || Diff.default_options
14
+ @screenshoter = Diff.screenshoter.new(capture_options.except(*STABILITY_OPTIONS), @comparison_options[:driver])
15
+ end
16
+
17
+ # Try to get screenshot from browser.
18
+ # On `stability_time_limit` it checks that page stop updating by comparison several screenshot attempts
19
+ # On reaching `wait` limit then it has been failed. On failing we annotate screenshot attempts to help to debug
20
+ def take_comparison_screenshot(screenshot_path)
21
+ new_screenshot_path = take_stable_screenshot(screenshot_path)
22
+
23
+ # We failed to get stable browser state! Generate difference between attempts to overview moving parts!
24
+ unless new_screenshot_path
25
+ # FIXME(uwe): Change to store the failure and only report if the test succeeds functionally.
26
+ annotate_attempts_and_fail!(screenshot_path)
27
+ end
28
+
29
+ FileUtils.mv(new_screenshot_path, screenshot_path, force: true)
30
+ Screenshoter.cleanup_attempts_screenshots(screenshot_path)
31
+ end
32
+
33
+ def take_stable_screenshot(screenshot_path)
34
+ # We try to compare first attempt with checkout version, in order to not run next screenshots
35
+ attempt_path = nil
36
+ screenshot_started_at = last_attempt_at = Time.now
37
+
38
+ # Cleanup all previous attempts for sure
39
+ Screenshoter.cleanup_attempts_screenshots(screenshot_path)
40
+
41
+ 0.step do |i|
42
+ # Prevents redundant screenshots generations
43
+ sleep(stability_time_limit) unless i == 0
44
+
45
+ elapsed_time = last_attempt_at - screenshot_started_at
46
+
47
+ prev_attempt_path = attempt_path
48
+ attempt_path = Screenshoter.gen_next_attempt_path(screenshot_path, i)
49
+
50
+ @screenshoter.take_screenshot(attempt_path)
51
+ last_attempt_at = Time.now
52
+
53
+ next unless prev_attempt_path
54
+ stabilization_comparator = build_comparison_for(attempt_path, prev_attempt_path)
55
+
56
+ # If previous screenshot is equal to the current, then we are good
57
+ return attempt_path if prev_attempt_path && stabilization_comparator.quick_equal?
58
+
59
+ # If timeout then we failed to generate valid screenshot
60
+ return nil if timeout?(elapsed_time)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def build_comparison_for(attempt_path, previous_attempt_path)
67
+ ImageCompare.new(attempt_path, previous_attempt_path, @comparison_options)
68
+ end
69
+
70
+ def annotate_attempts_and_fail!(screenshot_path)
71
+ screenshot_attempts = Screenshoter.attempts_screenshot_paths(screenshot_path)
72
+
73
+ annotate_stabilization_images(screenshot_attempts)
74
+
75
+ # TODO: Move fail to the queue after tests passed
76
+ fail("Could not get stable screenshot within #{wait}s:\n#{screenshot_attempts.join("\n")}")
77
+ end
78
+
79
+ # TODO: Add tests that we annotate all files except first one
80
+ def annotate_stabilization_images(attempts_screenshot_paths)
81
+ previous_file = nil
82
+ attempts_screenshot_paths.reverse_each do |file_name|
83
+ if previous_file && File.exist?(previous_file)
84
+ attempts_comparison = build_comparison_for(file_name, previous_file)
85
+
86
+ if attempts_comparison.different?
87
+ FileUtils.mv(attempts_comparison.annotated_base_image_path, previous_file, force: true)
88
+ else
89
+ warn "[capybara-screenshot-diff] Some attempts was stable, but mistakenly marked as not: " \
90
+ "#{previous_file} and #{file_name} are equal"
91
+ end
92
+
93
+ FileUtils.rm(attempts_comparison.annotated_image_path, force: true)
94
+ end
95
+
96
+ previous_file = file_name
97
+ end
98
+ end
99
+
100
+ def timeout?(elapsed_time)
101
+ elapsed_time > wait
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end