cornucopia 0.1.55 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -2
  3. data/.ruby-version +1 -0
  4. data/Gemfile +4 -1
  5. data/Gemfile.lock +297 -0
  6. data/cornucopia.gemspec +13 -12
  7. data/lib/cornucopia/capybara/finder_diagnostics/find_action/found_element.rb +155 -0
  8. data/lib/cornucopia/capybara/finder_diagnostics/find_action.rb +417 -0
  9. data/lib/cornucopia/capybara/finder_diagnostics.rb +9 -551
  10. data/lib/cornucopia/capybara/finder_extensions.rb +34 -26
  11. data/lib/cornucopia/capybara/install_extensions.rb +6 -6
  12. data/lib/cornucopia/capybara/matcher_extensions.rb +110 -47
  13. data/lib/cornucopia/capybara/page_diagnostics/window_iterator.rb +42 -0
  14. data/lib/cornucopia/capybara/page_diagnostics.rb +1 -30
  15. data/lib/cornucopia/capybara/synchronizable.rb +1 -1
  16. data/lib/cornucopia/site_prism/class_extensions.rb +13 -0
  17. data/lib/cornucopia/site_prism/element_extensions.rb +7 -4
  18. data/lib/cornucopia/site_prism/install_extensions.rb +1 -1
  19. data/lib/cornucopia/site_prism/page_application.rb +3 -3
  20. data/lib/cornucopia/site_prism/section_extensions.rb +3 -11
  21. data/lib/cornucopia/util/report_builder.rb +1 -1
  22. data/lib/cornucopia/version.rb +1 -1
  23. data/spec/dummy/app/assets/config/manifest.js +2 -0
  24. data/spec/dummy/config/application.rb +1 -1
  25. data/spec/lib/capybara/finder_diagnostics_spec.rb +11 -13
  26. data/spec/lib/capybara/finder_extensions_spec.rb +19 -11
  27. data/spec/lib/capybara/matcher_extensions_spec.rb +13 -15
  28. data/spec/lib/capybara/page_diagnostics_spec.rb +1 -1
  29. data/spec/lib/site_prism/element_extensions_spec.rb +3 -3
  30. data/spec/lib/util/configured_report_spec.rb +4 -4
  31. data/spec/lib/util/log_capture_spec.rb +5 -5
  32. data/spec/lib/util/report_builder_spec.rb +3 -3
  33. data/spec/lib/util/report_table_spec.rb +5 -5
  34. data/spec/pages/cornucopia_report_pages/cornucopia_report_test_contents_page.rb +1 -1
  35. data/spec/sample_report.rb +6 -6
  36. data/spec/spec_helper.rb +5 -1
  37. metadata +37 -10
