cornucopia 0.1.55 → 0.2.0
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.
- checksums.yaml +5 -5
- data/.gitignore +0 -2
- data/.ruby-version +1 -0
- data/Gemfile +4 -1
- data/Gemfile.lock +297 -0
- data/cornucopia.gemspec +13 -12
- data/lib/cornucopia/capybara/finder_diagnostics/find_action/found_element.rb +155 -0
- data/lib/cornucopia/capybara/finder_diagnostics/find_action.rb +417 -0
- data/lib/cornucopia/capybara/finder_diagnostics.rb +9 -551
- data/lib/cornucopia/capybara/finder_extensions.rb +34 -26
- data/lib/cornucopia/capybara/install_extensions.rb +6 -6
- data/lib/cornucopia/capybara/matcher_extensions.rb +110 -47
- data/lib/cornucopia/capybara/page_diagnostics/window_iterator.rb +42 -0
- data/lib/cornucopia/capybara/page_diagnostics.rb +1 -30
- data/lib/cornucopia/capybara/synchronizable.rb +1 -1
- data/lib/cornucopia/site_prism/class_extensions.rb +13 -0
- data/lib/cornucopia/site_prism/element_extensions.rb +7 -4
- data/lib/cornucopia/site_prism/install_extensions.rb +1 -1
- data/lib/cornucopia/site_prism/page_application.rb +3 -3
- data/lib/cornucopia/site_prism/section_extensions.rb +3 -11
- data/lib/cornucopia/util/report_builder.rb +1 -1
- data/lib/cornucopia/version.rb +1 -1
- data/spec/dummy/app/assets/config/manifest.js +2 -0
- data/spec/dummy/config/application.rb +1 -1
- data/spec/lib/capybara/finder_diagnostics_spec.rb +11 -13
- data/spec/lib/capybara/finder_extensions_spec.rb +19 -11
- data/spec/lib/capybara/matcher_extensions_spec.rb +13 -15
- data/spec/lib/capybara/page_diagnostics_spec.rb +1 -1
- data/spec/lib/site_prism/element_extensions_spec.rb +3 -3
- data/spec/lib/util/configured_report_spec.rb +4 -4
- data/spec/lib/util/log_capture_spec.rb +5 -5
- data/spec/lib/util/report_builder_spec.rb +3 -3
- data/spec/lib/util/report_table_spec.rb +5 -5
- data/spec/pages/cornucopia_report_pages/cornucopia_report_test_contents_page.rb +1 -1
- data/spec/sample_report.rb +6 -6
- data/spec/spec_helper.rb +5 -1
- 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
|