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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec'
|
|
4
|
+
require_relative '../../../lib/ruby_raider/runtime/rspec'
|
|
5
|
+
|
|
6
|
+
RSpec.describe 'Raider::Matchers::Base' do
|
|
7
|
+
after { Raider.reset }
|
|
8
|
+
|
|
9
|
+
let(:matcher_class) do
|
|
10
|
+
Class.new(Raider::Matchers::Base) do
|
|
11
|
+
def verb = 'have value'
|
|
12
|
+
def read(subject) = subject.value
|
|
13
|
+
def pass?(value) = value == expected
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
let(:subject_class) do
|
|
18
|
+
Class.new do
|
|
19
|
+
attr_reader :reads
|
|
20
|
+
|
|
21
|
+
def initialize(*values, error: nil)
|
|
22
|
+
@values = values
|
|
23
|
+
@error = error
|
|
24
|
+
@reads = 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def value
|
|
28
|
+
@reads += 1
|
|
29
|
+
raise @error, 'gone' if @error
|
|
30
|
+
|
|
31
|
+
@values[[@reads, @values.size].min - 1]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def tag_name = raise(Selenium::WebDriver::Error::WebDriverError, 'no tag')
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'passes once the value changes to the expected one' do
|
|
39
|
+
element = subject_class.new('a', 'a', 'b')
|
|
40
|
+
expect(matcher_class.new('b', wait: 1).matches?(element)).to be(true)
|
|
41
|
+
expect(element.reads).to be >= 3
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'retries the negated form until the value changes away' do
|
|
45
|
+
element = subject_class.new('b', 'b', 'a')
|
|
46
|
+
expect(matcher_class.new('b', wait: 1).does_not_match?(element)).to be(true)
|
|
47
|
+
expect(element.reads).to be >= 3
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'reports the last value seen after the wait' do
|
|
51
|
+
matcher = matcher_class.new('b', wait: 0.2)
|
|
52
|
+
expect(matcher.matches?(subject_class.new('a'))).to be(false)
|
|
53
|
+
expect(matcher.failure_message).to match(/\A#{Regexp.escape('expected <element> to have value "b", last seen "a" after ')}0\.\d+s\z/)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
it 'words the negated failure' do
|
|
57
|
+
matcher = matcher_class.new('a', wait: 0.2)
|
|
58
|
+
expect(matcher.does_not_match?(subject_class.new('a'))).to be(false)
|
|
59
|
+
expect(matcher.failure_message_when_negated)
|
|
60
|
+
.to match(/\A#{Regexp.escape('expected <element> not to have value "a", last seen "a" after ')}0\.\d+s\z/)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'uses the configured timeout when wait is nil' do
|
|
64
|
+
Raider.timeout = 0.2
|
|
65
|
+
matcher = matcher_class.new('b')
|
|
66
|
+
matcher.matches?(subject_class.new('a'))
|
|
67
|
+
expect(matcher.failure_message).to match(/after 0\.[23]\d*s\z/)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'calls a Proc actual on every poll and describes it as the block' do
|
|
71
|
+
calls = 0
|
|
72
|
+
matcher = matcher_class.new('b', wait: 0.3)
|
|
73
|
+
result = matcher.matches?(proc { subject_class.new("v#{calls += 1}") })
|
|
74
|
+
expect(result).to be(false)
|
|
75
|
+
expect(calls).to be >= 2
|
|
76
|
+
expect(matcher.failure_message).to start_with('expected the block to have value "b", last seen "v')
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'propagates an error raised by a Proc actual' do
|
|
80
|
+
expect { matcher_class.new('b', wait: 1).matches?(proc { raise 'boom' }) }.to raise_error(RuntimeError, 'boom')
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'reports not found when the element never appears' do
|
|
84
|
+
element = subject_class.new(error: Selenium::WebDriver::Error::NoSuchElementError)
|
|
85
|
+
matcher = matcher_class.new('b', wait: 0.2)
|
|
86
|
+
expect(matcher.matches?(element)).to be(false)
|
|
87
|
+
expect(matcher.failure_message).to include('last seen: not found')
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'counts absence as a pass when the matcher allows it' do
|
|
91
|
+
absent = Class.new(matcher_class) { def absent_passes? = true }
|
|
92
|
+
element = subject_class.new(error: Selenium::WebDriver::Error::StaleElementReferenceError)
|
|
93
|
+
expect(absent.new('x', wait: 0.2).matches?(element)).to be(true)
|
|
94
|
+
expect(absent.new('x', wait: 0.2).does_not_match?(element)).to be(false)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
context 'when the read raises a selenium TimeoutError' do
|
|
98
|
+
before { stub_const('SlowRead', Class.new(Selenium::WebDriver::Error::TimeoutError)) }
|
|
99
|
+
|
|
100
|
+
let(:timeout_error) { SlowRead }
|
|
101
|
+
let(:reader_class) do
|
|
102
|
+
Class.new do
|
|
103
|
+
def initialize(error, fails)
|
|
104
|
+
@error = error
|
|
105
|
+
@fails = fails
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def value
|
|
109
|
+
raise @error, 'slow' if (@fails -= 1) >= 0
|
|
110
|
+
|
|
111
|
+
'ok'
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'retries it as not yet' do
|
|
117
|
+
reader = reader_class.new(timeout_error, 2)
|
|
118
|
+
expect(matcher_class.new('ok', wait: 1).matches?(reader)).to be(true)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'counts it as absence when the matcher allows' do
|
|
122
|
+
absent = Class.new(matcher_class) { def absent_passes? = true }
|
|
123
|
+
expect(absent.new('x', wait: 1).matches?(reader_class.new(timeout_error, Float::INFINITY))).to be(true)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'still ends at the matcher deadline' do
|
|
127
|
+
expect(matcher_class.new('ok', wait: 0.2).matches?(reader_class.new(timeout_error, Float::INFINITY))).to be(false)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'reports the elapsed time rather than the configured wait' do
|
|
132
|
+
Raider.timeout = 30
|
|
133
|
+
matcher = matcher_class.new('b', wait: 0.3)
|
|
134
|
+
matcher.matches?(subject_class.new('a'))
|
|
135
|
+
seconds = matcher.failure_message[/after ([\d.]+)s/, 1].to_f
|
|
136
|
+
expect(seconds).to be_between(0.3, 0.5)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'fails with a message for an actual without tag_name' do
|
|
140
|
+
actual = Struct.new(:value).new('a')
|
|
141
|
+
matcher = matcher_class.new('b', wait: 0.1)
|
|
142
|
+
expect(matcher.matches?(actual)).to be(false)
|
|
143
|
+
expect(matcher.failure_message).to start_with('expected #<struct')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'supports block expectations and describes itself' do
|
|
147
|
+
matcher = matcher_class.new('b')
|
|
148
|
+
expect(matcher.supports_block_expectations?).to be(true)
|
|
149
|
+
expect(matcher.description).to eq('have value "b"')
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe '#read_with_zero_wait' do
|
|
153
|
+
let(:reader) { matcher_class.new('x') }
|
|
154
|
+
|
|
155
|
+
it 'passes timeout: 0 when the method accepts it' do
|
|
156
|
+
target = Class.new { def text(timeout: 5) = timeout }.new
|
|
157
|
+
expect(reader.read_with_zero_wait(target, :text)).to eq(0)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'omits it otherwise' do
|
|
161
|
+
target = Class.new { def text = 'plain' }.new
|
|
162
|
+
expect(reader.read_with_zero_wait(target, :text)).to eq('plain')
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec'
|
|
4
|
+
require_relative '../support/fake_bridge'
|
|
5
|
+
require_relative '../../../lib/ruby_raider/runtime/rspec'
|
|
6
|
+
|
|
7
|
+
RSpec.describe 'Raider element matchers' do
|
|
8
|
+
include Raider::Matchers
|
|
9
|
+
|
|
10
|
+
let(:bridge) { Raider::Spec::FakeBridge.new }
|
|
11
|
+
let(:element) { Selenium::WebDriver::Element.new(bridge, 'e1') }
|
|
12
|
+
let(:gone) { Selenium::WebDriver::Error::NoSuchElementError }
|
|
13
|
+
|
|
14
|
+
after { Raider.reset }
|
|
15
|
+
|
|
16
|
+
describe 'have_text' do
|
|
17
|
+
it 'passes once the text arrives' do
|
|
18
|
+
bridge.answer(:element_text, 'Welcome', 'Welcome back Ann')
|
|
19
|
+
expect(have_text('Welcome back Ann', wait: 1).matches?(element)).to be(true)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'matches a whitespace-normalised substring' do
|
|
23
|
+
bridge.answer(:element_text, "Hi\n Welcome back Ann ")
|
|
24
|
+
expect(have_text('Welcome back', wait: 1).matches?(element)).to be(true)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'fails exact on extra text with the last seen value' do
|
|
28
|
+
bridge.answer(:element_text, 'Welcome back Ann!')
|
|
29
|
+
matcher = have_text('Welcome back Ann', exact: true, wait: 0.2)
|
|
30
|
+
expect(matcher.matches?(element)).to be(false)
|
|
31
|
+
expect(matcher.failure_message)
|
|
32
|
+
.to match(/\A#{Regexp.escape('expected <element> to have exact text "Welcome back Ann", last seen "Welcome back Ann!" after ')}0\.\d+s\z/)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'matches a regexp against the normalised text' do
|
|
36
|
+
bridge.answer(:element_text, "Hi Ann\n Lee")
|
|
37
|
+
expect(have_text(/Ann Lee/, wait: 1).matches?(element)).to be(true)
|
|
38
|
+
expect(have_text(/Bob/, wait: 0.2).matches?(element)).to be(false)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'words an exact regexp failure' do
|
|
42
|
+
bridge.answer(:element_text, 'Bob')
|
|
43
|
+
matcher = have_text(/Ann/, exact: true, wait: 0.2)
|
|
44
|
+
expect(matcher.matches?(element)).to be(false)
|
|
45
|
+
expect(matcher.failure_message).to start_with('expected <element> to have exact text /Ann/, last seen "Bob"')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'waits for the text to change when negated' do
|
|
49
|
+
bridge.answer(:element_text, 'Loading', 'Loading', 'Done')
|
|
50
|
+
expect(have_text('Loading', wait: 1).does_not_match?(element)).to be(true)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'words the negated failure' do
|
|
54
|
+
bridge.answer(:element_text, 'Loading')
|
|
55
|
+
matcher = have_text('Loading', wait: 0.2)
|
|
56
|
+
expect(matcher.does_not_match?(element)).to be(false)
|
|
57
|
+
expect(matcher.failure_message_when_negated)
|
|
58
|
+
.to match(/\A#{Regexp.escape('expected <element> not to have text "Loading", last seen "Loading" after ')}0\.\d+s\z/)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'reads a template element with timeout: 0 so the wait is not doubled' do
|
|
62
|
+
seen = []
|
|
63
|
+
reader = Object.new
|
|
64
|
+
reader.define_singleton_method(:text) { |timeout: 5| seen << timeout && 'hello' }
|
|
65
|
+
expect(have_text('hello', wait: 1).matches?(reader)).to be(true)
|
|
66
|
+
expect(seen).to eq([0])
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'does not pass a timeout to a reader that does not take one' do
|
|
70
|
+
expect(have_text('hello', wait: 1).matches?(Struct.new(:text).new('hello'))).to be(true)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe 'have_value' do
|
|
75
|
+
it 'reads the value attribute' do
|
|
76
|
+
bridge.answer(:element_attribute, nil, 'admin')
|
|
77
|
+
expect(have_value('admin', wait: 1).matches?(element)).to be(true)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe 'have_value with a regexp' do
|
|
82
|
+
it 'matches the value' do
|
|
83
|
+
bridge.answer(:element_attribute, 'xyz')
|
|
84
|
+
expect(have_value(/y/, wait: 1).matches?(element)).to be(true)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
describe 'have_attribute' do
|
|
89
|
+
it 'passes when the attribute is present' do
|
|
90
|
+
bridge.answer(:element_attribute, nil, '/home')
|
|
91
|
+
expect(have_attribute('href', wait: 1).matches?(element)).to be(true)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'fails when the attribute stays absent' do
|
|
95
|
+
bridge.answer(:element_attribute, nil)
|
|
96
|
+
expect(have_attribute('href', wait: 0.2).matches?(element)).to be(false)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'matches a regexp' do
|
|
100
|
+
bridge.answer(:element_attribute, 'btn', 'btn active')
|
|
101
|
+
expect(have_attribute('class', /active/, wait: 1).matches?(element)).to be(true)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it 'compares a string by equality' do
|
|
105
|
+
bridge.answer(:element_attribute, 'btn active')
|
|
106
|
+
expect(have_attribute('class', 'btn', wait: 0.2).matches?(element)).to be(false)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
describe 'state matchers' do
|
|
111
|
+
it 'be_visible passes once displayed turns true' do
|
|
112
|
+
bridge.answer(:element_displayed?, false, true)
|
|
113
|
+
expect(be_visible(wait: 1).matches?(element)).to be(true)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'be_hidden passes in one poll on a block whose find is always empty' do
|
|
117
|
+
driver = Raider::Spec.driver(bridge)
|
|
118
|
+
calls = 0
|
|
119
|
+
Raider.timeout = 5
|
|
120
|
+
expect { (calls += 1) && driver.find_element(id: 'gone') }.to be_hidden
|
|
121
|
+
expect(calls).to eq(1)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
it 'have_text fails on a block whose find is always empty after about three polls' do
|
|
125
|
+
driver = Raider::Spec.driver(bridge)
|
|
126
|
+
calls = 0
|
|
127
|
+
matcher = have_text('x', wait: 0.3)
|
|
128
|
+
expect(matcher.matches?(-> { (calls += 1) && driver.find_element(id: 'gone') })).to be(false)
|
|
129
|
+
expect(calls).to be_between(3, 4)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'be_hidden passes when the element is absent' do
|
|
133
|
+
bridge.answer(:element_displayed?, gone)
|
|
134
|
+
expect(be_hidden(wait: 0.2).matches?(element)).to be(true)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'be_hidden passes when not displayed' do
|
|
138
|
+
bridge.answer(:element_displayed?, false)
|
|
139
|
+
expect(be_hidden(wait: 0.2).matches?(element)).to be(true)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
it 'be_hidden fails on a visible element with the last seen state' do
|
|
143
|
+
bridge.answer(:element_displayed?, true)
|
|
144
|
+
matcher = be_hidden(wait: 0.2)
|
|
145
|
+
expect(matcher.matches?(element)).to be(false)
|
|
146
|
+
expect(matcher.failure_message).to match(/\A#{Regexp.escape('expected <element> to be hidden, last seen visible after ')}0\.\d+s\z/)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it 'be_visible failure reads last seen hidden' do
|
|
150
|
+
bridge.answer(:element_displayed?, false)
|
|
151
|
+
matcher = be_visible(wait: 0.2)
|
|
152
|
+
expect(matcher.matches?(element)).to be(false)
|
|
153
|
+
expect(matcher.failure_message).to match(/\A#{Regexp.escape('expected <element> to be visible, last seen hidden after ')}0\.\d+s\z/)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'be_enabled and be_disabled read enabled?' do
|
|
157
|
+
bridge.answer(:element_enabled?, false, true)
|
|
158
|
+
expect(be_enabled(wait: 1).matches?(element)).to be(true)
|
|
159
|
+
bridge.answer(:element_enabled?, false)
|
|
160
|
+
expect(be_disabled(wait: 1).matches?(element)).to be(true)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'be_checked reads selected?' do
|
|
164
|
+
bridge.answer(:element_selected?, false, true)
|
|
165
|
+
expect(be_checked(wait: 1).matches?(element)).to be(true)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'be_checked failure reads last seen unchecked' do
|
|
169
|
+
bridge.answer(:element_selected?, false)
|
|
170
|
+
matcher = be_checked(wait: 0.2)
|
|
171
|
+
expect(matcher.matches?(element)).to be(false)
|
|
172
|
+
expect(matcher.failure_message).to match(/\A#{Regexp.escape('expected <element> to be checked, last seen unchecked after ')}0\.\d+s\z/)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
describe 'absence and staleness' do
|
|
177
|
+
it 'not_to be_visible passes on an absent element' do
|
|
178
|
+
bridge.answer(:element_displayed?, gone)
|
|
179
|
+
expect(be_visible(wait: 1).does_not_match?(element)).to be(true)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'not_to be_visible passes on a stale raw element' do
|
|
183
|
+
bridge.answer(:element_displayed?, Selenium::WebDriver::Error::StaleElementReferenceError)
|
|
184
|
+
expect(be_visible(wait: 1).does_not_match?(element)).to be(true)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it 'be_visible keeps retrying an absent element and fails' do
|
|
188
|
+
bridge.answer(:element_displayed?, gone)
|
|
189
|
+
matcher = be_visible(wait: 0.2)
|
|
190
|
+
expect(matcher.matches?(element)).to be(false)
|
|
191
|
+
expect(matcher.failure_message).to include('last seen: not found')
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it 'fails at once with the hint for a stale raw element' do
|
|
195
|
+
bridge.answer(:element_text, Selenium::WebDriver::Error::StaleElementReferenceError)
|
|
196
|
+
matcher = have_text('x', wait: 5)
|
|
197
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
198
|
+
expect(matcher.matches?(element)).to be(false)
|
|
199
|
+
expect(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started).to be < 1
|
|
200
|
+
expect(matcher.failure_message).to eq(
|
|
201
|
+
'expected <element> to have text "x", but the element is stale; ' \
|
|
202
|
+
'use the block form (expect { page.element }) so it is found again'
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
it 'be_hidden passes on a stale raw element' do
|
|
207
|
+
bridge.answer(:element_displayed?, Selenium::WebDriver::Error::StaleElementReferenceError)
|
|
208
|
+
expect(be_hidden(wait: 1).matches?(element)).to be(true)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it 'retries a stale element read through a block' do
|
|
212
|
+
bridge.answer(:element_text, Selenium::WebDriver::Error::StaleElementReferenceError, 'x')
|
|
213
|
+
expect(have_text('x', wait: 1).matches?(proc { element })).to be(true)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
it 'composes with and' do
|
|
217
|
+
bridge.answer(:element_text, 'abc')
|
|
218
|
+
bridge.answer(:element_displayed?, true)
|
|
219
|
+
expect(element).to have_text('a', wait: 1).and be_visible(wait: 1)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
describe 'compound matchers' do
|
|
224
|
+
before do
|
|
225
|
+
bridge.answer(:element_text, 'abc')
|
|
226
|
+
bridge.answer(:element_displayed?, true)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'passes the block form of and when both hold' do
|
|
230
|
+
expect { element }.to have_text('a', wait: 1).and be_visible(wait: 1)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it 'fails the block form of and with a combined message' do
|
|
234
|
+
matcher = have_text('a', wait: 0.1).and be_hidden(wait: 0.1)
|
|
235
|
+
expect(matcher.matches?(proc { element })).to be(false)
|
|
236
|
+
expect(matcher.failure_message).to include('to be hidden')
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
it 'passes the block form of or when one side holds' do
|
|
240
|
+
expect { element }.to have_text('zzz', wait: 0.1).or be_visible(wait: 0.1)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
it 'fails the block form of or when neither holds' do
|
|
244
|
+
matcher = have_text('zzz', wait: 0.1).or be_hidden(wait: 0.1)
|
|
245
|
+
expect(matcher.matches?(proc { element })).to be(false)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'passes a three-way block chain when all hold' do
|
|
249
|
+
bridge.answer(:element_enabled?, true)
|
|
250
|
+
expect { element }.to have_text('a', wait: 1).and(be_visible(wait: 1)).and be_enabled(wait: 1)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
it 'fails a three-way block chain when the last does not hold' do
|
|
254
|
+
bridge.answer(:element_enabled?, false)
|
|
255
|
+
matcher = have_text('a', wait: 0.1).and(be_visible(wait: 0.1)).and be_enabled(wait: 0.1)
|
|
256
|
+
expect(matcher.matches?(proc { element })).to be(false)
|
|
257
|
+
expect(matcher.failure_message).to include('to be enabled')
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'mixes or into a block chain' do
|
|
261
|
+
expect { element }.to have_text('zzz', wait: 0.1).or(be_hidden(wait: 0.1)).or be_visible(wait: 0.1)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'keeps the value form working' do
|
|
265
|
+
expect(element).to have_text('a', wait: 1) & be_visible(wait: 1)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'keeps the value form of or working' do
|
|
269
|
+
expect(element).to have_text('zzz', wait: 0.1) | be_visible(wait: 0.1)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
it 'works through expect syntax with a block' do
|
|
274
|
+
bridge.answer(:element_text, 'x')
|
|
275
|
+
expect { element }.to have_text('x', wait: 1)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec'
|
|
4
|
+
require_relative '../../../lib/ruby_raider/runtime/rspec'
|
|
5
|
+
|
|
6
|
+
RSpec.describe 'Raider page matchers' do
|
|
7
|
+
include Raider::Matchers
|
|
8
|
+
|
|
9
|
+
# A driver whose title and url change from poll to poll; the last answer repeats.
|
|
10
|
+
let(:driver_class) do
|
|
11
|
+
Class.new do
|
|
12
|
+
def initialize(titles, urls)
|
|
13
|
+
@titles = titles
|
|
14
|
+
@urls = urls
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def title
|
|
18
|
+
@titles.size > 1 ? @titles.shift : @titles.first
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def current_url
|
|
22
|
+
@urls.size > 1 ? @urls.shift : @urls.first
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
after { Raider.reset }
|
|
28
|
+
|
|
29
|
+
describe 'have_count' do
|
|
30
|
+
it 'passes at once on an Array of the right size' do
|
|
31
|
+
expect(have_count(2).matches?([1, 2])).to be(true)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'fails an Array without waiting' do
|
|
35
|
+
matcher = have_count(2, wait: 10)
|
|
36
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
37
|
+
expect(matcher.matches?([1])).to be(false)
|
|
38
|
+
expect(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started).to be < 0.05
|
|
39
|
+
expect(matcher.failure_message).to eq('expected the array to have count 2, last seen 1 after 0s')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'passes on the first poll for a block whose find_elements is always empty' do
|
|
43
|
+
driver = Raider::Spec.driver(Raider::Spec::FakeBridge.new)
|
|
44
|
+
calls = 0
|
|
45
|
+
Raider.timeout = 5
|
|
46
|
+
expect { (calls += 1) && driver.find_elements(css: '.row') }.to have_count(0)
|
|
47
|
+
expect(calls).to eq(1)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'keeps calling a block until the count arrives' do
|
|
51
|
+
calls = 0
|
|
52
|
+
expect { (calls += 1) > 2 ? [1, 2, 3] : [] }.to have_count(3)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'words a block failure with the last count' do
|
|
56
|
+
matcher = have_count(3, wait: 0.2)
|
|
57
|
+
expect(matcher.matches?(-> { [1, 2] })).to be(false)
|
|
58
|
+
expect(matcher.failure_message).to match(/\A#{Regexp.escape('expected the block to have count 3, last seen 2 after ')}0\.\d+s\z/)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'waits for the count to leave when negated' do
|
|
62
|
+
sizes = [[1, 2, 3], [1, 2, 3], [1]]
|
|
63
|
+
expect(have_count(3, wait: 1).does_not_match?(-> { sizes.size > 1 ? sizes.shift : sizes.first })).to be(true)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
describe 'have_title' do
|
|
68
|
+
it 'passes once the title changes' do
|
|
69
|
+
driver = driver_class.new(%w[Loading Account], [''])
|
|
70
|
+
expect(have_title('Account', wait: 1).matches?(driver)).to be(true)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'accepts a Regexp' do
|
|
74
|
+
expect(driver_class.new(['My Account'], [''])).to have_title(/Account\z/)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'fails with the last title' do
|
|
78
|
+
matcher = have_title('Account', wait: 0.2)
|
|
79
|
+
expect(matcher.matches?(driver_class.new(['Home'], ['']))).to be(false)
|
|
80
|
+
expect(matcher.failure_message).to match(/\A#{Regexp.escape('expected the page to have title "Account", last seen "Home" after ')}0\.\d+s\z/)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe 'have_url' do
|
|
85
|
+
let(:driver) { driver_class.new([''], ['https://x/account']) }
|
|
86
|
+
|
|
87
|
+
it 'matches a Regexp' do
|
|
88
|
+
expect(driver).to have_url(%r{/account$})
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'matches a whole String' do
|
|
92
|
+
expect(driver).to have_url('https://x/account')
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'does not match a fragment of the url' do
|
|
96
|
+
matcher = have_url('account', wait: 0.2)
|
|
97
|
+
expect(matcher.matches?(driver)).to be(false)
|
|
98
|
+
expect(matcher.failure_message)
|
|
99
|
+
.to match(/\A#{Regexp.escape('expected the page to have url "account", last seen "https://x/account" after ')}0\.\d+s\z/)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec'
|
|
4
|
+
require_relative '../../../lib/ruby_raider/runtime'
|
|
5
|
+
require_relative '../../../lib/ruby_raider/runtime/rspec'
|
|
6
|
+
require_relative '../support/fake_bridge'
|
|
7
|
+
|
|
8
|
+
RSpec.describe 'Raider presence matchers' do
|
|
9
|
+
include Raider::Matchers
|
|
10
|
+
|
|
11
|
+
let(:bridge) { Raider::Spec::FakeBridge.new }
|
|
12
|
+
let(:driver) { Raider::Spec.driver(bridge) }
|
|
13
|
+
|
|
14
|
+
before { Raider.timeout = 0.3 }
|
|
15
|
+
|
|
16
|
+
after { Raider.reset }
|
|
17
|
+
|
|
18
|
+
it 'passes on the second poll once the element appears' do
|
|
19
|
+
bridge.enqueue([])
|
|
20
|
+
bridge.enqueue(['e'])
|
|
21
|
+
expect(driver).to have_element(id: 'x')
|
|
22
|
+
expect(bridge.calls.count { |call| call.first == :find_elements_by }).to eq(2)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'passes a negated expectation on the first poll when nothing matches' do
|
|
26
|
+
expect(driver).not_to have_element(id: 'x')
|
|
27
|
+
expect(bridge.calls.count { |call| call.first == :find_elements_by }).to eq(1)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'names the locator and the last count on failure' do
|
|
31
|
+
matcher = have_element({ id: 'x' }, wait: 0.1)
|
|
32
|
+
expect(matcher.matches?(driver)).to be(false)
|
|
33
|
+
expect(matcher.failure_message).to match(/to have element .*id: "x".*last seen 0/)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'builds a css locator from have_css' do
|
|
37
|
+
expect(have_css('.row').expected).to eq({ css: '.row' })
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'builds an xpath locator from have_xpath' do
|
|
41
|
+
expect(have_xpath('//p', wait: 1).expected).to eq({ xpath: '//p' })
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'refuses a hash together with keywords' do
|
|
45
|
+
expect { have_element({ id: 'x' }, css: '.y') }.to raise_error(ArgumentError)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'refuses no locator at all' do
|
|
49
|
+
expect { have_element }.to raise_error(ArgumentError)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'says the block when the subject is a block' do
|
|
53
|
+
Raider.timeout = 0.1
|
|
54
|
+
bridge.enqueue(['e'])
|
|
55
|
+
bridge.enqueue(['e'])
|
|
56
|
+
expect { expect { driver }.not_to have_element(id: 'x') }
|
|
57
|
+
.to raise_error(RSpec::Expectations::ExpectationNotMetError, /expected the block not to have element/)
|
|
58
|
+
end
|
|
59
|
+
end
|