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,207 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec'
|
|
4
|
+
require_relative '../../lib/ruby_raider/runtime'
|
|
5
|
+
require_relative 'support/fake_bridge'
|
|
6
|
+
|
|
7
|
+
RSpec.describe 'Raider::Actionability' do
|
|
8
|
+
let(:bridge) { Raider::Spec::FakeBridge.new }
|
|
9
|
+
let(:element) { Selenium::WebDriver::Element.new(bridge, 'e1') }
|
|
10
|
+
let(:overlay) { { 'check' => 'events', 'covering' => { 'element' => '<div class="overlay">', 'x' => 412, 'y' => 300 } } }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
Raider.reset
|
|
14
|
+
Raider::Actionability.reset_notice
|
|
15
|
+
allow($stderr).to receive(:puts)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
after { Raider.reset }
|
|
19
|
+
|
|
20
|
+
it 'makes no script call when actionability is off' do
|
|
21
|
+
Raider.actionability = false
|
|
22
|
+
Raider::Actionability.ensure(element, :click, force: false)
|
|
23
|
+
expect(bridge.script_calls).to be_empty
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'makes no script call but one tag_name command when forced' do
|
|
27
|
+
bridge.answer(:element_tag_name, 'div')
|
|
28
|
+
Raider::Actionability.ensure(element, :click, force: true)
|
|
29
|
+
expect([bridge.script_calls, bridge.calls]).to eq([[], [[:element_tag_name, 'e1']]])
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'polls until the check returns nil' do
|
|
33
|
+
bridge.script_results.push({ 'check' => 'visible' }, { 'check' => 'visible' }, nil)
|
|
34
|
+
Raider::Actionability.ensure(element, :click, force: false)
|
|
35
|
+
expect(bridge.script_calls.size).to eq(3)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'pauses briefly and re-checks at once after a first stable poll' do
|
|
39
|
+
first = { 'check' => 'stable', 'first' => true }
|
|
40
|
+
bridge.script_results.push(first, nil)
|
|
41
|
+
allow(Raider::Actionability).to receive(:sleep)
|
|
42
|
+
Raider::Actionability.ensure(element, :click, force: false)
|
|
43
|
+
expect(Raider::Actionability).to have_received(:sleep).with(0.02).once
|
|
44
|
+
expect(bridge.script_calls.size).to eq(2)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'passes the script and the action checks to the browser' do
|
|
48
|
+
bridge.script_results.push(nil)
|
|
49
|
+
Raider::Actionability.ensure(element, :submit, force: false)
|
|
50
|
+
expect(bridge.script_calls.first).to eq([Raider::Actionability::SCRIPT, element, %w[visible enabled inert]])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'raises NotActionable naming the covering element' do
|
|
54
|
+
Raider.timeout = 0.3
|
|
55
|
+
bridge.script_results.concat([overlay] * 20)
|
|
56
|
+
expect { Raider::Actionability.ensure(element, :click, force: false) }
|
|
57
|
+
.to raise_error(Raider::NotActionable, /receive events; `<div class="overlay">` covers it at \(412, 300\)/)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'returns zero when actionability is off or forced' do
|
|
61
|
+
Raider.actionability = false
|
|
62
|
+
off = Raider::Actionability.ensure(element, :click, force: false)
|
|
63
|
+
Raider.actionability = true
|
|
64
|
+
bridge.answer(:element_tag_name, 'div')
|
|
65
|
+
expect([off, Raider::Actionability.ensure(element, :click, force: true)]).to eq([0, 0])
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'returns the number of not-ready polls on the web' do
|
|
69
|
+
bridge.script_results.push({ 'check' => 'visible' }, { 'check' => 'enabled' }, nil)
|
|
70
|
+
expect(Raider::Actionability.ensure(element, :click, force: false)).to eq(2)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'does not count the first stable poll as not ready' do
|
|
74
|
+
bridge.script_results.push({ 'check' => 'stable', 'first' => true }, nil)
|
|
75
|
+
allow(Raider::Actionability).to receive(:sleep)
|
|
76
|
+
expect(Raider::Actionability.ensure(element, :click, force: false)).to eq(0)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
describe 'on an Appium native context' do
|
|
80
|
+
let(:context) { 'NATIVE_APP' }
|
|
81
|
+
let(:appium) do
|
|
82
|
+
name = context
|
|
83
|
+
Class.new(Raider::Spec::FakeBridge) { define_method(:current_context) { name } }.new
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def native = @native ||= Selenium::WebDriver::Element.new(appium, 'e1')
|
|
87
|
+
|
|
88
|
+
def ensure_native(action, **opts)
|
|
89
|
+
Raider::Actionability.ensure(native, action, force: false, **opts)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'polls until displayed and enabled, then returns the not-ready count without a script call' do
|
|
93
|
+
appium.answer(:element_displayed?, false, false, true).answer(:element_enabled?, false, true)
|
|
94
|
+
expect(ensure_native(:click)).to eq(3)
|
|
95
|
+
expect(appium.script_calls).to be_empty
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'returns zero when ready at once' do
|
|
99
|
+
appium.answer(:element_displayed?, true).answer(:element_enabled?, true)
|
|
100
|
+
expect(ensure_native(:click)).to eq(0)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
%i[click send_keys clear].each do |action|
|
|
104
|
+
it "raises NotActionable to be visible for a hidden element on #{action}" do
|
|
105
|
+
Raider.timeout = 0.3
|
|
106
|
+
appium.answer(:element_displayed?, false).answer(:element_enabled?, true)
|
|
107
|
+
expect { ensure_native(action) }.to raise_error(Raider::NotActionable, /waited 0\.[34]\d?s for .* to be visible/)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "raises NotActionable to be enabled for a disabled element on #{action}" do
|
|
111
|
+
Raider.timeout = 0.3
|
|
112
|
+
appium.answer(:element_displayed?, true).answer(:element_enabled?, false)
|
|
113
|
+
expect { ensure_native(action) }.to raise_error(Raider::NotActionable, /waited 0\.[34]\d?s for .* to be enabled/)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'makes no check call for submit' do
|
|
118
|
+
allow(native).to receive(:displayed?)
|
|
119
|
+
allow(native).to receive(:enabled?)
|
|
120
|
+
expect(ensure_native(:submit)).to eq(0)
|
|
121
|
+
expect(native).not_to have_received(:displayed?)
|
|
122
|
+
expect(native).not_to have_received(:enabled?)
|
|
123
|
+
expect([appium.script_calls, appium.calls]).to eq([[], []])
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'raises a stale element at once rather than waiting out the timeout' do
|
|
127
|
+
Raider.timeout = 5
|
|
128
|
+
allow(native).to receive(:displayed?).and_raise(Selenium::WebDriver::Error::StaleElementReferenceError)
|
|
129
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
130
|
+
expect { ensure_native(:click) }.to raise_error(Selenium::WebDriver::Error::StaleElementReferenceError)
|
|
131
|
+
expect(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started).to be < 1
|
|
132
|
+
expect(native).to have_received(:displayed?).once
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'reports the elapsed wait of the gate, not the configured timeout' do
|
|
136
|
+
Raider.timeout = 5
|
|
137
|
+
appium.answer(:element_displayed?, false)
|
|
138
|
+
error = nil
|
|
139
|
+
Raider::Wait.until(timeout: 0.2) do
|
|
140
|
+
ensure_native(:click)
|
|
141
|
+
rescue Raider::NotActionable => e
|
|
142
|
+
error = e
|
|
143
|
+
end
|
|
144
|
+
expect(error.message).to match(/waited 0\.[23]\d?s for/)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'raises KeyError for an action with no native table entry' do
|
|
148
|
+
expect { ensure_native(:hover) }.to raise_error(KeyError)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'makes one displayed call and no enabled call when forced' do
|
|
152
|
+
allow(native).to receive(:displayed?).and_return(true)
|
|
153
|
+
allow(native).to receive(:enabled?)
|
|
154
|
+
expect(Raider::Actionability.ensure(native, :click, force: true)).to eq(0)
|
|
155
|
+
expect(native).to have_received(:displayed?).once
|
|
156
|
+
expect(native).not_to have_received(:enabled?)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it 'raises on a stale element when forced' do
|
|
160
|
+
appium.answer(:element_displayed?, Selenium::WebDriver::Error::StaleElementReferenceError)
|
|
161
|
+
expect { Raider::Actionability.ensure(native, :click, force: true) }
|
|
162
|
+
.to raise_error(Selenium::WebDriver::Error::StaleElementReferenceError)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'makes no displayed or enabled call when actionability is off' do
|
|
166
|
+
Raider.actionability = false
|
|
167
|
+
allow(native).to receive(:displayed?)
|
|
168
|
+
allow(native).to receive(:enabled?)
|
|
169
|
+
expect(ensure_native(:click)).to eq(0)
|
|
170
|
+
expect(native).not_to have_received(:displayed?)
|
|
171
|
+
expect(native).not_to have_received(:enabled?)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'asks for the context once per action' do
|
|
175
|
+
appium.answer(:element_displayed?, true).answer(:element_enabled?, true)
|
|
176
|
+
allow(appium).to receive(:current_context).and_call_original
|
|
177
|
+
ensure_native(:click)
|
|
178
|
+
expect(appium).to have_received(:current_context).once
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
context 'when the context is a webview' do
|
|
182
|
+
let(:context) { 'WEBVIEW_1' }
|
|
183
|
+
|
|
184
|
+
it 'uses the script path as before' do
|
|
185
|
+
appium.script_results.push({ 'check' => 'visible' }, nil)
|
|
186
|
+
expect(ensure_native(:click)).to eq(1)
|
|
187
|
+
expect(appium.script_calls.size).to eq(2)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
it 'prints the notice once' do
|
|
193
|
+
bridge.script_results.push(nil, nil)
|
|
194
|
+
2.times { Raider::Actionability.ensure(element, :click, force: false) }
|
|
195
|
+
expect($stderr).to have_received(:puts).with(/actionability on/).once
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'prints no notice with RAIDER_QUIET=1' do
|
|
199
|
+
bridge.script_results.push(nil)
|
|
200
|
+
old = ENV.fetch('RAIDER_QUIET', nil)
|
|
201
|
+
ENV['RAIDER_QUIET'] = '1'
|
|
202
|
+
Raider::Actionability.ensure(element, :click, force: false)
|
|
203
|
+
expect($stderr).not_to have_received(:puts)
|
|
204
|
+
ensure
|
|
205
|
+
old ? ENV['RAIDER_QUIET'] = old : ENV.delete('RAIDER_QUIET')
|
|
206
|
+
end
|
|
207
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec'
|
|
4
|
+
require_relative '../../lib/ruby_raider/runtime'
|
|
5
|
+
require_relative 'support/fake_bridge'
|
|
6
|
+
|
|
7
|
+
RSpec.describe 'Raider::Actionable' do
|
|
8
|
+
let(:bridge) { Raider::Spec::FakeBridge.new }
|
|
9
|
+
let(:element) { Selenium::WebDriver::Element.new(bridge, 'e1') }
|
|
10
|
+
let(:keys) { Selenium::WebDriver::Keys.encode(['a', :enter]) }
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
Raider.reset
|
|
14
|
+
Raider::Actionability.reset_notice
|
|
15
|
+
allow($stderr).to receive(:puts)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
after { Raider.reset }
|
|
19
|
+
|
|
20
|
+
it 'is prepended to Selenium::WebDriver::Element' do
|
|
21
|
+
expect(Selenium::WebDriver::Element.ancestors.first).to eq(Raider::Actionable)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'defines only the four action methods and the send_key alias' do
|
|
25
|
+
expect(Raider::Actionable.instance_methods(false)).to contain_exactly(:click, :send_keys, :send_key, :clear, :submit)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'polls the script until it passes, then clicks' do
|
|
29
|
+
bridge.script_results.push({ 'check' => 'visible' }, nil)
|
|
30
|
+
element.click
|
|
31
|
+
expect([bridge.script_calls.size, bridge.calls.last]).to eq([2, [:click_element, 'e1']])
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'clicks after one tag_name command when forced' do
|
|
35
|
+
bridge.answer(:element_tag_name, 'div')
|
|
36
|
+
element.click(force: true)
|
|
37
|
+
expect([bridge.script_calls, bridge.calls]).to eq([[], [[:element_tag_name, 'e1'], [:click_element, 'e1']]])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'sends the keys Selenium would send' do
|
|
41
|
+
bridge.script_results.push(nil)
|
|
42
|
+
element.send_keys('a', :enter)
|
|
43
|
+
expect(bridge.calls.last).to eq([:send_keys_to_element, 'e1', keys])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'gates the send_key alias too' do
|
|
47
|
+
bridge.script_results.push(nil)
|
|
48
|
+
element.send_key('a')
|
|
49
|
+
expect([bridge.script_calls.size, bridge.calls.last]).to eq([1, [:send_keys_to_element, 'e1', Selenium::WebDriver::Keys.encode(['a'])]])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'sends keys when forced without leaking the keyword' do
|
|
53
|
+
bridge.answer(:element_tag_name, 'div')
|
|
54
|
+
element.send_keys('a', :enter, force: true)
|
|
55
|
+
expect(bridge.calls.last).to eq([:send_keys_to_element, 'e1', keys])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'clears after the check' do
|
|
59
|
+
bridge.script_results.push(nil)
|
|
60
|
+
element.clear
|
|
61
|
+
expect([bridge.script_calls.size, bridge.calls.last]).to eq([1, [:clear_element, 'e1']])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it 'submits after the check' do
|
|
65
|
+
bridge.script_results.push(nil)
|
|
66
|
+
element.submit
|
|
67
|
+
expect([bridge.script_calls.size, bridge.calls.last]).to eq([1, [:submit_element, 'e1']])
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'makes no script call when actionability is off' do
|
|
71
|
+
Raider.actionability = false
|
|
72
|
+
element.click
|
|
73
|
+
expect([bridge.script_calls, bridge.calls]).to eq([[], [[:click_element, 'e1']]])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'raises a stale element at once without acting' do
|
|
77
|
+
stale = Class.new(Raider::Spec::FakeBridge) do
|
|
78
|
+
def execute_script(*) = raise(Selenium::WebDriver::Error::StaleElementReferenceError, 'stale')
|
|
79
|
+
end.new
|
|
80
|
+
expect { Selenium::WebDriver::Element.new(stale, 'e1').click }
|
|
81
|
+
.to raise_error(Selenium::WebDriver::Error::StaleElementReferenceError)
|
|
82
|
+
expect(stale.calls).to be_empty
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'lets a script timeout from the page propagate' do
|
|
86
|
+
hung = Class.new(Raider::Spec::FakeBridge) do
|
|
87
|
+
def execute_script(*) = raise(Selenium::WebDriver::Error::ScriptTimeoutError, 'hung')
|
|
88
|
+
end.new
|
|
89
|
+
expect { Selenium::WebDriver::Element.new(hung, 'e1').click }
|
|
90
|
+
.to raise_error(Selenium::WebDriver::Error::ScriptTimeoutError)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
it 'gates subclasses such as the Appium element' do
|
|
94
|
+
appium_like = Class.new(Selenium::WebDriver::Element)
|
|
95
|
+
stub_const('AppiumLikeElement', appium_like)
|
|
96
|
+
bridge.script_results.push(nil)
|
|
97
|
+
appium_like.new(bridge, 'e1').click
|
|
98
|
+
expect([bridge.script_calls.size, bridge.calls.last]).to eq([1, [:click_element, 'e1']])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
describe 'on an Appium native context' do
|
|
102
|
+
let(:appium) { Class.new(Raider::Spec::FakeBridge) { def current_context = 'NATIVE_APP' }.new }
|
|
103
|
+
let(:native) { Selenium::WebDriver::Element.new(appium, 'e1') }
|
|
104
|
+
|
|
105
|
+
before { appium.answer(:element_displayed?, false, true).answer(:element_enabled?, false, true) }
|
|
106
|
+
|
|
107
|
+
it 'polls displayed and enabled, then clicks without a script call' do
|
|
108
|
+
native.click
|
|
109
|
+
expect(appium.calls).to include([:click_element, 'e1'])
|
|
110
|
+
expect(appium.script_calls).to be_empty
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'clicks after one displayed call when forced' do
|
|
114
|
+
allow(native).to receive(:displayed?).and_return(false)
|
|
115
|
+
native.click(force: true)
|
|
116
|
+
expect(native).to have_received(:displayed?).once
|
|
117
|
+
expect(appium.calls).to include([:click_element, 'e1'])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'sends keys once ready' do
|
|
121
|
+
native.send_keys('a')
|
|
122
|
+
expect(appium.calls).to include([:send_keys_to_element, 'e1', ['a']])
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec'
|
|
4
|
+
require_relative '../../../lib/ruby_raider/runtime'
|
|
5
|
+
require_relative '../support/browser'
|
|
6
|
+
|
|
7
|
+
RSpec.describe 'Raider actionability script in a real browser', :browser do
|
|
8
|
+
let(:driver) { Raider::Spec::Browser.driver }
|
|
9
|
+
let(:click) { Raider::Actionability::CHECKS[:click] }
|
|
10
|
+
let(:send_keys) { Raider::Actionability::CHECKS[:send_keys] }
|
|
11
|
+
|
|
12
|
+
before { Raider::Spec::Browser.open('actionability.html') }
|
|
13
|
+
|
|
14
|
+
after(:all) { Raider::Spec::Browser.quit } # rubocop:disable RSpec/BeforeAfterAll
|
|
15
|
+
|
|
16
|
+
def element(id)
|
|
17
|
+
driver.find_element(id: id)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def run(id, checks)
|
|
21
|
+
driver.execute_script(Raider::Actionability::SCRIPT, element(id), checks)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Stable needs a previous poll to compare with, so poll twice like the gate does.
|
|
25
|
+
def settled(id, checks)
|
|
26
|
+
run(id, checks)
|
|
27
|
+
run(id, checks)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'passes a plain button once it has been polled twice' do
|
|
31
|
+
expect(run('plain', click)).to eq('check' => 'stable', 'first' => true)
|
|
32
|
+
expect(run('plain', click)).to be_nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'fails visible for display: none and visibility: hidden' do
|
|
36
|
+
expect(run('hidden-display', click)).to eq('check' => 'visible')
|
|
37
|
+
expect(run('hidden-visibility', click)).to eq('check' => 'visible')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'scrolls an off-screen button into view and passes' do
|
|
41
|
+
expect(settled('far', click)).to be_nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'reports the overlay covering a button with integer coordinates' do
|
|
45
|
+
result = settled('covered', click)
|
|
46
|
+
expect(result['check']).to eq('events')
|
|
47
|
+
expect(result['covering']['element']).to eq('div#overlay')
|
|
48
|
+
expect(result['covering']['x']).to be_a(Integer)
|
|
49
|
+
expect(result['covering']['y']).to be_a(Integer)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'reports the sticky header covering a button after the script scrolls' do
|
|
53
|
+
result = settled('under-header', click)
|
|
54
|
+
expect(result['check']).to eq('events')
|
|
55
|
+
expect(result['covering']['element']).to eq('header#top')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'fails enabled for a disabled button and a button in a disabled fieldset' do
|
|
59
|
+
expect(run('disabled', click)).to eq('check' => 'enabled')
|
|
60
|
+
expect(run('in-fieldset', click)).to eq('check' => 'enabled')
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'treats the first legend of a disabled fieldset as enabled' do
|
|
64
|
+
expect(settled('legend-button', click)).to be_nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it 'fails editable for a readonly input and passes a writable one and contenteditable' do
|
|
68
|
+
expect(run('readonly', send_keys)).to eq('check' => 'editable')
|
|
69
|
+
expect(run('writable', send_keys)).to be_nil
|
|
70
|
+
expect(run('area', send_keys)).to be_nil
|
|
71
|
+
expect(run('editable', send_keys)).to be_nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
it 'passes editable for a button and the body, which keys such as Enter and Escape target' do
|
|
75
|
+
expect(run('plain', send_keys)).to be_nil
|
|
76
|
+
expect(driver.execute_script(Raider::Actionability::SCRIPT, driver.find_element(tag_name: 'body'), send_keys)).to be_nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'fails inert inside an [inert] region' do
|
|
80
|
+
expect(run('in-inert', click)).to eq('check' => 'inert')
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'fails inert outside an open modal dialog and passes inside it' do
|
|
84
|
+
driver.execute_script("document.getElementById('dlg').showModal()")
|
|
85
|
+
expect(run('outside-dialog', click)).to eq('check' => 'inert')
|
|
86
|
+
expect(settled('in-dialog', click)).to be_nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'reports a moving button as not stable on consecutive polls' do
|
|
90
|
+
expect(run('moving', click)).to eq('check' => 'stable', 'first' => true)
|
|
91
|
+
sleep 0.1
|
|
92
|
+
expect(run('moving', click)).to eq('check' => 'stable')
|
|
93
|
+
sleep 0.1
|
|
94
|
+
expect(run('moving', click)).to eq('check' => 'stable')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'reports an element moving vertically in a scrollable page as not stable' do
|
|
98
|
+
expect(run('bouncer', click)).to eq('check' => 'stable', 'first' => true)
|
|
99
|
+
sleep 0.1
|
|
100
|
+
expect(run('bouncer', click)).to eq('check' => 'stable')
|
|
101
|
+
sleep 0.1
|
|
102
|
+
expect(run('bouncer', click)).to eq('check' => 'stable')
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'passes a static button on the second poll' do
|
|
106
|
+
run('plain', click)
|
|
107
|
+
sleep 0.1
|
|
108
|
+
expect(run('plain', click)).to be_nil
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it 'passes editable for a select' do
|
|
112
|
+
expect(run('choices', send_keys)).to be_nil
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'fails visible for a file input inside a display: none container, except when typing a path' do
|
|
116
|
+
expect(run('file-hidden', %w[visible])).to eq('check' => 'visible')
|
|
117
|
+
expect(run('file-hidden', click)).to eq('check' => 'visible')
|
|
118
|
+
expect(run('file-hidden', send_keys)).to be_nil
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it 'fails visible for a zero-sized button' do
|
|
122
|
+
expect(run('zero', click)).to eq('check' => 'visible')
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'scrolls a button clipped inside an overflow container into view' do
|
|
126
|
+
expect(settled('deep', click)).to be_nil
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
it 'passes inert for a button in a shadow root inside the open modal dialog' do
|
|
130
|
+
driver.execute_script("document.getElementById('dlg').showModal()")
|
|
131
|
+
button = driver.execute_script("return document.getElementById('shadow-host').shadowRoot.getElementById('shadow-button')")
|
|
132
|
+
driver.execute_script(Raider::Actionability::SCRIPT, button, click)
|
|
133
|
+
expect(driver.execute_script(Raider::Actionability::SCRIPT, button, click)).to be_nil
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'skips stable and events for an option' do
|
|
137
|
+
expect(run('opt-a', click)).to be_nil
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'passes visible for a file input with an empty rect' do
|
|
141
|
+
expect(run('file', %w[visible])).to be_nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'keeps the previous rect in a WeakMap and not on the element' do
|
|
145
|
+
run('plain', click)
|
|
146
|
+
expect(driver.execute_script('return typeof window.__raiderRects')).to eq('object')
|
|
147
|
+
expect(driver.execute_script("return '__raiderRect' in document.getElementById('plain')")).to be(false)
|
|
148
|
+
expect(driver.execute_script('return window.__raider === undefined')).to be(true)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rspec'
|
|
4
|
+
require_relative '../../../lib/ruby_raider/runtime'
|
|
5
|
+
require_relative '../support/browser'
|
|
6
|
+
|
|
7
|
+
RSpec.describe 'Raider::Actionable in a real browser', :browser do
|
|
8
|
+
let(:driver) { Raider::Spec::Browser.driver }
|
|
9
|
+
|
|
10
|
+
before do
|
|
11
|
+
Raider.reset
|
|
12
|
+
Raider.timeout = 1
|
|
13
|
+
Raider::Spec::Browser.open('actionability.html')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
after { Raider.reset }
|
|
17
|
+
|
|
18
|
+
after(:all) { Raider::Spec::Browser.quit } # rubocop:disable RSpec/BeforeAfterAll
|
|
19
|
+
|
|
20
|
+
def element(id)
|
|
21
|
+
driver.find_element(id: id)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'refuses to click a button the overlay covers' do
|
|
25
|
+
expect { element('covered').click }
|
|
26
|
+
.to raise_error(Raider::NotActionable, /receive events; `div#overlay` covers it/)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'skips the checks with force: true and leaves the covered click to Selenium' do
|
|
30
|
+
expect { element('covered').click(force: true) }
|
|
31
|
+
.to raise_error(Selenium::WebDriver::Error::ElementClickInterceptedError)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'scrolls to and clicks an off-screen button' do
|
|
35
|
+
element('far').click
|
|
36
|
+
expect(element('far').attribute('data-clicked')).to eq('1')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'refuses a button in a disabled fieldset' do
|
|
40
|
+
expect { element('in-fieldset').click }.to raise_error(Raider::NotActionable, /to be enabled/)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'refuses a button inside a closed details' do
|
|
44
|
+
expect { element('in-details').click }.to raise_error(Raider::NotActionable, /to be visible/)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it 'names the sticky header covering a button' do
|
|
48
|
+
expect { element('under-header').click }.to raise_error(Raider::NotActionable, /`header#top` covers it/)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
it 'refuses to type into a readonly input' do
|
|
52
|
+
expect { element('readonly').send_keys('x') }.to raise_error(Raider::NotActionable, /to be editable/)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'types into a contenteditable element' do
|
|
56
|
+
element('editable').send_keys('hello')
|
|
57
|
+
expect(element('editable').text).to include('hello')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'clicks a button clipped inside an overflow container' do
|
|
61
|
+
element('deep').click
|
|
62
|
+
expect(element('deep').attribute('data-clicked')).to eq('1')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'sends keys to the body' do
|
|
66
|
+
expect { driver.find_element(tag_name: 'body').send_keys(:escape) }.not_to raise_error
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'refuses a moving button' do
|
|
70
|
+
expect { element('moving').click }.to raise_error(Raider::NotActionable, /to be stable/)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'lets Selenium select an option by text' do
|
|
74
|
+
Selenium::WebDriver::Support::Select.new(element('choices')).select_by(:text, 'Blue')
|
|
75
|
+
expect(element('choices').attribute('value')).to eq('blue')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it 'leaves the covered click to Selenium when actionability is off' do
|
|
79
|
+
Raider.actionability = false
|
|
80
|
+
expect { element('covered').click }.to raise_error(Selenium::WebDriver::Error::ElementClickInterceptedError)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it 'costs under 50 ms per first click on distinct visible buttons', :slow do
|
|
84
|
+
mean_ms = lambda do
|
|
85
|
+
Raider::Spec::Browser.open('actionability.html')
|
|
86
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
87
|
+
10.times { |index| element("m#{index}").click }
|
|
88
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - started) / 10 * 1000
|
|
89
|
+
end
|
|
90
|
+
Raider.actionability = true
|
|
91
|
+
on_mean = mean_ms.call
|
|
92
|
+
Raider.actionability = false
|
|
93
|
+
off_mean = mean_ms.call
|
|
94
|
+
puts format('actionability on %<on>.1f ms, off %<off>.1f ms, difference %<diff>.1f ms per first click',
|
|
95
|
+
on: on_mean, off: off_mean, diff: on_mean - off_mean)
|
|
96
|
+
expect(on_mean - off_mean).to be < 50
|
|
97
|
+
end
|
|
98
|
+
end
|