cornucopia 0.1.55 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|