ruby_raider 3.13.0 → 4.0.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 +4 -4
- data/.github/workflows/runtime.yml +38 -0
- data/.github/workflows/system_tests.yml +1 -23
- data/.reek.yml +1 -0
- data/.rspec +1 -0
- data/Gemfile +2 -0
- data/README.md +16 -49
- data/RELEASE.md +22 -0
- data/bin/raider +5 -0
- data/lib/commands/scaffolding_commands.rb +2 -25
- data/lib/commands/utility_commands.rb +23 -3
- data/lib/generators/automation/automation_generator.rb +0 -5
- data/lib/generators/automation/templates/component.tt +1 -0
- data/lib/generators/automation/templates/home.tt +2 -2
- data/lib/generators/automation/templates/login.tt +6 -26
- data/lib/generators/automation/templates/page.tt +23 -18
- data/lib/generators/automation/templates/partials/home_page_selector.tt +1 -2
- data/lib/generators/automation/templates/partials/initialize_selector.tt +0 -8
- data/lib/generators/automation/templates/partials/pdp_page_selector.tt +1 -2
- data/lib/generators/automation/templates/partials/url_methods.tt +8 -2
- data/lib/generators/automation/templates/partials/visit_method.tt +0 -5
- data/lib/generators/automation/templates/pdp.tt +1 -9
- data/lib/generators/common_generator.rb +0 -2
- data/lib/generators/generator.rb +12 -19
- data/lib/generators/helper_generator.rb +21 -19
- data/lib/generators/infrastructure/templates/azure_pipelines.tt +1 -1
- data/lib/generators/infrastructure/templates/github.tt +2 -3
- data/lib/generators/infrastructure/templates/github_appium.tt +0 -8
- data/lib/generators/invoke_generators.rb +4 -23
- data/lib/generators/menu_generator.rb +1 -9
- data/lib/generators/rspec/templates/accessibility_spec.tt +1 -9
- data/lib/generators/rspec/templates/spec.tt +8 -24
- data/lib/generators/template_renderer/partial_cache.rb +3 -1
- data/lib/generators/template_renderer.rb +1 -1
- data/lib/generators/templates/common/gemfile.tt +3 -10
- data/lib/generators/templates/common/git_ignore.tt +0 -5
- data/lib/generators/templates/common/partials/mobile_config.tt +3 -1
- data/lib/generators/templates/common/partials/web_config.tt +10 -1
- data/lib/generators/templates/common/rakefile.tt +0 -15
- data/lib/generators/templates/common/read_me.tt +357 -250
- data/lib/generators/templates/common/rubocop.tt +0 -2
- data/lib/generators/templates/helpers/driver_helper.tt +8 -0
- data/lib/generators/templates/helpers/gesture_helper.tt +78 -0
- data/lib/generators/templates/helpers/network_helper.tt +521 -0
- data/lib/generators/templates/helpers/partials/quit_driver.tt +0 -2
- data/lib/generators/templates/helpers/partials/screenshot.tt +0 -2
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +5 -2
- data/lib/generators/templates/helpers/screenshot_helper.tt +490 -0
- data/lib/generators/templates/helpers/spec_helper.tt +39 -29
- data/lib/generators/templates/helpers/trace_helper.tt +240 -95
- data/lib/generators/templates/helpers/video_helper.tt +3 -8
- data/lib/ruby_raider/runtime/actionability.rb +129 -0
- data/lib/ruby_raider/runtime/actionable.rb +49 -0
- data/lib/ruby_raider/runtime/by_role.rb +55 -0
- data/lib/ruby_raider/runtime/config.rb +79 -0
- data/lib/ruby_raider/runtime/context.rb +38 -0
- data/lib/ruby_raider/runtime/errors.rb +69 -0
- data/lib/ruby_raider/runtime/finders.rb +73 -0
- data/lib/ruby_raider/runtime/js/actionability.js +134 -0
- data/lib/ruby_raider/runtime/js/role_engine.js +315 -0
- data/lib/ruby_raider/runtime/locator_converter.rb +72 -0
- data/lib/ruby_raider/runtime/matchers/base.rb +222 -0
- data/lib/ruby_raider/runtime/matchers/element.rb +183 -0
- data/lib/ruby_raider/runtime/matchers/page.rb +71 -0
- data/lib/ruby_raider/runtime/matchers/presence.rb +41 -0
- data/lib/ruby_raider/runtime/matchers/retrying_block.rb +104 -0
- data/lib/ruby_raider/runtime/observer.rb +170 -0
- data/lib/ruby_raider/runtime/role_finder.rb +83 -0
- data/lib/ruby_raider/runtime/rspec.rb +78 -0
- data/lib/ruby_raider/runtime/selectors.rb +60 -0
- data/lib/ruby_raider/runtime/strict.rb +142 -0
- data/lib/ruby_raider/runtime/wait.rb +84 -0
- data/lib/ruby_raider/runtime.rb +20 -0
- data/lib/scaffolding/name_normalizer.rb +1 -1
- data/lib/scaffolding/project_detector.rb +2 -28
- data/lib/scaffolding/scaffolding.rb +0 -20
- data/lib/scaffolding/templates/component.tt +0 -14
- data/lib/scaffolding/templates/page_object.tt +2 -14
- data/lib/scaffolding/templates/spec.tt +1 -9
- data/lib/utilities/utilities.rb +10 -8
- data/lib/version +1 -1
- data/ruby_raider.gemspec +2 -1
- data/sig/commands/scaffolding_commands.rbs +0 -2
- data/sig/commands/utility_commands.rbs +6 -0
- data/sig/generators/generator.rbs +0 -4
- data/sig/generators/helper_generator.rbs +0 -1
- data/sig/generators/menu_generator.rbs +0 -1
- data/sig/ruby_raider/runtime/actionability.rbs +25 -0
- data/sig/ruby_raider/runtime/actionable.rbs +13 -0
- data/sig/ruby_raider/runtime/by_role.rbs +15 -0
- data/sig/ruby_raider/runtime/config.rbs +26 -0
- data/sig/ruby_raider/runtime/context.rbs +12 -0
- data/sig/ruby_raider/runtime/errors.rbs +36 -0
- data/sig/ruby_raider/runtime/finders.rbs +26 -0
- data/sig/ruby_raider/runtime/locator_converter.rbs +21 -0
- data/sig/ruby_raider/runtime/matchers/base.rbs +64 -0
- data/sig/ruby_raider/runtime/matchers/element.rbs +78 -0
- data/sig/ruby_raider/runtime/matchers/page.rbs +38 -0
- data/sig/ruby_raider/runtime/matchers/presence.rbs +21 -0
- data/sig/ruby_raider/runtime/matchers/retrying_block.rbs +27 -0
- data/sig/ruby_raider/runtime/observer.rbs +46 -0
- data/sig/ruby_raider/runtime/role_finder.rbs +18 -0
- data/sig/ruby_raider/runtime/selectors.rbs +21 -0
- data/sig/ruby_raider/runtime/strict.rbs +35 -0
- data/sig/ruby_raider/runtime/wait.rbs +17 -0
- data/sig/ruby_raider/runtime.rbs +2 -0
- data/sig/scaffolding/project_detector.rbs +0 -4
- data/sig/scaffolding/scaffolding.rbs +0 -5
- data/sig/utilities/utilities.rbs +2 -1
- data/sig/vendor/selenium_webdriver.rbs +71 -0
- data/spec/commands/raider_commands_spec.rb +38 -3
- data/spec/commands/scaffolding_commands_spec.rb +27 -0
- data/spec/commands/utility_commands_spec.rb +53 -0
- data/spec/generators/gesture_helper_spec.rb +123 -0
- data/spec/generators/network_helper_spec.rb +634 -0
- data/spec/generators/screenshot_helper_spec.rb +776 -0
- data/spec/generators/template_renderer_spec.rb +0 -4
- data/spec/generators/trace_helper_spec.rb +489 -97
- data/spec/generators/video_helper_spec.rb +0 -9
- data/spec/integration/commands/browser_update_after_creation_spec.rb +0 -8
- data/spec/integration/commands/scaffolding_commands_spec.rb +12 -70
- data/spec/integration/commands/utility_commands_spec.rb +19 -41
- data/spec/integration/content/azure_content_spec.rb +2 -6
- data/spec/integration/content/ci_content_spec.rb +1 -55
- data/spec/integration/content/common_content_spec.rb +1 -118
- data/spec/integration/content/config_content_spec.rb +34 -31
- data/spec/integration/content/gemfile_content_spec.rb +7 -72
- data/spec/integration/content/helper_content_spec.rb +105 -99
- data/spec/integration/content/page_content_spec.rb +85 -87
- data/spec/integration/content/readme_content_spec.rb +134 -75
- data/spec/integration/content/rspec_reporter_content_spec.rb +2 -2
- data/spec/integration/content/rubocop_content_spec.rb +1 -1
- data/spec/integration/content/syntax_validation_spec.rb +2 -2
- data/spec/integration/content/test_content_spec.rb +23 -147
- data/spec/integration/end_to_end_features_spec.rb +1 -43
- data/spec/integration/end_to_end_spec.rb +319 -326
- data/spec/integration/generators/automation_generator_spec.rb +3 -37
- data/spec/integration/generators/axe_addon_spec.rb +2 -35
- data/spec/integration/generators/azure_generator_spec.rb +1 -1
- data/spec/integration/generators/common_generator_spec.rb +1 -40
- data/spec/integration/generators/config_features_spec.rb +1 -24
- data/spec/integration/generators/github_generator_spec.rb +0 -12
- data/spec/integration/generators/helpers_generator_spec.rb +74 -80
- data/spec/integration/generators/invoke_generators_spec.rb +54 -45
- data/spec/integration/generators/rspec_generator_spec.rb +0 -6
- data/spec/integration/scaffolding_e2e_spec.rb +17 -179
- data/spec/integration/spec_helper.rb +4 -6
- data/spec/integration/trace_format_checker.rb +11 -2
- data/spec/integration/trace_page_server.rb +72 -5
- data/spec/menus/menu_generator_spec.rb +14 -39
- data/spec/runtime/actionability_spec.rb +207 -0
- data/spec/runtime/actionable_spec.rb +125 -0
- data/spec/runtime/browser/actionability_script_browser_spec.rb +150 -0
- data/spec/runtime/browser/actionable_browser_spec.rb +98 -0
- data/spec/runtime/browser/finders_browser_spec.rb +148 -0
- data/spec/runtime/browser/matchers_browser_spec.rb +67 -0
- data/spec/runtime/browser/retrying_block_browser_spec.rb +37 -0
- data/spec/runtime/browser/role_engine_browser_spec.rb +184 -0
- data/spec/runtime/browser/strict_browser_spec.rb +38 -0
- data/spec/runtime/by_role_spec.rb +52 -0
- data/spec/runtime/config_spec.rb +79 -0
- data/spec/runtime/context_spec.rb +73 -0
- data/spec/runtime/errors_spec.rb +89 -0
- data/spec/runtime/finders_spec.rb +165 -0
- data/spec/runtime/fixtures/actionability.html +70 -0
- data/spec/runtime/fixtures/locators.html +46 -0
- data/spec/runtime/fixtures/matchers.html +33 -0
- data/spec/runtime/fixtures/roles.html +162 -0
- data/spec/runtime/matchers/base_spec.rb +165 -0
- data/spec/runtime/matchers/element_spec.rb +277 -0
- data/spec/runtime/matchers/page_spec.rb +102 -0
- data/spec/runtime/matchers/presence_spec.rb +59 -0
- data/spec/runtime/matchers/retrying_block_spec.rb +165 -0
- data/spec/runtime/observer_spec.rb +341 -0
- data/spec/runtime/role_finder_spec.rb +156 -0
- data/spec/runtime/role_query_spec.rb +75 -0
- data/spec/runtime/selectors_spec.rb +135 -0
- data/spec/runtime/strict_spec.rb +257 -0
- data/spec/runtime/support/browser.rb +48 -0
- data/spec/runtime/support/fake_bridge.rb +117 -0
- data/spec/runtime/wait_spec.rb +71 -0
- data/spec/scaffolding/name_normalizer_spec.rb +3 -2
- data/spec/scaffolding/scaffold_project_detector_spec.rb +4 -43
- data/spec/system/selenium_spec.rb +1 -5
- data/spec/system/support/system_test_helper.rb +2 -5
- data/spec/utilities/headless_config_spec.rb +0 -7
- data/spec/utilities/utilities_spec.rb +24 -0
- metadata +113 -48
- data/lib/generators/automation/templates/header.tt +0 -9
- data/lib/generators/cucumber/cucumber_generator.rb +0 -58
- data/lib/generators/cucumber/templates/accessibility_feature.tt +0 -5
- data/lib/generators/cucumber/templates/accessibility_steps.tt +0 -17
- data/lib/generators/cucumber/templates/cucumber.tt +0 -1
- data/lib/generators/cucumber/templates/env.tt +0 -7
- data/lib/generators/cucumber/templates/feature.tt +0 -21
- data/lib/generators/cucumber/templates/partials/appium_env.tt +0 -26
- data/lib/generators/cucumber/templates/partials/driver_world.tt +0 -18
- data/lib/generators/cucumber/templates/partials/mobile_steps.tt +0 -20
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +0 -120
- data/lib/generators/cucumber/templates/partials/watir_env.tt +0 -48
- data/lib/generators/cucumber/templates/partials/watir_world.tt +0 -4
- data/lib/generators/cucumber/templates/partials/web_steps.tt +0 -28
- data/lib/generators/cucumber/templates/steps.tt +0 -5
- data/lib/generators/cucumber/templates/user_factory.tt +0 -15
- data/lib/generators/cucumber/templates/world.tt +0 -7
- data/lib/generators/templates/helpers/browser_helper.tt +0 -31
- data/lib/generators/templates/helpers/raider_element.tt +0 -159
- data/lib/generators/templates/helpers/wait_helper.tt +0 -611
- data/lib/scaffolding/templates/feature.tt +0 -6
- data/lib/scaffolding/templates/steps.tt +0 -19
- data/sig/generators/cucumber/cucumber_generator.rbs +0 -12
- data/spec/fixtures/watir_master/cucumber_watir/Readme.md.snapshot +0 -175
- data/spec/fixtures/watir_master/cucumber_watir/features/step_definitions/login_steps.rb.snapshot +0 -24
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/abstract/page.rb.snapshot +0 -37
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/pages/login.rb.snapshot +0 -33
- data/spec/fixtures/watir_master/rspec_watir/Readme.md.snapshot +0 -174
- data/spec/fixtures/watir_master/rspec_watir/page_objects/abstract/page.rb.snapshot +0 -37
- data/spec/fixtures/watir_master/rspec_watir/page_objects/pages/login.rb.snapshot +0 -33
- data/spec/fixtures/watir_master/rspec_watir/spec/login_page_spec.rb.snapshot +0 -49
- data/spec/integration/content/cucumber_reporter_content_spec.rb +0 -161
- data/spec/integration/generators/cucumber_generator_spec.rb +0 -100
- data/spec/integration/helpers/wait_helper_mobile_spec.rb +0 -281
- data/spec/integration/helpers/wait_helper_spec.rb +0 -679
- data/spec/integration/helpers/wait_helper_support.rb +0 -183
- data/spec/system/watir_spec.rb +0 -38
|
@@ -1,679 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'wait_helper_support'
|
|
4
|
-
|
|
5
|
-
WaitHelperSpec.load_templates(mobile: false)
|
|
6
|
-
|
|
7
|
-
module WaitHelperSpec
|
|
8
|
-
class Page
|
|
9
|
-
include WaitHelper
|
|
10
|
-
|
|
11
|
-
attr_reader :driver
|
|
12
|
-
|
|
13
|
-
def initialize(driver)
|
|
14
|
-
@driver = driver
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# A <select> answering Support::Select's option queries.
|
|
19
|
-
class FakeSelect < FakeElement
|
|
20
|
-
OPTIONS = [%w[Red red], %w[Blue blue], %w[Green green]].freeze
|
|
21
|
-
|
|
22
|
-
def initialize(**state)
|
|
23
|
-
super(tag_name: 'select', **state)
|
|
24
|
-
@options = OPTIONS.each_with_index.map do |(label, value), index|
|
|
25
|
-
FakeElement.new(text: label, attributes: { 'value' => value }, index:)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
attr_reader :options
|
|
30
|
-
|
|
31
|
-
def find_elements(xpath: nil, tag_name: nil, **locator)
|
|
32
|
-
return super(**locator) unless xpath || tag_name
|
|
33
|
-
return @options if tag_name
|
|
34
|
-
|
|
35
|
-
text = xpath[/normalize-space\(\.\) = "(.*)"\]/, 1]
|
|
36
|
-
value = xpath[/@value = "(.*)"\]/, 1]
|
|
37
|
-
@options.select { |option| option.text == text || option.attribute('value') == value }
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
describe 'WaitHelper' do
|
|
43
|
-
let(:driver) { WaitHelperSpec::FakeDriver.new }
|
|
44
|
-
let(:page) { WaitHelperSpec::Page.new(driver) }
|
|
45
|
-
|
|
46
|
-
def element(**state) = WaitHelperSpec::FakeElement.new(**state)
|
|
47
|
-
def clock = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
48
|
-
def errors = Selenium::WebDriver::Error
|
|
49
|
-
|
|
50
|
-
describe '#find_element' do
|
|
51
|
-
it 'returns the element once it is present and visible' do
|
|
52
|
-
found = element
|
|
53
|
-
driver.script({ id: 'x' }, nil, element(displayed: false), found)
|
|
54
|
-
expect(page.find_element({ id: 'x' }, timeout: 1)).to eq(found)
|
|
55
|
-
expect(driver.finds.size).to eq(3)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
it 'returns a hidden element with visible: false' do
|
|
59
|
-
hidden = element(displayed: false)
|
|
60
|
-
driver.script({ id: 'x' }, hidden)
|
|
61
|
-
expect(page.find_element({ id: 'x' }, visible: false, timeout: 1)).to eq(hidden)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
it 'symbolizes a string-keyed locator' do
|
|
65
|
-
driver.script({ id: 'x' }, element)
|
|
66
|
-
page.find_element({ 'id' => 'x' }, timeout: 1)
|
|
67
|
-
expect(driver.finds).to eq([{ id: 'x' }])
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
it 'raises ArgumentError at once for an empty or a two-key locator' do
|
|
71
|
-
started = clock
|
|
72
|
-
expect { page.find_element({}) }.to raise_error(ArgumentError)
|
|
73
|
-
expect { page.find_element({ id: 'a', css: 'b' }) }.to raise_error(ArgumentError)
|
|
74
|
-
expect(clock - started).to be < 0.1
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
it 'makes one attempt at timeout: 0' do
|
|
78
|
-
driver.script({ id: 'hidden' }, element(displayed: false))
|
|
79
|
-
expect { page.find_element({ id: 'hidden' }, timeout: 0) }
|
|
80
|
-
.to raise_error(WaitHelper::TimeoutError, /last seen: present but not visible/)
|
|
81
|
-
expect(driver.finds.size).to eq(1)
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
describe '#find_elements' do
|
|
86
|
-
it 'returns every match, hidden ones included, once one is present' do
|
|
87
|
-
shown = element
|
|
88
|
-
hidden = element(displayed: false)
|
|
89
|
-
driver.script_many({ css: 'li' }, [], [shown, hidden])
|
|
90
|
-
expect(page.find_elements({ css: 'li' }, timeout: 1)).to eq([shown, hidden])
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
it 'returns [] at the timeout without raising' do
|
|
94
|
-
expect(page.find_elements({ css: 'li' }, timeout: 0.2)).to eq([])
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
describe '#click' do
|
|
99
|
-
it 'waits through not found, hidden, disabled and moving, then clicks once' do
|
|
100
|
-
target = element(rects: [[0, 0, 10, 10], [5, 5, 10, 10], [5, 5, 10, 10]])
|
|
101
|
-
driver.script({ id: 'go' }, nil, element(displayed: false), element(enabled: false), target)
|
|
102
|
-
page.click({ id: 'go' }, timeout: 2)
|
|
103
|
-
expect(target.log).to eq([[:click]])
|
|
104
|
-
expect(driver.finds.size).to eq(6)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
it 'retries intercepted, stale and not-interactable clicks, re-finding by locator' do
|
|
108
|
-
target = element(errors: [errors::ElementClickInterceptedError.new('covered'),
|
|
109
|
-
errors::StaleElementReferenceError.new('stale'),
|
|
110
|
-
errors::ElementNotInteractableError.new('not interactable')])
|
|
111
|
-
driver.script({ id: 'go' }, target)
|
|
112
|
-
page.click({ id: 'go' }, timeout: 3)
|
|
113
|
-
expect(target.log).to eq([[:click]])
|
|
114
|
-
expect(driver.finds.size).to be > 3
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it 'raises InvalidSelectorError on the first attempt' do
|
|
118
|
-
driver.script({ css: '[[' }, errors::InvalidSelectorError)
|
|
119
|
-
expect { page.click({ css: '[[' }, timeout: 1) }.to raise_error(errors::InvalidSelectorError)
|
|
120
|
-
expect(driver.finds.size).to eq(1)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
it 'resolves a per-OS locator with appium_device' do
|
|
124
|
-
android = WaitHelperSpec::FakeDriver.new(device: :android)
|
|
125
|
-
android.script({ accessibility_id: 'b' }, element)
|
|
126
|
-
per_os = { ios: { accessibility_id: 'a' }, android: { accessibility_id: 'b' } }
|
|
127
|
-
WaitHelperSpec::Page.new(android).click(per_os, timeout: 1)
|
|
128
|
-
expect(android.finds.uniq).to eq([{ accessibility_id: 'b' }])
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
it 'raises a stale passed element at once, suggesting a locator' do
|
|
132
|
-
passed = element
|
|
133
|
-
passed.go_stale
|
|
134
|
-
expect { page.click(passed, timeout: 2) }.to raise_error(errors::StaleElementReferenceError, /pass .*locator/)
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
describe 'typing and forms' do
|
|
139
|
-
it 'passes every key to send_keys' do
|
|
140
|
-
field = element
|
|
141
|
-
driver.script({ id: 'x' }, field)
|
|
142
|
-
page.send_keys({ id: 'x' }, 'a', :tab, timeout: 1)
|
|
143
|
-
expect(field.log).to eq([[:send_keys, 'a', :tab]])
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
it 'clears and submits once visible and enabled, returning the element' do
|
|
147
|
-
field = element
|
|
148
|
-
driver.script({ id: 'x' }, element(enabled: false), field)
|
|
149
|
-
expect(page.clear({ id: 'x' }, timeout: 1)).to eq(field)
|
|
150
|
-
expect(page.submit({ id: 'x' }, timeout: 1)).to eq(field)
|
|
151
|
-
expect(field.log).to eq([[:clear], [:submit]])
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
it 'selects by text, value and index through Support::Select' do
|
|
155
|
-
dropdown = WaitHelperSpec::FakeSelect.new
|
|
156
|
-
driver.script({ id: 'colour' }, dropdown)
|
|
157
|
-
page.select_by({ id: 'colour' }, :text, 'Blue', timeout: 1)
|
|
158
|
-
page.select_by({ id: 'colour' }, :value, 'green', timeout: 1)
|
|
159
|
-
page.select_by({ id: 'colour' }, :index, 0, timeout: 1)
|
|
160
|
-
expect(dropdown.options.map(&:log)).to eq([[[:click]], [[:click]], [[:click]]])
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
describe 'actions' do
|
|
165
|
-
it 'hovers with move_to once the element is stable' do
|
|
166
|
-
menu = element
|
|
167
|
-
driver.script({ id: 'menu' }, menu)
|
|
168
|
-
page.hover({ id: 'menu' }, timeout: 1)
|
|
169
|
-
expect(driver.actions).to eq([[:move_to, menu], [:perform]])
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
it 'drags the source onto the target' do
|
|
173
|
-
source = element
|
|
174
|
-
target = element
|
|
175
|
-
driver.script({ id: 'src' }, source).script({ id: 'tgt' }, target)
|
|
176
|
-
expect(page.drag_and_drop({ id: 'src' }, { id: 'tgt' }, timeout: 1)).to eq(source)
|
|
177
|
-
expect(driver.actions).to eq([[:drag_and_drop, source, target], [:perform]])
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
it 'scrolls a hidden but present element into view' do
|
|
181
|
-
below = element(displayed: false)
|
|
182
|
-
driver.script({ id: 'below' }, below)
|
|
183
|
-
page.scroll_into_view({ id: 'below' }, timeout: 1)
|
|
184
|
-
expect(driver.actions).to eq([[:scroll_to, below], [:perform]])
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
describe 'readers' do
|
|
189
|
-
it 'answer for a present element, hidden included' do
|
|
190
|
-
driver.script({ id: 'x' }, element(displayed: false, text: 'Hello', attributes: { 'href' => '/a' }))
|
|
191
|
-
expect(page.text({ id: 'x' }, timeout: 1)).to eq('Hello')
|
|
192
|
-
expect(page.attribute({ id: 'x' }, 'href', timeout: 1)).to eq('/a')
|
|
193
|
-
expect(page.displayed?({ id: 'x' }, timeout: 1)).to be(false)
|
|
194
|
-
expect(page.enabled?({ id: 'x' }, timeout: 1)).to be(true)
|
|
195
|
-
expect(page.selected?({ id: 'x' }, timeout: 1)).to be(false)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
it 'raise the timeout error when nothing matches' do
|
|
199
|
-
expect { page.text({ id: 'none' }, timeout: 0.2) }.to raise_error(WaitHelper::TimeoutError)
|
|
200
|
-
expect { page.displayed?({ id: 'none' }, timeout: 0.2) }.to raise_error(WaitHelper::TimeoutError)
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
describe 'braceless locators' do
|
|
205
|
-
it 'treat keywords as the locator' do
|
|
206
|
-
driver.script({ css: '.x' }, element(tag_name: 'span')).script({ id: 'y' }, element).script({ id: 'z' }, element)
|
|
207
|
-
expect(page.find_element(css: '.x').tag_name).to eq('span')
|
|
208
|
-
page.click(id: 'y')
|
|
209
|
-
expect(page.text(id: 'z')).to eq('hi')
|
|
210
|
-
expect(driver.finds.uniq).to eq([{ css: '.x' }, { id: 'y' }, { id: 'z' }])
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
describe 'timeout errors' do
|
|
215
|
-
it 'subclass Selenium TimeoutError, describe the wait and start the backtrace at the caller' do
|
|
216
|
-
driver.script({ id: 'x' }, element(displayed: false))
|
|
217
|
-
expect { page.send_keys({ id: 'x' }, 'a', timeout: 0.3) }.to raise_error(WaitHelper::TimeoutError) { |error|
|
|
218
|
-
expect(error).to be_a(Selenium::WebDriver::Error::TimeoutError)
|
|
219
|
-
expect(error.message)
|
|
220
|
-
.to eq('Waited 0.3s for {id: "x"} to be visible and enabled; last seen: present but not visible')
|
|
221
|
-
expect(error.backtrace.first).to include(File.basename(__FILE__))
|
|
222
|
-
expect(error.backtrace.first).not_to include('wait_helper.rb')
|
|
223
|
-
}
|
|
224
|
-
end
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
describe '#eventually' do
|
|
228
|
-
it 'returns the block value after two failing tries' do
|
|
229
|
-
tries = 0
|
|
230
|
-
result = page.eventually(timeout: 1) do
|
|
231
|
-
tries += 1
|
|
232
|
-
expect(tries).to eq(3)
|
|
233
|
-
:done
|
|
234
|
-
end
|
|
235
|
-
expect(result).to eq(:done)
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
it 're-raises the last expectation failure with the still-failing prefix' do
|
|
239
|
-
answer = 1
|
|
240
|
-
expect { page.eventually(timeout: 0.3) { expect(answer).to eq(2) } }
|
|
241
|
-
.to raise_error(RSpec::Expectations::ExpectationNotMetError) { |error|
|
|
242
|
-
expect(error.message).to start_with('(still failing after 0.3s)').and(include('expected: 2'))
|
|
243
|
-
}
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
it 'raises any other error on the first try' do
|
|
247
|
-
tries = 0
|
|
248
|
-
expect { page.eventually(timeout: 1) { raise "boom #{tries += 1}" } }.to raise_error(RuntimeError, 'boom 1')
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
it 'stretches a nested call without timeout: to what is left of its own deadline' do
|
|
252
|
-
allow(WaitHelper).to receive(:default_timeout).and_return(1)
|
|
253
|
-
driver.script({ css: '.x' }, *Array.new(15), element(text: 'y'))
|
|
254
|
-
tries = 0
|
|
255
|
-
started = clock
|
|
256
|
-
page.eventually(timeout: 3) do
|
|
257
|
-
tries += 1
|
|
258
|
-
expect(page.text(css: '.x')).to eq 'y'
|
|
259
|
-
end
|
|
260
|
-
expect([tries, clock - started]).to match([1, be_between(1.3, 2.9)])
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
it 'retries a nested timeout: per attempt and fails at its own deadline with the prefix' do
|
|
264
|
-
started = clock
|
|
265
|
-
expect { page.eventually(timeout: 1) { page.text({ css: '.never' }, timeout: 0.2) } }
|
|
266
|
-
.to raise_error(WaitHelper::TimeoutError, /\A\(still failing after 1s\)\nWaited [\d.]+s for \{css: "\.never"\}/)
|
|
267
|
-
expect(clock - started).to be_between(0.9, 1.4)
|
|
268
|
-
expect(driver.finds.size).to be > 5
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
it 'caps a nested action at its own deadline' do
|
|
272
|
-
started = clock
|
|
273
|
-
expect { page.eventually(timeout: 0.5) { page.click({ id: 'never' }, timeout: 5) } }
|
|
274
|
-
.to raise_error(WaitHelper::TimeoutError)
|
|
275
|
-
expect(clock - started).to be < 0.8
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
describe RaiderElement do
|
|
280
|
-
it 'chains finders, waiting at each level inside the parent' do
|
|
281
|
-
email = element
|
|
282
|
-
form = element.script({ id: 'email' }, nil, email)
|
|
283
|
-
driver.script({ css: '.form' }, nil, form)
|
|
284
|
-
page.find_element(css: '.form').find_element(id: 'email').send_keys('a')
|
|
285
|
-
expect(driver.finds).to eq([{ css: '.form' }] * 2)
|
|
286
|
-
expect(form.finds).to eq([{ id: 'email' }] * 2)
|
|
287
|
-
expect(email.log).to eq([[:send_keys, 'a']])
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
it 'returns an Array of wrappers from find_elements' do
|
|
291
|
-
driver.script_many({ css: 'li' }, [element, element])
|
|
292
|
-
expect(page.find_elements(css: 'li')).to all(be_a(described_class))
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
it 'delegates unwrapped methods to the raw element and compares by it' do
|
|
296
|
-
raw = element(tag_name: 'a')
|
|
297
|
-
driver.script({ id: 'x' }, raw)
|
|
298
|
-
wrapper = page.find_element(id: 'x')
|
|
299
|
-
other = page.find_element(id: 'x')
|
|
300
|
-
expect([wrapper.tag_name, wrapper.rect, wrapper.respond_to?(:tag_name)]).to eq(['a', [0, 0, 10, 10], true])
|
|
301
|
-
expect(wrapper.native).to be(raw)
|
|
302
|
-
expect([wrapper == raw, wrapper == other, [wrapper, other].uniq.size]).to eq([true, true, 1])
|
|
303
|
-
expect(wrapper).not_to be_a(Selenium::WebDriver::Element)
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
it 're-finds a stale wrapper through its scope' do
|
|
307
|
-
first = element
|
|
308
|
-
second = element
|
|
309
|
-
driver.script({ id: 'x' }, first, second)
|
|
310
|
-
wrapper = page.find_element(id: 'x')
|
|
311
|
-
first.go_stale
|
|
312
|
-
wrapper.click(timeout: 1)
|
|
313
|
-
expect([first.log, second.log, wrapper.native]).to eq([[], [[:click]], second])
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
it 'keeps its element after a timed-out action' do
|
|
317
|
-
raw = element(tag_name: 'a')
|
|
318
|
-
driver.script({ id: 'x' }, raw, nil)
|
|
319
|
-
wrapper = page.find_element(id: 'x')
|
|
320
|
-
hash = wrapper.hash
|
|
321
|
-
raw.go_hidden
|
|
322
|
-
expect { wrapper.click(timeout: 0.2) }.to raise_error(WaitHelper::TimeoutError)
|
|
323
|
-
expect([wrapper.native, wrapper.tag_name, wrapper == raw, wrapper.hash]).to eq([raw, 'a', true, hash])
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
it 'heals a chain when the parent was re-rendered' do
|
|
327
|
-
old_button = element
|
|
328
|
-
new_button = element
|
|
329
|
-
old_form = element.script({ id: 'go' }, old_button)
|
|
330
|
-
new_form = element.script({ id: 'go' }, new_button)
|
|
331
|
-
driver.script({ css: '.form' }, old_form, new_form)
|
|
332
|
-
button = page.find_element(css: '.form').find_element(id: 'go')
|
|
333
|
-
[old_form, old_button].each(&:go_stale)
|
|
334
|
-
button.click(timeout: 1)
|
|
335
|
-
expect(new_button.log).to eq([[:click]])
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
it 'raises a stale find_elements member with a re-query hint' do
|
|
339
|
-
item = element
|
|
340
|
-
driver.script_many({ css: 'li' }, [item])
|
|
341
|
-
member = page.find_elements(css: 'li').first
|
|
342
|
-
item.go_stale
|
|
343
|
-
expect { member.click(timeout: 1) }.to raise_error(errors::StaleElementReferenceError, /find_elements again/)
|
|
344
|
-
end
|
|
345
|
-
|
|
346
|
-
context 'when a parent that cannot be found again goes stale' do
|
|
347
|
-
let(:raw_row) { element.script({ css: 'td' }, element) }
|
|
348
|
-
let(:hint) { /find_elements again/ }
|
|
349
|
-
|
|
350
|
-
def stale_row
|
|
351
|
-
driver.script_many({ css: 'tr' }, [raw_row])
|
|
352
|
-
row = page.find_elements(css: 'tr').first
|
|
353
|
-
raw_row.go_stale
|
|
354
|
-
row
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
def at_once
|
|
358
|
-
started = clock
|
|
359
|
-
yield
|
|
360
|
-
expect(clock - started).to be < 0.3
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
it 'raises from find_elements at timeout: 0 instead of answering []' do
|
|
364
|
-
row = stale_row
|
|
365
|
-
expect { row.find_elements({ css: '.err' }, timeout: 0) }.to raise_error(errors::StaleElementReferenceError, hint)
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
it 'raises from find_elements at once instead of waiting and answering []' do
|
|
369
|
-
row = stale_row
|
|
370
|
-
at_once { expect { row.find_elements(css: 'td', timeout: 2) }.to raise_error(errors::StaleElementReferenceError, hint) }
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
it 'raises from find_element at once with the hint' do
|
|
374
|
-
row = stale_row
|
|
375
|
-
at_once { expect { row.find_element(css: 'td', timeout: 2) }.to raise_error(errors::StaleElementReferenceError, hint) }
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
it 'raises for a parent passed in as an element, suggesting a locator' do
|
|
379
|
-
parent = page.find_element(raw_row)
|
|
380
|
-
raw_row.go_stale
|
|
381
|
-
at_once do
|
|
382
|
-
expect { parent.find_element(css: 'td', timeout: 2) }
|
|
383
|
-
.to raise_error(errors::StaleElementReferenceError, /pass .*locator/)
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
it 'raises at once for a stale ancestor further up the chain' do
|
|
388
|
-
leaf = element
|
|
389
|
-
middle = element.script({ css: 'td' }, leaf)
|
|
390
|
-
raw_row.script({ css: '.cell' }, middle)
|
|
391
|
-
driver.script_many({ css: 'tr' }, [raw_row])
|
|
392
|
-
button = page.find_elements(css: 'tr').first.find_element(css: '.cell').find_element(css: 'td')
|
|
393
|
-
[raw_row, middle, leaf].each(&:go_stale)
|
|
394
|
-
at_once { expect { button.click(timeout: 2) }.to raise_error(errors::StaleElementReferenceError, hint) }
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
it 'still re-finds a stale child inside a live parent' do
|
|
398
|
-
old_button = element
|
|
399
|
-
new_button = element
|
|
400
|
-
raw_row.script({ css: 'button' }, old_button, new_button)
|
|
401
|
-
driver.script_many({ css: 'tr' }, [raw_row])
|
|
402
|
-
button = page.find_elements(css: 'tr').first.find_element(css: 'button')
|
|
403
|
-
old_button.go_stale
|
|
404
|
-
button.click(timeout: 1)
|
|
405
|
-
expect(new_button.log).to eq([[:click]])
|
|
406
|
-
end
|
|
407
|
-
end
|
|
408
|
-
|
|
409
|
-
it 'passes both raw elements to drag_and_drop' do
|
|
410
|
-
source = element
|
|
411
|
-
target = element
|
|
412
|
-
driver.script({ id: 'src' }, source).script({ id: 'tgt' }, target)
|
|
413
|
-
page.find_element(id: 'src').drag_and_drop(page.find_element(id: 'tgt'))
|
|
414
|
-
step = driver.actions.first
|
|
415
|
-
expect([step[1].equal?(source), step[2].equal?(target)]).to eq([true, true])
|
|
416
|
-
end
|
|
417
|
-
|
|
418
|
-
it 'adds exactly the planned public methods' do
|
|
419
|
-
expect(described_class.public_instance_methods(false).sort)
|
|
420
|
-
.to eq(%i[== attribute clear click displayed? drag_and_drop enabled? eql? find_element find_elements hash
|
|
421
|
-
hover native scroll_into_view select_by selected? send_keys submit text])
|
|
422
|
-
end
|
|
423
|
-
end
|
|
424
|
-
|
|
425
|
-
describe 'the choke point' do
|
|
426
|
-
let(:actions) { [] }
|
|
427
|
-
|
|
428
|
-
before do
|
|
429
|
-
dropdown = WaitHelperSpec::FakeSelect.new
|
|
430
|
-
dropdown.script({ css: 'y' }, element).script_many({ css: 'y' }, [element])
|
|
431
|
-
driver.script({ id: 'x' }, dropdown)
|
|
432
|
-
allow_any_instance_of(WaitHelper::Waiter).to receive(:perform_action).and_wrap_original do |original, *args, **options, &block|
|
|
433
|
-
actions << args.first
|
|
434
|
-
original.call(*args, **options, &block)
|
|
435
|
-
end
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
def calls(locator)
|
|
439
|
-
{ find_element: [locator], find_elements: [locator], click: [locator], clear: [locator], submit: [locator],
|
|
440
|
-
hover: [locator], scroll_into_view: [locator], text: [locator], displayed?: [locator],
|
|
441
|
-
enabled?: [locator], selected?: [locator], send_keys: [locator, 'a'], select_by: [locator, :text, 'Blue'],
|
|
442
|
-
attribute: [locator, 'href'], drag_and_drop: [locator, locator] }
|
|
443
|
-
end
|
|
444
|
-
|
|
445
|
-
it 'is called once per mixin method, with the method name' do
|
|
446
|
-
calls({ id: 'x' }).each do |name, args|
|
|
447
|
-
actions.clear
|
|
448
|
-
page.public_send(name, *args, timeout: 1)
|
|
449
|
-
expect(actions).to eq([name])
|
|
450
|
-
end
|
|
451
|
-
end
|
|
452
|
-
|
|
453
|
-
it 'is called once per wrapper method, with the method name' do
|
|
454
|
-
wrapper = page.find_element(id: 'x')
|
|
455
|
-
calls({ css: 'y' }).each do |name, args|
|
|
456
|
-
args = args.drop(1) unless %i[find_element find_elements drag_and_drop].include?(name)
|
|
457
|
-
args = [wrapper] if name == :drag_and_drop
|
|
458
|
-
actions.clear
|
|
459
|
-
wrapper.public_send(name, *args, timeout: 1)
|
|
460
|
-
expect(actions).to eq([name])
|
|
461
|
-
end
|
|
462
|
-
end
|
|
463
|
-
end
|
|
464
|
-
|
|
465
|
-
describe 'footprint' do
|
|
466
|
-
it 'adds exactly the public API, with no private methods and nothing named wait' do
|
|
467
|
-
expect(WaitHelper.public_instance_methods(false).sort)
|
|
468
|
-
.to eq(%i[attribute clear click displayed? drag_and_drop enabled? eventually find_element find_elements hover
|
|
469
|
-
scroll_into_view select_by selected? send_keys submit text])
|
|
470
|
-
expect(WaitHelper.private_instance_methods(false)).to be_empty
|
|
471
|
-
expect(page.respond_to?(:wait, true)).to be(false)
|
|
472
|
-
end
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
describe '.observer' do
|
|
476
|
-
# Records every hook call; tokens are the call's index.
|
|
477
|
-
let(:observer) do
|
|
478
|
-
Class.new do
|
|
479
|
-
attr_reader :calls
|
|
480
|
-
|
|
481
|
-
def initialize = @calls = []
|
|
482
|
-
|
|
483
|
-
%i[action_started group_started].each do |hook|
|
|
484
|
-
define_method(hook) do |**details|
|
|
485
|
-
@calls << [hook, details]
|
|
486
|
-
@calls.size - 1
|
|
487
|
-
end
|
|
488
|
-
end
|
|
489
|
-
|
|
490
|
-
%i[action_ready action_finished group_retried group_finished].each do |hook|
|
|
491
|
-
define_method(hook) do |token, **details|
|
|
492
|
-
@calls << [hook, token, details]
|
|
493
|
-
end
|
|
494
|
-
end
|
|
495
|
-
end.new
|
|
496
|
-
end
|
|
497
|
-
|
|
498
|
-
around do |example|
|
|
499
|
-
WaitHelper.observer = observer
|
|
500
|
-
example.run
|
|
501
|
-
ensure
|
|
502
|
-
WaitHelper.observer = nil
|
|
503
|
-
end
|
|
504
|
-
|
|
505
|
-
def started = observer.calls.select { |call| call.first == :action_started }.map(&:last)
|
|
506
|
-
def finished = observer.calls.select { |call| call.first == :action_finished }.map(&:last)
|
|
507
|
-
|
|
508
|
-
it 'reports a click with its locator, source line, waited time and the ready element' do
|
|
509
|
-
button = element
|
|
510
|
-
driver.script({ id: 'go' }, nil, button)
|
|
511
|
-
line = __LINE__ + 1
|
|
512
|
-
page.click({ id: 'go' }, timeout: 1)
|
|
513
|
-
|
|
514
|
-
expect(observer.calls.map(&:first)).to eq(%i[action_started action_ready action_finished])
|
|
515
|
-
expect(started.first).to include(action: :click, locator: { strategy: 'id', selector: 'go' }, within: [],
|
|
516
|
-
source: { file: Pathname(__FILE__).relative_path_from(Dir.pwd).to_s, line: line })
|
|
517
|
-
.and(satisfy { |details| !details.key?(:label) })
|
|
518
|
-
expect(observer.calls[2][1]).to eq(0)
|
|
519
|
-
expect(finished.first).to include(status: :passed, retries: 2, error: nil, element: button)
|
|
520
|
-
expect(finished.first[:waited_ms]).to be_between(50, 500)
|
|
521
|
-
end
|
|
522
|
-
|
|
523
|
-
it 'reports the keys of send_keys and how and what of select_by as args, and nil args for other actions' do
|
|
524
|
-
driver.script({ id: 'user' }, element)
|
|
525
|
-
driver.script({ id: 'colour' }, WaitHelperSpec::FakeSelect.new)
|
|
526
|
-
page.send_keys({ id: 'user' }, 'admin', :enter, timeout: 1)
|
|
527
|
-
page.select_by({ id: 'colour' }, :text, 'Blue', timeout: 1)
|
|
528
|
-
page.click({ id: 'user' }, timeout: 1)
|
|
529
|
-
|
|
530
|
-
expect(started.map { |event| event.values_at(:action, :args) })
|
|
531
|
-
.to eq([[:send_keys, ['admin', :enter]], [:select_by, [:text, 'Blue']], [:click, nil]])
|
|
532
|
-
end
|
|
533
|
-
|
|
534
|
-
it 'reports action_ready with the ready element before the action block runs' do
|
|
535
|
-
field = element
|
|
536
|
-
driver.script({ id: 'user' }, nil, field)
|
|
537
|
-
log_when_ready = []
|
|
538
|
-
allow(observer).to receive(:action_ready).and_wrap_original do |original, token, **details|
|
|
539
|
-
log_when_ready << details[:element].log.dup
|
|
540
|
-
original.call(token, **details)
|
|
541
|
-
end
|
|
542
|
-
page.send_keys({ id: 'user' }, 'admin', timeout: 1)
|
|
543
|
-
|
|
544
|
-
expect(observer.calls.map(&:first)).to eq(%i[action_started action_ready action_finished])
|
|
545
|
-
expect(observer.calls[1]).to eq([:action_ready, 0, { element: field }])
|
|
546
|
-
expect(log_when_ready).to eq([[]])
|
|
547
|
-
expect(field.log).to eq([[:send_keys, 'admin']])
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
it 'reports a chained find with within, outermost first' do
|
|
551
|
-
driver.script({ css: '.form' }, element.script({ tag_name: 'td' }, element.script({ id: 'email' }, element)))
|
|
552
|
-
page.find_element(css: '.form').find_element(tag_name: 'td').find_element(id: 'email')
|
|
553
|
-
|
|
554
|
-
expect(started.last).to include(action: :find_element, locator: { strategy: 'id', selector: 'email' },
|
|
555
|
-
within: [{ strategy: 'css', selector: '.form' }, { strategy: 'tag_name', selector: 'td' }])
|
|
556
|
-
end
|
|
557
|
-
|
|
558
|
-
it 'reports a timed-out action as failed with its error, then re-raises' do
|
|
559
|
-
driver.script({ id: 'gone' }, nil)
|
|
560
|
-
expect { page.click({ id: 'gone' }, timeout: 0.3) }.to raise_error(WaitHelper::TimeoutError)
|
|
561
|
-
|
|
562
|
-
expect(finished.first).to include(status: :failed, element: nil)
|
|
563
|
-
expect(finished.first[:error]).to be_a(WaitHelper::TimeoutError)
|
|
564
|
-
expect(finished.first[:retries]).to be >= 2
|
|
565
|
-
end
|
|
566
|
-
|
|
567
|
-
it 'reports eventually as a group with the retries it took' do
|
|
568
|
-
tries = 0
|
|
569
|
-
page.eventually(timeout: 2) { expect(tries += 1).to eq(3) }
|
|
570
|
-
|
|
571
|
-
group = observer.calls.find { |call| call.first == :group_started }.last
|
|
572
|
-
expect(group).to include(action: :eventually)
|
|
573
|
-
expect(group[:source]).to include(line: be_a(Integer))
|
|
574
|
-
expect(observer.calls.last).to match([:group_finished, 0, { status: :passed, retries: 2, error: nil }])
|
|
575
|
-
end
|
|
576
|
-
|
|
577
|
-
it 'reports group_retried with the group token and the earlier error before each new pass of eventually' do
|
|
578
|
-
tries = 0
|
|
579
|
-
page.eventually(timeout: 2) { expect(tries += 1).to eq(3) }
|
|
580
|
-
|
|
581
|
-
retried = observer.calls.select { |call| call.first == :group_retried }
|
|
582
|
-
expect(retried.map { |call| call[1] }).to eq([0, 0])
|
|
583
|
-
expect(retried.map { |call| call.last[:error] }).to all(be_a(RSpec::Expectations::ExpectationNotMetError))
|
|
584
|
-
expect(observer.calls.map(&:first)).to eq(%i[group_started group_retried group_retried group_finished])
|
|
585
|
-
end
|
|
586
|
-
|
|
587
|
-
it 'reports a failing eventually as failed with its error' do
|
|
588
|
-
tries = 0
|
|
589
|
-
expect { page.eventually(timeout: 0.2) { expect(tries += 1).to eq(0) } }.to raise_error(RSpec::Expectations::ExpectationNotMetError)
|
|
590
|
-
expect(observer.calls.last.last).to include(status: :failed, error: be_a(RSpec::Expectations::ExpectationNotMetError))
|
|
591
|
-
end
|
|
592
|
-
|
|
593
|
-
it 'normalises class to class_name and link to link_text' do
|
|
594
|
-
aliases = { css_selector: 'css', class: 'class_name', link: 'link_text', partial_link: 'partial_link_text', name: 'name' }
|
|
595
|
-
aliases.each_key do |strategy|
|
|
596
|
-
driver.script({ strategy => 'x' }, element)
|
|
597
|
-
page.displayed?({ strategy => 'x' }, timeout: 0)
|
|
598
|
-
end
|
|
599
|
-
|
|
600
|
-
expect(started.map { |event| event[:locator][:strategy] }).to eq(aliases.values)
|
|
601
|
-
end
|
|
602
|
-
|
|
603
|
-
it 'never takes the source from a file in helpers/, such as the trace helper' do
|
|
604
|
-
# The given path of wait_helper.rb, and the real path of raider_element.rb (via __dir__).
|
|
605
|
-
given, real = WaitHelper::Waiter::HELPER_FILES.map { |file| File.dirname(file) }
|
|
606
|
-
|
|
607
|
-
expect(WaitHelper::Observation.project?(File.join(given, 'trace_helper.rb'))).to be(false)
|
|
608
|
-
expect(WaitHelper::Observation.project?(File.join(real, 'driver_helper.rb'))).to be(false)
|
|
609
|
-
expect(WaitHelper::Observation.project?(File.expand_path('spec/login_spec.rb'))).to be(true)
|
|
610
|
-
end
|
|
611
|
-
|
|
612
|
-
it 'reports no label for an action called from a page_objects method' do
|
|
613
|
-
Dir.mktmpdir do |dir|
|
|
614
|
-
FileUtils.mkdir_p(File.join(dir, 'page_objects'))
|
|
615
|
-
path = File.join(dir, 'page_objects', 'login.rb')
|
|
616
|
-
File.write(path, "def login_button(page) = page.click({ id: 'go' }, timeout: 1)\n")
|
|
617
|
-
load path
|
|
618
|
-
driver.script({ id: 'go' }, element)
|
|
619
|
-
login_button(page)
|
|
620
|
-
end
|
|
621
|
-
|
|
622
|
-
expect(started.first).not_to have_key(:label)
|
|
623
|
-
expect(started.first[:source][:file]).to end_with('page_objects/login.rb')
|
|
624
|
-
end
|
|
625
|
-
|
|
626
|
-
it 'drops the observer, warns once, and still clicks when working out the source raises' do
|
|
627
|
-
button = element
|
|
628
|
-
driver.script({ id: 'go' }, button)
|
|
629
|
-
allow(Dir).to receive(:pwd).and_raise(Errno::ENOENT)
|
|
630
|
-
|
|
631
|
-
expect { 2.times { page.click({ id: 'go' }, timeout: 1) } }.to output(/\A\[trace\] observer stopped: Errno::ENOENT.*\n\z/).to_stderr
|
|
632
|
-
expect(button.log).to eq([%i[click]] * 2)
|
|
633
|
-
expect(WaitHelper.observer).to be_nil
|
|
634
|
-
end
|
|
635
|
-
|
|
636
|
-
it 'drops an observer that raises, warns once, and still clicks' do
|
|
637
|
-
allow(observer).to receive(:action_started).and_raise('boom')
|
|
638
|
-
button = element
|
|
639
|
-
driver.script({ id: 'go' }, button)
|
|
640
|
-
|
|
641
|
-
expect { 2.times { page.click({ id: 'go' }, timeout: 1) } }.to output(/\A\[trace\] observer stopped: RuntimeError: boom\n\z/).to_stderr
|
|
642
|
-
expect(button.log).to eq([%i[click]] * 2)
|
|
643
|
-
expect(WaitHelper.observer).to be_nil
|
|
644
|
-
end
|
|
645
|
-
|
|
646
|
-
it 'still prints observer stopped under -W0, where Kernel#warn is silent' do
|
|
647
|
-
allow(observer).to receive(:action_started).and_raise('boom')
|
|
648
|
-
driver.script({ id: 'go' }, element)
|
|
649
|
-
verbose = $VERBOSE
|
|
650
|
-
$VERBOSE = nil
|
|
651
|
-
|
|
652
|
-
expect { page.click({ id: 'go' }, timeout: 1) }.to output("[trace] observer stopped: RuntimeError: boom\n").to_stderr
|
|
653
|
-
ensure
|
|
654
|
-
$VERBOSE = verbose
|
|
655
|
-
end
|
|
656
|
-
end
|
|
657
|
-
|
|
658
|
-
describe '.default_timeout' do
|
|
659
|
-
around do |example|
|
|
660
|
-
Dir.mktmpdir do |dir|
|
|
661
|
-
Dir.chdir(dir) { example.run }
|
|
662
|
-
ensure
|
|
663
|
-
WaitHelper.instance_variable_set(:@default_timeout, nil)
|
|
664
|
-
end
|
|
665
|
-
end
|
|
666
|
-
|
|
667
|
-
it 'is 10 without config/config.yml' do
|
|
668
|
-
WaitHelper.instance_variable_set(:@default_timeout, nil)
|
|
669
|
-
expect(WaitHelper.default_timeout).to eq(10)
|
|
670
|
-
end
|
|
671
|
-
|
|
672
|
-
it 'reads timeout from config/config.yml' do
|
|
673
|
-
WaitHelper.instance_variable_set(:@default_timeout, nil)
|
|
674
|
-
Dir.mkdir('config')
|
|
675
|
-
File.write('config/config.yml', YAML.dump('timeout' => 3))
|
|
676
|
-
expect(WaitHelper.default_timeout).to eq(3)
|
|
677
|
-
end
|
|
678
|
-
end
|
|
679
|
-
end
|