@@ -0,0 +1,417 @@
1
+ # frozen_string_literal: true
2
+
3
+ require ::File.expand_path("../../util/configuration", File.dirname(__FILE__))
4
+ require ::File.expand_path("../../util/report_builder", File.dirname(__FILE__))
5
+ require ::File.expand_path("find_action/found_element", File.dirname(__FILE__))
6
+
7
+ module Cornucopia
8
+ module Capybara
9
+ class FinderDiagnostics
10
+ # At the end of the day, almost everything in Capybara calls all to find the element that needs
11
+ # to be worked on. The main difference is if it is synchronized or not.
12
+ #
13
+ # The FindAction class also uses all, but it is never synchronized, and its primary purpose
14
+ # is really to output a bunch of diagnostic information to try to help you do a postmortem on
15
+ # just what is happening.
16
+ #
17
+ # A lot of things could be happening, so a lot of information is output, not all of which is
18
+ # relevant or even useful in every situation.
19
+ #
20
+ # The first thing output is the error (the problem)
21
+ # Then what action was being tried is output (context)
22
+ #
23
+ # In case the problem was with finding the desired element, a list of all elements which
24
+ # could be found using the passed in parameters is output.
25
+ #
26
+ # In case the problem was with context (inside a within block on purpose or accidentally)
27
+ # a list of all other elements which could be found on the page using the passed in
28
+ # parameters is output.
29
+ #
30
+ # In case the problem is something else, we output a screenshot and the page HTML.
31
+ #
32
+ # In case the problem has now solved itself, we try the action again. (This has a very low
33
+ # probability of working as this basically devolves to just duplicating what Capybara is already doing,
34
+ # but it seems like it is worth a shot at least...)
35
+ #
36
+ # In case the problem is a driver bug (specifically Selenium which has some known issues) and
37
+ # is in fact why I even bother trying to do this, try performing the action via javascript.
38
+ # NOTE: As noted in many blogs this type of workaround is not generally a good idea as it can
39
+ # result in false-positives. However since Selenium is buggy, this is the
40
+ # best solution I have other than going to capybara-webkit or poltergeist
41
+ class FindAction
42
+ @@diagnosed_finders = {}
43
+
44
+ Cornucopia::Util::ReportBuilder.on_close do
45
+ Cornucopia::Capybara::FinderDiagnostics::FindAction.clear_diagnosed_finders
46
+ end
47
+
48
+ # Clears the class variable @@diagnosed_finders between tests if called.
49
+ # This is done so that finder analysis is called at least once per test.
50
+ def self.clear_diagnosed_finders
51
+ @@diagnosed_finders = {}
52
+ end
53
+
54
+ attr_accessor :return_value
55
+ attr_accessor :support_options
56
+
57
+ def initialize(test_object, report_options, support_options, function_name, *args, **options, &block)
58
+ @test_object = test_object
59
+ @function_name = function_name
60
+ @args = args
61
+ @block = block
62
+ @support_options = support_options
63
+ @options = options
64
+ @report_options = report_options || {}
65
+
66
+ @report_options[:report] ||= Cornucopia::Util::ReportBuilder.current_report
67
+ end
68
+
69
+ def run
70
+ begin
71
+ simple_run
72
+ rescue
73
+ error = $!
74
+ if perform_analysis(support_options[:__cornucopia_retry_with_found])
75
+ # Cornucopia::Util::Configuration.alternate_retry)
76
+ @return_value
77
+ else
78
+ raise error
79
+ end
80
+ end
81
+ end
82
+
83
+ def simple_run(cornucopia_args = {})
84
+ simple_run_args = args.clone
85
+ simple_run_options = options.clone.merge(cornucopia_args)
86
+
87
+ test_object.send(function_name, *simple_run_args, **simple_run_options)
88
+ end
89
+
90
+ # def can_dump_details?(attempt_retry, attempt_alternate_retry)
91
+ def can_dump_details?(attempt_retry)
92
+ can_dump = false
93
+
94
+ if (Object.const_defined?("Capybara"))
95
+ can_dump = !@@diagnosed_finders.keys.include?(dump_detail_args(attempt_retry))
96
+ end
97
+
98
+ can_dump
99
+ end
100
+
101
+ def capybara_session
102
+ if Object.const_defined?("::Capybara") &&
103
+ ::Capybara.send(:session_pool).present?
104
+ my_page = ::Capybara.current_session
105
+
106
+ my_page if (my_page && my_page.current_url.present? && my_page.current_url != "about:blank")
107
+ end
108
+ rescue StandardError
109
+ nil
110
+ end
111
+
112
+ # def dump_detail_args(attempt_retry, attempt_alternate_retry)
113
+ def dump_detail_args(attempt_retry)
114
+ check_args = search_args.clone
115
+ my_page = capybara_session
116
+
117
+ check_args << options.clone
118
+ check_args << !!attempt_retry
119
+ # check_args << !!attempt_alternate_retry
120
+
121
+ if (my_page && my_page.current_url.present? && my_page.current_url != "about:blank")
122
+ check_args << Digest::SHA2.hexdigest(my_page.html)
123
+ end
124
+
125
+ check_args
126
+ end
127
+
128
+ # def perform_retry(attempt_retry, attempt_alternate_retry, report, report_table)
129
+ def perform_retry(attempt_retry, report, report_table)
130
+ retry_successful = false
131
+
132
+ if attempt_retry && retry_action_with_found_element(report, report_table)
133
+ retry_successful = true
134
+ # else
135
+ # if attempt_alternate_retry && alternate_action_with_found_element(report, report_table)
136
+ # retry_successful = true
137
+ # end
138
+ end
139
+
140
+ retry_successful
141
+ end
142
+
143
+ # def perform_analysis(attempt_retry, attempt_alternate_retry)
144
+ def perform_analysis(attempt_retry)
145
+ retry_successful = false
146
+
147
+ time = Benchmark.measure do
148
+ puts " Cornucopia::FinderDiagnostics::perform_analysis" if Cornucopia::Util::Configuration.benchmark
149
+
150
+ if can_dump_details?(attempt_retry)
151
+ generate_report "An error occurred while processing \"#{function_name.to_s}\":",
152
+ $! do |report, report_table|
153
+ retry_successful = perform_retry(attempt_retry, report, report_table)
154
+ end
155
+
156
+ dump_args = dump_detail_args(attempt_retry)
157
+ @@diagnosed_finders[dump_args] = { tried: true }
158
+ else
159
+ retry_successful = perform_retry(attempt_retry, nil, nil)
160
+ end
161
+ end
162
+
163
+ puts " Cornucopia::FinderDiagnostics::perform_analysis time: #{time}" if Cornucopia::Util::Configuration.benchmark
164
+
165
+ retry_successful
166
+ end
167
+
168
+ def generate_report(message, error = nil, &block)
169
+ if report_options[:report] && report_options[:table]
170
+ generate_report_in_table report_options[:table], error, &block
171
+ else
172
+ report_options[:report] ||= Cornucopia::Util::ReportBuilder.current_report
173
+ report_options[:table] = nil
174
+
175
+ report_options[:report].within_section(message) do |_report|
176
+ generate_report_in_table report_options[:table], error, &block
177
+ end
178
+ end
179
+ end
180
+
181
+ def generate_report_in_table(table, error = nil, &block)
182
+ report_options[:table] = table
183
+
184
+ init_search_args
185
+ all_elements
186
+ all_other_elements
187
+ guessed_types
188
+
189
+ configured_report = Cornucopia::Util::Configuration.report_configuration(:capybara_finder_diagnostics)
190
+
191
+ configured_report.add_report_objects(finder: self, exception: error)
192
+ configured_report.generate_report(report_options[:report], report_table: report_options[:table], &block)
193
+ end
194
+
195
+ def retry_action_with_found_element report, report_table
196
+ return_result = false
197
+ result = "Failed"
198
+
199
+ case function_name.to_s
200
+ when "assert_selector"
201
+ if found_element
202
+ @return_value = true
203
+ return_result = true
204
+ result = "Found"
205
+ end
206
+
207
+ when "assert_no_selector"
208
+ unless found_element
209
+ @return_value = true
210
+ return_result = true
211
+ result = "Not Found"
212
+ end
213
+
214
+ when "find", "all"
215
+ if found_element
216
+ result = "Success"
217
+
218
+ @return_value = found_element
219
+
220
+ return_result = true
221
+ end
222
+ end
223
+
224
+ report_table.write_stats "Retrying action:", result if report_table && return_result
225
+
226
+ return_result
227
+ end
228
+
229
+ # def alternate_action_with_found_element report, report_table
230
+ # return_result = false
231
+ #
232
+ # result = "Could not attempt to try the action through an alternate method."
233
+ # if found_element &&
234
+ # ::Capybara.current_session.respond_to?(:evaluate_script)
235
+ # begin
236
+ # native_id = get_attribute found_element, "id"
237
+ # if (native_id)
238
+ # case function_name.to_s
239
+ # when "click_link_or_button", "click_link", "click_button"
240
+ # @return_value = ::Capybara.current_session.evaluate_script("$(\"\##{native_id}\")[0].click()")
241
+ # when "fill_in"
242
+ # @return_value = ::Capybara.current_session.evaluate_script("$(\"\##{native_id}\")[0].val(\"#{options[:with]}\")")
243
+ # when "choose", "check"
244
+ # @return_value = ::Capybara.current_session.evaluate_script("$(\"\##{native_id}\")[0].val(\"checked\", true)")
245
+ # when "uncheck"
246
+ # @return_value = ::Capybara.current_session.evaluate_script("$(\"\##{native_id}\")[0].val(\"checked\", false)")
247
+ # when "select"
248
+ # @return_value = ::Capybara.current_session.evaluate_script("$(\"\##{native_id}\")[0].val(\"selected\", true)")
249
+ # when "unselect"
250
+ # @return_value = ::Capybara.current_session.evaluate_script("$(\"\##{native_id}\")[0].val(\"selected\", false)")
251
+ # else
252
+ # result = "Could not decide what to do with #{function_name}"
253
+ # raise new Exception("unknown action")
254
+ # end
255
+ #
256
+ # return_result = true
257
+ # end
258
+ # rescue
259
+ # result ||= "Still couldn't do the action - #{$!.to_s}."
260
+ # end
261
+ # end
262
+ #
263
+ # report_table.write_stats "Trying alternate action:", result if report_table
264
+ # return_result
265
+ # end
266
+
267
+ def found_element
268
+ if function_name.to_sym == :all
269
+ all_elements.map(&:found_element)
270
+ else
271
+ if all_elements && all_elements.length == 1
272
+ all_elements[0].try(:found_element)
273
+ else
274
+ nil
275
+ end
276
+ end
277
+ end
278
+
279
+ def all_elements
280
+ unless @all_elements
281
+ all_options = options.clone
282
+
283
+ if all_options && all_options.has_key?(:from)
284
+ from_within = nil
285
+
286
+ report_options[:report].within_table(report_table: report_options[:table]) do |report_table|
287
+ Cornucopia::Util::ReportTable.new(nested_table: report_table,
288
+ nested_table_label: "Within block:") do |sub_report|
289
+ sub_report_options = report_options.clone
290
+ sub_report_options[:table] = sub_report
291
+ from_within = Cornucopia::Capybara::FinderDiagnostics::FindAction.
292
+ new(test_object,
293
+ sub_report_options,
294
+ {},
295
+ :find,
296
+ :select,
297
+ all_options[:from])
298
+
299
+ from_within.generate_report_in_table(sub_report, nil)
300
+ end
301
+ end
302
+
303
+ from_element = from_within.found_element
304
+ if search_args[0].is_a?(Symbol)
305
+ search_args[0] = :option
306
+ end
307
+ all_options.delete(:from)
308
+
309
+ unless from_element
310
+ @all_elements = []
311
+ return @all_elements
312
+ end
313
+ else
314
+ from_element = test_object
315
+ end
316
+
317
+ begin
318
+ @all_elements = from_element.all(*search_args, **all_options.merge(visible: false, __cornucopia_no_analysis: true)).to_a
319
+ rescue
320
+ @all_elements = []
321
+ end
322
+
323
+ if @all_elements
324
+ @all_elements = @all_elements.map do |element|
325
+ FoundElement.new(element)
326
+ end.compact
327
+ end
328
+ end
329
+
330
+ @all_elements
331
+ end
332
+
333
+ def all_other_elements
334
+ unless @all_other_elements
335
+ from_element = capybara_session
336
+ all_options = options.clone
337
+ all_options.delete :from
338
+
339
+ return unless from_element
340
+
341
+ begin
342
+ @all_other_elements = from_element.all(*search_args, **all_options.merge(visible: false, __cornucopia_no_analysis: true)).to_a
343
+ rescue
344
+ @all_other_elements = []
345
+ end
346
+
347
+ if @all_other_elements
348
+ @all_other_elements = @all_other_elements.map do |element|
349
+ FoundElement.new(element) unless all_elements.include?(element)
350
+ end
351
+
352
+ @all_other_elements = @all_other_elements - @all_elements
353
+ @all_other_elements.compact!
354
+ end
355
+ end
356
+
357
+ @all_other_elements
358
+ end
359
+
360
+ def search_args
361
+ init_search_args
362
+ @search_args
363
+ end
364
+
365
+ def options
366
+ init_search_args
367
+ @options
368
+ end
369
+
370
+ def init_search_args
371
+ unless @search_args
372
+ @search_args = args.clone
373
+
374
+ if guessed_types.length > 0 && @search_args[0] != guessed_types[0]
375
+ @search_args.insert(0, guessed_types[0])
376
+ end
377
+ if guessed_types.length <= 0 && @search_args[0] != ::Capybara.default_selector
378
+ @search_args.insert(0, ::Capybara.default_selector)
379
+ end
380
+ end
381
+ end
382
+
383
+ # a list of guesses as to what kind of object is being searched for
384
+ def guessed_types
385
+ unless @guessed_types
386
+ if search_args.length > 0
387
+ if search_args[0].is_a?(Symbol)
388
+ @guessed_types = [search_args[0]]
389
+ else
390
+ @guessed_types = %i[id css xpath link_or_button fillable_field radio_button checkbox select option
391
+ file_field table field fieldset content].select do |test_type|
392
+ begin
393
+ test_object.all(test_type, *search_args, **options.merge(visible: false, __cornucopia_no_analysis: true)).length > 0
394
+ rescue
395
+ # Normally bad form, but for this function, we just don't want this to throw errors.
396
+ # We are only concerned with whatever actually succeeds.
397
+ false
398
+ end
399
+ end
400
+ end
401
+ end
402
+ end
403
+
404
+ @guessed_types
405
+ end
406
+
407
+ private
408
+
409
+ attr_reader :test_object,
410
+ :function_name,
411
+ :args,
412
+ :block,
413
+ :report_options
414
+ end
415
+ end
416
+ end
417
+ end