ruby_raider 3.10.0 → 3.12.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/reek.yml +5 -1
- data/.github/workflows/release.yml +2 -1
- data/.github/workflows/system_tests.yml +1 -1
- data/.reek.yml +13 -0
- data/README.md +44 -4
- data/lib/commands/utility_commands.rb +1 -1
- data/lib/generators/automation/automation_generator.rb +15 -10
- data/lib/generators/automation/templates/appium_caps.tt +2 -4
- data/lib/generators/automation/templates/home.tt +1 -2
- data/lib/generators/automation/templates/login.tt +6 -6
- data/lib/generators/automation/templates/page.tt +10 -3
- data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -6
- data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -6
- data/lib/generators/automation/templates/pdp.tt +3 -2
- data/lib/generators/cucumber/cucumber_generator.rb +15 -2
- data/lib/generators/cucumber/templates/feature.tt +5 -0
- data/lib/generators/cucumber/templates/partials/driver_world.tt +9 -0
- data/lib/generators/cucumber/templates/partials/mobile_steps.tt +1 -1
- data/lib/generators/cucumber/templates/partials/selenium_env.tt +20 -0
- data/lib/generators/cucumber/templates/partials/watir_env.tt +16 -0
- data/lib/generators/cucumber/templates/partials/web_steps.tt +8 -0
- data/lib/generators/generator.rb +15 -4
- data/lib/generators/helper_generator.rb +15 -0
- data/lib/generators/invoke_generators.rb +23 -0
- data/lib/generators/menu_generator.rb +26 -14
- data/lib/generators/rspec/rspec_generator.rb +10 -3
- data/lib/generators/rspec/templates/spec.tt +27 -1
- data/lib/generators/template_renderer/template_error.rb +8 -8
- data/lib/generators/templates/common/git_ignore.tt +4 -1
- data/lib/generators/templates/common/partials/mobile_config.tt +1 -0
- data/lib/generators/templates/common/partials/web_config.tt +5 -2
- data/lib/generators/templates/common/read_me.tt +508 -0
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +1 -3
- data/lib/generators/templates/helpers/raider_element.tt +159 -0
- data/lib/generators/templates/helpers/session_state.tt +246 -0
- data/lib/generators/templates/helpers/spec_helper.tt +28 -0
- data/lib/generators/templates/helpers/wait_helper.tt +470 -0
- data/lib/ruby_raider/ffmpeg.rb +7 -7
- data/lib/ruby_raider.rb +2 -0
- data/lib/scaffolding/project_detector.rb +6 -0
- data/lib/scaffolding/scaffolding.rb +4 -0
- data/lib/scaffolding/templates/component.tt +4 -0
- data/lib/scaffolding/templates/page_object.tt +8 -0
- data/lib/version +1 -1
- data/ruby_raider.gemspec +1 -0
- data/sig/generators/generator.rbs +2 -0
- data/sig/scaffolding/project_detector.rbs +1 -0
- data/sig/scaffolding/scaffolding.rbs +1 -0
- data/spec/commands/raider_commands_spec.rb +24 -0
- data/spec/fixtures/watir_master/cucumber_watir/Readme.md.snapshot +175 -0
- data/spec/fixtures/watir_master/cucumber_watir/features/step_definitions/login_steps.rb.snapshot +24 -0
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/abstract/page.rb.snapshot +39 -0
- data/spec/fixtures/watir_master/cucumber_watir/page_objects/pages/login.rb.snapshot +33 -0
- data/spec/fixtures/watir_master/rspec_watir/Readme.md.snapshot +174 -0
- data/spec/fixtures/watir_master/rspec_watir/page_objects/abstract/page.rb.snapshot +39 -0
- data/spec/fixtures/watir_master/rspec_watir/page_objects/pages/login.rb.snapshot +33 -0
- data/spec/fixtures/watir_master/rspec_watir/spec/login_page_spec.rb.snapshot +49 -0
- data/spec/integration/commands/scaffolding_commands_spec.rb +49 -0
- data/spec/integration/commands/utility_commands_spec.rb +8 -0
- data/spec/integration/content/azure_content_spec.rb +4 -0
- data/spec/integration/content/ci_content_spec.rb +11 -0
- data/spec/integration/content/common_content_spec.rb +27 -0
- data/spec/integration/content/config_content_spec.rb +62 -0
- data/spec/integration/content/cucumber_reporter_content_spec.rb +25 -0
- data/spec/integration/content/helper_content_spec.rb +111 -0
- data/spec/integration/content/page_content_spec.rb +75 -4
- data/spec/integration/content/readme_content_spec.rb +149 -0
- data/spec/integration/content/syntax_validation_spec.rb +38 -0
- data/spec/integration/content/test_content_spec.rb +82 -0
- data/spec/integration/end_to_end_spec.rb +73 -28
- data/spec/integration/generators/automation_generator_spec.rb +50 -0
- data/spec/integration/generators/common_generator_spec.rb +20 -0
- data/spec/integration/generators/config_features_spec.rb +2 -2
- data/spec/integration/generators/cucumber_generator_spec.rb +36 -0
- data/spec/integration/generators/helpers_generator_spec.rb +60 -0
- data/spec/integration/generators/invoke_generators_spec.rb +70 -0
- data/spec/integration/generators/rspec_generator_spec.rb +20 -0
- data/spec/integration/helpers/session_state_spec.rb +474 -0
- data/spec/integration/helpers/wait_helper_mobile_spec.rb +281 -0
- data/spec/integration/helpers/wait_helper_spec.rb +496 -0
- data/spec/integration/helpers/wait_helper_support.rb +183 -0
- data/spec/integration/settings_helper.rb +4 -1
- data/spec/integration/spec_helper.rb +2 -2
- data/spec/menus/menu_generator_spec.rb +64 -2
- data/spec/scaffolding/scaffold_project_detector_spec.rb +12 -0
- metadata +31 -2
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'wait_helper_support'
|
|
4
|
+
|
|
5
|
+
# The mobile render loads into App, so its WaitHelper and RaiderElement never meet the web
|
|
6
|
+
# render's top-level ones when both spec files run in one process.
|
|
7
|
+
module WaitHelperMobileSpec
|
|
8
|
+
module App; end
|
|
9
|
+
WaitHelperSpec.load_templates(mobile: true, into: App)
|
|
10
|
+
|
|
11
|
+
class Page
|
|
12
|
+
include App::WaitHelper
|
|
13
|
+
|
|
14
|
+
attr_reader :driver
|
|
15
|
+
|
|
16
|
+
def initialize(driver)
|
|
17
|
+
@driver = driver
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
RSpec.describe 'WaitHelper on mobile' do
|
|
22
|
+
let(:driver) { WaitHelperSpec::FakeDriver.new(device: :ios) }
|
|
23
|
+
let(:android) { WaitHelperSpec::FakeDriver.new(device: :android) }
|
|
24
|
+
let(:page) { Page.new(driver) }
|
|
25
|
+
let(:per_os) { { ios: { accessibility_id: 'ios-card' }, android: { accessibility_id: 'android-card' } } }
|
|
26
|
+
|
|
27
|
+
def element(**state) = WaitHelperSpec::FakeElement.new(**state)
|
|
28
|
+
def clock = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
29
|
+
def screen_area = { left: 0, top: 20, width: 390, height: 800 }
|
|
30
|
+
|
|
31
|
+
describe '#tap and #click' do
|
|
32
|
+
it 'click once the element is visible, enabled and stable' do
|
|
33
|
+
%i[tap click].each do |name|
|
|
34
|
+
target = element(rects: [[0, 0, 10, 10], [5, 5, 10, 10], [5, 5, 10, 10]])
|
|
35
|
+
driver.script({ accessibility_id: name.to_s }, nil, element(displayed: false), element(enabled: false), target)
|
|
36
|
+
expect(page.public_send(name, { accessibility_id: name.to_s }, timeout: 2)).to eq(target)
|
|
37
|
+
expect(target.log).to eq([[:click]])
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe 'tap with a block' do
|
|
43
|
+
it 'is Object#tap on a RaiderElement: yields and returns the wrapper without clicking' do
|
|
44
|
+
card = element
|
|
45
|
+
driver.script({ accessibility_id: 'card' }, card)
|
|
46
|
+
wrapper = page.find_element(accessibility_id: 'card')
|
|
47
|
+
seen = nil
|
|
48
|
+
expect(wrapper.tap { |yielded| seen = yielded }).to be(wrapper)
|
|
49
|
+
expect([seen, card.log]).to eq([wrapper, []])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'is Object#tap on the mixin: yields self without looking anything up' do
|
|
53
|
+
seen = nil
|
|
54
|
+
expect(page.tap { |yielded| seen = yielded }).to be(page)
|
|
55
|
+
expect(page.tap(&:driver)).to be(page)
|
|
56
|
+
expect([seen, driver.finds, driver.scripts]).to eq([page, [], []])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'still clicks without a block' do
|
|
60
|
+
card = element
|
|
61
|
+
driver.script({ id: 'x' }, card)
|
|
62
|
+
page.tap({ id: 'x' }, timeout: 1)
|
|
63
|
+
page.tap(id: 'x')
|
|
64
|
+
page.find_element(id: 'x').tap
|
|
65
|
+
expect(card.log).to eq([[:click]] * 3)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
describe '#swipe' do
|
|
70
|
+
it 'uses mobile: swipe on iOS' do
|
|
71
|
+
card = element
|
|
72
|
+
driver.script({ accessibility_id: 'card' }, card)
|
|
73
|
+
expect(page.swipe('left', { accessibility_id: 'card' }, timeout: 1)).to eq(card)
|
|
74
|
+
expect(driver.scripts).to eq([['mobile: swipe', { direction: 'left', element: card.id }]])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'uses mobile: swipeGesture on Android' do
|
|
78
|
+
card = element
|
|
79
|
+
android.script({ accessibility_id: 'card' }, card)
|
|
80
|
+
Page.new(android).swipe('left', { accessibility_id: 'card' }, timeout: 1)
|
|
81
|
+
expect(android.scripts)
|
|
82
|
+
.to eq([['mobile: swipeGesture', { elementId: card.id, direction: 'left', percent: 0.75 }]])
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'waits for the element to be visible first' do
|
|
86
|
+
card = element
|
|
87
|
+
driver.script({ accessibility_id: 'card' }, nil, element(displayed: false), card)
|
|
88
|
+
page.swipe('up', { accessibility_id: 'card' }, timeout: 1)
|
|
89
|
+
expect([driver.finds.size, driver.scripts.size]).to eq([3, 1])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'swipes the whole screen without a locator' do
|
|
93
|
+
expect(page.swipe('up')).to be_nil
|
|
94
|
+
Page.new(android).swipe('up')
|
|
95
|
+
expect(driver.scripts).to eq([['mobile: swipe', { direction: 'up' }]])
|
|
96
|
+
expect(android.scripts).to eq([['mobile: swipeGesture', { **screen_area, direction: 'up', percent: 0.75 }]])
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
it 'rejects a direction other than up, down, left or right' do
|
|
100
|
+
expect { page.swipe('sideways') }.to raise_error(ArgumentError, /up, down, left or right/)
|
|
101
|
+
expect(driver.scripts).to be_empty
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
describe '#scroll' do
|
|
106
|
+
it 'scrolls the screen without a locator' do
|
|
107
|
+
page.scroll('down')
|
|
108
|
+
Page.new(android).scroll('down')
|
|
109
|
+
expect(driver.scripts).to eq([['mobile: scroll', { direction: 'down' }]])
|
|
110
|
+
expect(android.scripts).to eq([['mobile: scrollGesture', { **screen_area, direction: 'down', percent: 0.75 }]])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it 'scrolls a container' do
|
|
114
|
+
list = element
|
|
115
|
+
android.script({ id: 'list' }, list)
|
|
116
|
+
Page.new(android).scroll('up', { id: 'list' }, timeout: 1)
|
|
117
|
+
expect(android.scripts).to eq([['mobile: scrollGesture', { elementId: list.id, direction: 'up', percent: 0.75 }]])
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe 'an unknown device' do
|
|
122
|
+
it 'raises NotImplementedError naming it, before waiting' do
|
|
123
|
+
windows = WaitHelperSpec::FakeDriver.new(device: :windows)
|
|
124
|
+
started = clock
|
|
125
|
+
expect { Page.new(windows).swipe('left', { id: 'never' }, timeout: 2) }
|
|
126
|
+
.to raise_error(NotImplementedError, /windows/)
|
|
127
|
+
expect { Page.new(windows).scroll('down') }.to raise_error(NotImplementedError, /windows/)
|
|
128
|
+
expect(clock - started).to be < 0.5
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
describe '#scroll_to' do
|
|
133
|
+
it 'returns a RaiderElement once the element turns visible after 2 scrolls' do
|
|
134
|
+
footer = element
|
|
135
|
+
driver.script({ accessibility_id: 'footer' }, nil, element(displayed: false), footer)
|
|
136
|
+
found = page.scroll_to({ accessibility_id: 'footer' }, timeout: 2)
|
|
137
|
+
expect(found).to be_a(App::RaiderElement).and eq(footer)
|
|
138
|
+
expect(driver.scripts).to eq([['mobile: scroll', { direction: 'down' }]] * 2)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'scrolls in the given direction' do
|
|
142
|
+
driver.script({ accessibility_id: 'header' }, nil, element)
|
|
143
|
+
page.scroll_to({ accessibility_id: 'header' }, direction: 'up', timeout: 2)
|
|
144
|
+
expect(driver.scripts).to eq([['mobile: scroll', { direction: 'up' }]])
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'stops after max_scrolls scrolls and says so' do
|
|
148
|
+
started = clock
|
|
149
|
+
expect { page.scroll_to({ accessibility_id: 'footer' }, max_scrolls: 3, timeout: 5) }
|
|
150
|
+
.to raise_error(App::WaitHelper::TimeoutError) { |error|
|
|
151
|
+
expect(error.message).to include('to be visible').and include('after 3 scrolls')
|
|
152
|
+
}
|
|
153
|
+
expect(driver.scripts.size).to eq(3)
|
|
154
|
+
expect(driver.finds.size).to eq(4)
|
|
155
|
+
expect(clock - started).to be < 2
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'also stops at the timeout, naming the scrolls so far' do
|
|
159
|
+
expect { page.scroll_to({ accessibility_id: 'footer' }, timeout: 0) }
|
|
160
|
+
.to raise_error(App::WaitHelper::TimeoutError, /after 0 scrolls/)
|
|
161
|
+
expect(driver.scripts).to be_empty
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
describe 'per-OS locators' do
|
|
166
|
+
it 'resolve on tap and swipe' do
|
|
167
|
+
card = element
|
|
168
|
+
android.script({ accessibility_id: 'android-card' }, card)
|
|
169
|
+
mobile = Page.new(android)
|
|
170
|
+
mobile.tap(per_os, timeout: 1)
|
|
171
|
+
mobile.swipe('right', per_os, timeout: 1)
|
|
172
|
+
expect(android.finds.uniq).to eq([{ accessibility_id: 'android-card' }])
|
|
173
|
+
expect([card.log, android.scripts.size]).to eq([[[:click]], 1])
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'resolve on a nested find_element' do
|
|
177
|
+
row = element
|
|
178
|
+
list = element.script({ accessibility_id: 'ios-row' }, row)
|
|
179
|
+
driver.script({ accessibility_id: 'ios-card' }, list)
|
|
180
|
+
found = page.find_element(per_os).find_element({ ios: { accessibility_id: 'ios-row' }, android: { id: 'row' } })
|
|
181
|
+
expect(found).to eq(row)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
describe 'RaiderElement' do
|
|
186
|
+
it 'has the mobile set without the locator' do
|
|
187
|
+
card = element
|
|
188
|
+
driver.script({ accessibility_id: 'card' }, card)
|
|
189
|
+
wrapper = page.find_element(accessibility_id: 'card')
|
|
190
|
+
expect(wrapper.tap.swipe('left').scroll('down').scroll_to).to be(wrapper)
|
|
191
|
+
expect(card.log).to eq([[:click]])
|
|
192
|
+
expect(driver.scripts).to eq([['mobile: swipe', { direction: 'left', element: card.id }],
|
|
193
|
+
['mobile: scroll', { direction: 'down', element: card.id }]])
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it 'adds exactly the planned public methods' do
|
|
197
|
+
expect(App::RaiderElement.public_instance_methods(false).sort)
|
|
198
|
+
.to eq(%i[== attribute clear click displayed? enabled? eql? find_element find_elements hash native scroll
|
|
199
|
+
scroll_to selected? send_keys swipe tap text])
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe 'the choke point' do
|
|
204
|
+
let(:actions) { [] }
|
|
205
|
+
|
|
206
|
+
before do
|
|
207
|
+
driver.script({ id: 'x' }, element.script({ css: 'y' }, element).script_many({ css: 'y' }, [element]))
|
|
208
|
+
allow_any_instance_of(App::WaitHelper::Waiter).to receive(:perform_action).and_wrap_original do |original, *args, **options, &block|
|
|
209
|
+
actions << args.first
|
|
210
|
+
original.call(*args, **options, &block)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def calls(locator)
|
|
215
|
+
{ find_element: [locator], find_elements: [locator], click: [locator], tap: [locator], clear: [locator],
|
|
216
|
+
text: [locator], displayed?: [locator], enabled?: [locator], selected?: [locator],
|
|
217
|
+
send_keys: [locator, 'a'], attribute: [locator, 'name'], swipe: ['left', locator],
|
|
218
|
+
scroll: ['down', locator], scroll_to: [locator] }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
it 'is called once per mixin method, with the method name' do
|
|
222
|
+
calls({ id: 'x' }).each do |name, args|
|
|
223
|
+
actions.clear
|
|
224
|
+
page.public_send(name, *args, timeout: 1)
|
|
225
|
+
expect(actions).to eq([name])
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it 'is called once for a whole-screen gesture' do
|
|
230
|
+
%i[swipe scroll].each do |name|
|
|
231
|
+
actions.clear
|
|
232
|
+
page.public_send(name, 'up')
|
|
233
|
+
expect(actions).to eq([name])
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it 'is called once per wrapper method, with the method name' do
|
|
238
|
+
wrapper = page.find_element(id: 'x')
|
|
239
|
+
calls({ css: 'y' }).each do |name, args|
|
|
240
|
+
args = args.reject { |arg| arg.is_a?(Hash) } unless %i[find_element find_elements].include?(name)
|
|
241
|
+
actions.clear
|
|
242
|
+
wrapper.public_send(name, *args, timeout: 1)
|
|
243
|
+
expect(actions).to eq([name])
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
describe 'footprint' do
|
|
249
|
+
it 'adds exactly the mobile API, with no private methods and nothing named wait' do
|
|
250
|
+
expect(App::WaitHelper.public_instance_methods(false).sort)
|
|
251
|
+
.to eq(%i[attribute clear click displayed? enabled? eventually find_element find_elements scroll scroll_to
|
|
252
|
+
selected? send_keys swipe tap text])
|
|
253
|
+
expect(App::WaitHelper.private_instance_methods(false)).to be_empty
|
|
254
|
+
expect(page.respond_to?(:wait, true)).to be(false)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
describe '#eventually' do
|
|
259
|
+
it 'retries the block as on web' do
|
|
260
|
+
tries = 0
|
|
261
|
+
expect(page.eventually(timeout: 1) { expect(tries += 1).to eq(2) }).to be(true)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
it 'retries a nested timeout: until its own deadline, as on web' do
|
|
265
|
+
started = clock
|
|
266
|
+
expect { page.eventually(timeout: 0.6) { page.text({ accessibility_id: 'never' }, timeout: 0.2) } }
|
|
267
|
+
.to raise_error(App::WaitHelper::TimeoutError, /\A\(still failing after 0.6s\)/)
|
|
268
|
+
expect(clock - started).to be_between(0.5, 1.0)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
it 'raises at once for a stale find_elements member used as a parent' do
|
|
272
|
+
row = element.script({ accessibility_id: 'cell' }, element)
|
|
273
|
+
driver.script_many({ accessibility_id: 'row' }, [row])
|
|
274
|
+
member = page.find_elements(accessibility_id: 'row').first
|
|
275
|
+
row.go_stale
|
|
276
|
+
expect { member.find_element(accessibility_id: 'cell', timeout: 2) }
|
|
277
|
+
.to raise_error(Selenium::WebDriver::Error::StaleElementReferenceError, /find_elements again/)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|