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
|
@@ -19,10 +19,6 @@
|
|
|
19
19
|
<p align="center"> For more information and updates on releases, see <a href="https://ruby-raider.com">https://ruby-raider.com</a></p>
|
|
20
20
|
</div>
|
|
21
21
|
|
|
22
|
-
<%- if watir? -%>
|
|
23
|
-
> **Deprecated:** Watir is deprecated and will be removed in the next major version of ruby_raider; new projects should use Selenium. Watir is only available through `-p automation:watir`. See [Migrating from Watir](https://github.com/RaiderHQ/ruby_raider#migrating-from-watir).
|
|
24
|
-
|
|
25
|
-
<%- end -%>
|
|
26
22
|
## What is Ruby Raider?
|
|
27
23
|
|
|
28
24
|
Ruby Raider is a generator and scaffolding gem to make UI test automation easier.
|
|
@@ -31,11 +27,8 @@ Ruby Raider is a generator and scaffolding gem to make UI test automation easier
|
|
|
31
27
|
|
|
32
28
|
| Web Testing Framework | Mobile Testing Framework |
|
|
33
29
|
|-----------------------|------------------------------------|
|
|
34
|
-
| Cucumber and Selenium | Cucumber and Appium for iOS |
|
|
35
30
|
| RSpec and Selenium | RSpec and Appium for iOS |
|
|
36
|
-
|
|
|
37
|
-
| RSpec and Watir | RSpec and Appium for Android |
|
|
38
|
-
| | Cucumber and Appium Cross-platform |
|
|
31
|
+
| | RSpec and Appium for Android |
|
|
39
32
|
| | RSpec and Appium Cross-platform |
|
|
40
33
|
|
|
41
34
|
**Optional add-on for web projects:**
|
|
@@ -81,13 +74,10 @@ Shortcuts: `g` (generate), `n` (new), `u` (utility), `v` (version)
|
|
|
81
74
|
```
|
|
82
75
|
raider g page [NAME] # Create page object
|
|
83
76
|
raider g spec [NAME] # Create RSpec test
|
|
84
|
-
raider g
|
|
85
|
-
raider g steps [NAME] # Create step definitions
|
|
86
|
-
raider g scaffold [NAME] # Create page + test + steps
|
|
77
|
+
raider g scaffold [NAME] # Create page + test
|
|
87
78
|
```
|
|
88
79
|
|
|
89
|
-
<%-
|
|
90
|
-
<%- test_cmd = rspec? ? 'rspec' : 'cucumber' -%>
|
|
80
|
+
<%- test_cmd = 'rspec' -%>
|
|
91
81
|
## How the framework works
|
|
92
82
|
|
|
93
83
|
This section walks through the project: where things live, how a test finds an element and acts on it, and how to
|
|
@@ -98,7 +88,7 @@ run the tests.
|
|
|
98
88
|
|
|
99
89
|
This project was generated without example tests, so its <%= mobile? ? 'pages' : 'pages, components' %> and tests folders are empty.
|
|
100
90
|
Grow it with the scaffolding commands above: `raider g scaffold checkout` writes a `Checkout` page and its
|
|
101
|
-
|
|
91
|
+
spec, and `raider g page` and `raider g spec` write one file at a time. The code examples
|
|
102
92
|
below show what goes in them.
|
|
103
93
|
<%- end -%>
|
|
104
94
|
|
|
@@ -118,58 +108,38 @@ languages call a function (it starts with `def` and ends with `end`).
|
|
|
118
108
|
- `page_objects/pages/`: one class per screen of the app, empty for now. A page holds the locators of its elements
|
|
119
109
|
and the actions a test can do on that screen.
|
|
120
110
|
<%- end -%>
|
|
121
|
-
- `page_objects/abstract/`: `page.rb`, the base class every page builds on. It
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
111
|
+
- `page_objects/abstract/`: `page.rb`, the base class every page builds on. It requires the Raider runtime and includes
|
|
112
|
+
`gesture_helper.rb`.
|
|
113
|
+
- `helpers/`: `driver_helper.rb` starts the Appium session, `gesture_helper.rb` has `swipe`, `scroll` and `scroll_to`,
|
|
114
|
+
and `video_helper.rb` records the screen<%= cross_platform? ? '. `appium_helper.rb` has `element(ios: …, android: …)`, a lookup for both platforms' : '' %>.
|
|
125
115
|
`spec_helper.rb` sets up RSpec: it starts the app before each test and closes it after.
|
|
126
116
|
<%- if examples? -%>
|
|
127
117
|
- `spec/`: the tests. `pdp_page_spec.rb` opens a product and checks its "Add to Cart" button.
|
|
128
118
|
<%- else -%>
|
|
129
119
|
- `spec/`: the tests, empty for now.
|
|
130
120
|
<%- end -%>
|
|
131
|
-
<%- elsif examples? -%>
|
|
132
|
-
- `features/`: the tests. `home.feature` describes them in plain English, `step_definitions/home_steps.rb` holds the
|
|
133
|
-
Ruby code behind each step, and `support/` starts the app (`env.rb`) and makes the helpers available to the steps
|
|
134
|
-
(`world.rb`).
|
|
135
|
-
<%- else -%>
|
|
136
|
-
- `features/`: the tests, empty for now. A `.feature` file describes them in plain English, `step_definitions/` holds
|
|
137
|
-
the Ruby code behind each step, and `support/` starts the app (`env.rb`) and makes the helpers available to the
|
|
138
|
-
steps (`world.rb`).
|
|
139
|
-
<%- end -%>
|
|
140
121
|
<%- else -%>
|
|
141
122
|
`config.yml` is described under "Settings" below.
|
|
142
123
|
<%- if examples? -%>
|
|
143
124
|
- `page_objects/pages/`: one class per page of the site. `login.rb` is the login page and `account.rb` the account
|
|
144
125
|
page. A page holds the locators of its elements and the actions a test can do on that page.
|
|
145
|
-
- `page_objects/components/`: parts that appear on many pages.
|
|
126
|
+
- `page_objects/components/`: parts that appear on many pages. Create one with `raider g component`.
|
|
146
127
|
<%- else -%>
|
|
147
128
|
- `page_objects/pages/`: one class per page of the site, empty for now. A page holds the locators of its elements
|
|
148
129
|
and the actions a test can do on that page.
|
|
149
|
-
- `page_objects/components/`: parts that appear on many pages
|
|
130
|
+
- `page_objects/components/`: parts that appear on many pages. Create one with `raider g component`.
|
|
150
131
|
<%- end -%>
|
|
151
132
|
- `page_objects/abstract/`: `page.rb` and `component.rb`, the base classes every page and component builds on.
|
|
152
|
-
`page.rb` gives each page
|
|
153
|
-
- `helpers/`: `driver_helper.rb` starts the browser, `
|
|
154
|
-
`
|
|
155
|
-
<%- if rspec? -%>
|
|
156
|
-
`spec_helper.rb` sets up RSpec: it opens the browser before each test and closes it after.
|
|
157
|
-
<%- end -%>
|
|
133
|
+
`page.rb` requires the Raider runtime and gives each page `by_role`.
|
|
134
|
+
- `helpers/`: `driver_helper.rb` starts the browser, `video_helper.rb` records failure videos and `session_state.rb`
|
|
135
|
+
reuses the login. `spec_helper.rb` sets up RSpec: it opens the browser before each test and closes it after.
|
|
158
136
|
<%- if examples? -%>
|
|
159
|
-
- `models/`: test data. `data/users.yml` holds the users, and `model_factory.rb
|
|
137
|
+
- `models/`: test data. `data/users.yml` holds the users, and `model_factory.rb` loads them.
|
|
160
138
|
<%- end -%>
|
|
161
|
-
<%- if
|
|
139
|
+
<%- if examples? -%>
|
|
162
140
|
- `spec/`: the tests. `login_page_spec.rb` logs in with the right password and with a wrong one.
|
|
163
|
-
<%- elsif rspec? -%>
|
|
164
|
-
- `spec/`: the tests, empty for now.
|
|
165
|
-
<%- elsif examples? -%>
|
|
166
|
-
- `features/`: the tests. `login.feature` describes them in plain English, `step_definitions/login_steps.rb` holds
|
|
167
|
-
the Ruby code behind each step, and `support/` opens the browser (`env.rb`) and makes the helpers available to the
|
|
168
|
-
steps (`world.rb`).
|
|
169
141
|
<%- else -%>
|
|
170
|
-
- `
|
|
171
|
-
holds the Ruby code behind each step, and `support/` opens the browser (`env.rb`) and makes the helpers available
|
|
172
|
-
to the steps (`world.rb`).
|
|
142
|
+
- `spec/`: the tests, empty for now.
|
|
173
143
|
<%- end -%>
|
|
174
144
|
<%- end -%>
|
|
175
145
|
<%- if azure? && !mobile? -%>
|
|
@@ -197,15 +167,20 @@ The `browserstack` part is used instead when you run with `TEST_ENV=browserstack
|
|
|
197
167
|
`config/config.yml`:
|
|
198
168
|
|
|
199
169
|
- `platform`: which part of `capabilities.yml` to use, `ios` or `android`.
|
|
200
|
-
- `timeout`: how many seconds
|
|
170
|
+
- `timeout`: how many seconds finds, actions and assertions wait before the test fails (10). To wait longer,
|
|
201
171
|
change it, for example to `timeout: 15`.
|
|
172
|
+
- `strict` (`true`) and `actionability` (`true`): see [Waiting](#waiting). `raider u strict off` and
|
|
173
|
+
`raider u actionability off` set them. `RAIDER_TIMEOUT`, `RAIDER_STRICT` and `RAIDER_ACTIONABILITY` override the file for one run.
|
|
202
174
|
- `video`: failure videos, see [Video replay](#video-replay).
|
|
203
175
|
<%- else -%>
|
|
204
|
-
This project has no `config/config.yml
|
|
205
|
-
|
|
176
|
+
This project has no `config/config.yml`. The runtime waits up to 10 seconds, with strict and actionability on. To change
|
|
177
|
+
that for one run, set `RAIDER_TIMEOUT`, `RAIDER_STRICT` or `RAIDER_ACTIONABILITY`, for example `RAIDER_TIMEOUT=15`. To
|
|
178
|
+
change it for the project, create `config/config.yml` with the keys you want:
|
|
206
179
|
|
|
207
180
|
```yaml
|
|
208
181
|
timeout: 15
|
|
182
|
+
strict: true
|
|
183
|
+
actionability: true
|
|
209
184
|
```
|
|
210
185
|
<%- end -%>
|
|
211
186
|
<%- else -%>
|
|
@@ -218,8 +193,13 @@ timeout: 15
|
|
|
218
193
|
<%- else -%>
|
|
219
194
|
- `url`: the address of the site under test. Each page adds its own path to it. Change it to your site.
|
|
220
195
|
<%- end -%>
|
|
221
|
-
- `timeout`: how many seconds
|
|
222
|
-
- `
|
|
196
|
+
- `timeout`: how many seconds finds, actions and assertions wait before the test fails (10).
|
|
197
|
+
- `strict`: `true` makes `find_element` fail when a locator matches more than one element. `false` returns the first.
|
|
198
|
+
`raider u strict off` sets it.
|
|
199
|
+
- `actionability`: `true` makes actions wait until the element is ready, see
|
|
200
|
+
[Waiting and actionability](#waiting-and-actionability). `raider u actionability off` sets it.
|
|
201
|
+
`RAIDER_TIMEOUT`, `RAIDER_STRICT` and `RAIDER_ACTIONABILITY` override the file for one run.
|
|
202
|
+
- `viewport`: the page viewport's width and height in CSS pixels.
|
|
223
203
|
- `browser_arguments`: extra options passed to each browser when it starts.
|
|
224
204
|
- `headless`: not in the file by default. Add `headless: true`, or set `HEADLESS=true` for one run, to run the
|
|
225
205
|
browser without a window.
|
|
@@ -242,203 +222,243 @@ with it. For example, on a <%= mobile? ? 'product list screen' : 'login page' %>
|
|
|
242
222
|
```ruby
|
|
243
223
|
<%- if mobile? -%>
|
|
244
224
|
def go_to_product_detail
|
|
245
|
-
|
|
225
|
+
first_product.click
|
|
246
226
|
end
|
|
247
227
|
|
|
248
228
|
def first_product
|
|
249
229
|
<%- if cross_platform? -%>
|
|
250
|
-
{ ios: { accessibility_id: 'product-item-1' },
|
|
251
|
-
android: { accessibility_id: 'product-item-1' } }
|
|
230
|
+
element({ ios: { accessibility_id: 'product-item-1' }, android: { accessibility_id: 'product-item-1' } })
|
|
252
231
|
<%- else -%>
|
|
253
|
-
|
|
232
|
+
driver.find_element(accessibility_id: 'product-item-1')
|
|
254
233
|
<%- end -%>
|
|
255
234
|
end
|
|
256
235
|
<%- else -%>
|
|
257
236
|
def login(username, password)
|
|
258
|
-
send_keys(
|
|
259
|
-
send_keys(
|
|
260
|
-
click
|
|
237
|
+
username_field.send_keys(username)
|
|
238
|
+
password_field.send_keys(password)
|
|
239
|
+
login_button.click
|
|
261
240
|
end
|
|
262
241
|
|
|
263
242
|
def username_field
|
|
264
|
-
|
|
243
|
+
driver.find_element(id: 'loginFrm_loginname')
|
|
265
244
|
end
|
|
266
245
|
<%- end -%>
|
|
267
246
|
```
|
|
268
247
|
|
|
248
|
+
Pages call <%= mobile? ? 'Appium' : 'Selenium' %> directly: `driver.find_element` finds an element and `click` and `send_keys` act on it.
|
|
249
|
+
The Raider runtime (`ruby_raider/runtime`) makes them wait. `helpers/driver_helper.rb` and `page_objects/abstract/page.rb`
|
|
250
|
+
require it, so it is loaded before the driver exists.
|
|
269
251
|
<%- if mobile? -%>
|
|
270
|
-
An element method returns a **locator**: a Ruby hash (a key and a value in curly braces) that tells Appium how to
|
|
271
|
-
find the element. `{ accessibility_id: 'product-item-1' }` means "the element whose accessibility id is
|
|
272
|
-
`product-item-1`". `xpath`, `id` and `class_name` work too.
|
|
273
252
|
<%- if cross_platform? -%>
|
|
274
253
|
|
|
275
|
-
Because this project runs on both iOS and Android, a locator can name one for each: `
|
|
276
|
-
|
|
254
|
+
Because this project runs on both iOS and Android, a locator can name one for each: `element(ios: { … }, android: { … })`
|
|
255
|
+
from `helpers/appium_helper.rb` picks the one for the device the test runs on. It calls `driver.find_element`, so it waits
|
|
256
|
+
like any other find.
|
|
277
257
|
<%- end -%>
|
|
278
|
-
|
|
279
|
-
The action, `tap(first_product)`, comes from the wait helper: it waits until the product is ready, then taps it.
|
|
280
258
|
<%- else -%>
|
|
281
|
-
An element method returns a **locator**: a Ruby hash (a key and a value in curly braces) that tells Selenium how to
|
|
282
|
-
find the element. `{ id: 'loginFrm_loginname' }` means "the element whose id is `loginFrm_loginname`". `css`,
|
|
283
|
-
`xpath`, `name`, `class` and `link_text` work too.
|
|
284
|
-
|
|
285
|
-
The actions, `send_keys` and `click`, come from the wait helper: each one waits until the element is ready, then
|
|
286
|
-
types or clicks.
|
|
287
259
|
|
|
288
260
|
<%- if examples? -%>
|
|
289
|
-
A **component** is a part of the page that has its own methods. `
|
|
290
|
-
|
|
261
|
+
A **component** is a part of the page that has its own methods. It builds on `page_objects/abstract/component.rb`. This
|
|
262
|
+
project has none yet; add one with `raider g component`.
|
|
291
263
|
<%- else -%>
|
|
292
|
-
A **component** is a part of the page that has its own methods. It builds on `page_objects/abstract/component.rb`,
|
|
293
|
-
|
|
264
|
+
A **component** is a part of the page that has its own methods. It builds on `page_objects/abstract/component.rb`, which
|
|
265
|
+
keeps the element it wraps as `component`, so `component.find_element(...)` searches inside it.
|
|
294
266
|
<%- end -%>
|
|
267
|
+
<%- end -%>
|
|
268
|
+
|
|
269
|
+
### Locators
|
|
270
|
+
|
|
271
|
+
<%- if mobile? -%>
|
|
272
|
+
An element method returns a **locator**: a Ruby hash (a key and a value in curly braces) that tells Appium how to find
|
|
273
|
+
the element. `{ accessibility_id: 'product-item-1' }` means "the element whose accessibility id is `product-item-1`".
|
|
274
|
+
These keys work on an app: `accessibility_id:`, `id:`, `xpath:` and `class_name:`.
|
|
295
275
|
|
|
296
276
|
```ruby
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
end
|
|
277
|
+
driver.find_element(accessibility_id: 'product-item-1')
|
|
278
|
+
driver.find_elements(accessibility_id: 'product-item-1')
|
|
300
279
|
```
|
|
301
280
|
|
|
302
|
-
`
|
|
303
|
-
<%-
|
|
281
|
+
`find_element` returns the first match. `find_elements` returns every match.
|
|
282
|
+
<%- else -%>
|
|
283
|
+
An element method returns a **locator**: a Ruby hash (a key and a value in curly braces) that tells Selenium how to find
|
|
284
|
+
the element. `{ id: 'loginFrm_loginname' }` means "the element whose id is `loginFrm_loginname`". Selenium's own keys
|
|
285
|
+
work: `id:`, `css:`, `xpath:`, `name:`, `class:` and `link_text:`. Raider adds the ones people see on the page:
|
|
304
286
|
|
|
305
|
-
|
|
287
|
+
| Key | Finds | Example |
|
|
288
|
+
|-----|-------|---------|
|
|
289
|
+
| `label:` | the field with that label text, `aria-label` or `aria-labelledby` | `driver.find_element(label: 'Email')` |
|
|
290
|
+
| `placeholder:` | the field with that placeholder | `driver.find_element(placeholder: 'Search')` |
|
|
291
|
+
| `alt:` | the element with that `alt` text, such as an image | `driver.find_element(alt: 'Logo')` |
|
|
292
|
+
| `title:` | the element with that `title` | `driver.find_element(title: 'Close')` |
|
|
293
|
+
| `text:` | the innermost element whose text contains it | `driver.find_element(text: 'Sign in')` |
|
|
294
|
+
| `test_id:` | the element whose `data-testid` equals it | `driver.find_element(test_id: 'submit')` |
|
|
306
295
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
`timeout` seconds. If the element is still not ready, the test fails and says what it saw last:
|
|
296
|
+
`label:`, `placeholder:`, `alt:`, `title:` and `text:` match a part of the text, ignoring extra spaces. For a match of the
|
|
297
|
+
whole text, pass the value and `{ exact: true }` in an array:
|
|
310
298
|
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
Waited 10s for {accessibility_id: "product-item-1"} to be visible, enabled and stable; last seen: not found
|
|
314
|
-
<%- else -%>
|
|
315
|
-
Waited 10s for {id: "loginFrm_loginname"} to be visible and enabled; last seen: present but not visible
|
|
316
|
-
<%- end -%>
|
|
299
|
+
```ruby
|
|
300
|
+
driver.find_element(label: ['Email', { exact: true }])
|
|
317
301
|
```
|
|
318
302
|
|
|
319
|
-
|
|
303
|
+
`test_id:` reads `data-testid`. If your app uses another attribute, set `Raider.test_id_attribute = 'data-qa'` or
|
|
304
|
+
`RAIDER_TEST_ID_ATTRIBUTE=data-qa`.
|
|
320
305
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
| `find_elements(locator)` | present, at least one | returns a list of RaiderElements, or `[]` if none showed up |
|
|
325
|
-
<%- if mobile? -%>
|
|
326
|
-
| `click(locator)`, `tap(locator)` | visible, enabled and still | taps it (`tap` and `click` do the same) |
|
|
327
|
-
| `send_keys(locator, 'text')` | visible and enabled | types the text |
|
|
328
|
-
| `clear(locator)` | visible and enabled | empties the field |
|
|
329
|
-
| `swipe('left', locator)` | visible | swipes on it: `'up'`, `'down'`, `'left'` or `'right'` |
|
|
330
|
-
| `scroll('down', locator)` | visible | scrolls inside it |
|
|
331
|
-
| `scroll_to(locator)` | visible, scrolling down up to 10 times | returns it as a RaiderElement |
|
|
306
|
+
`by_role` finds an element the way a person or a screen reader does, by its ARIA role and its name.
|
|
307
|
+
<%- if examples? -%>
|
|
308
|
+
`login.rb` uses it for the button:
|
|
332
309
|
<%- else -%>
|
|
333
|
-
|
|
334
|
-
| `send_keys(locator, 'text')` | visible and enabled | types the text |
|
|
335
|
-
| `clear(locator)` | visible and enabled | empties the field |
|
|
336
|
-
| `submit(locator)` | visible and enabled | submits its form |
|
|
337
|
-
| `select_by(locator, :text, 'Blue')` | visible and enabled | picks a dropdown option by `:text`, `:value` or `:index` |
|
|
338
|
-
| `hover(locator)` | visible and still | moves the mouse over it |
|
|
339
|
-
| `drag_and_drop(source, target)` | source visible, enabled and still; target visible | drags the source onto the target |
|
|
340
|
-
| `scroll_into_view(locator)` | present | scrolls the page to it |
|
|
310
|
+
For example, a button:
|
|
341
311
|
<%- end -%>
|
|
342
|
-
| `text(locator)`, `attribute(locator, 'name')` | present | returns its text or the attribute's value |
|
|
343
|
-
| `displayed?(locator)`, `enabled?(locator)`, `selected?(locator)` | present | returns `true` or `false` |
|
|
344
|
-
| `eventually { … }` | (see below) | retries the block until it passes |
|
|
345
312
|
|
|
346
|
-
|
|
313
|
+
```ruby
|
|
314
|
+
driver.find_element(by_role('button', name: 'Login'))
|
|
315
|
+
```
|
|
347
316
|
|
|
348
|
-
|
|
349
|
-
- Write the locator with or without braces: `tap(accessibility_id: 'login')` and `tap({ accessibility_id: 'login' })`
|
|
350
|
-
are the same. When a method takes more than the locator, put the locator in braces:
|
|
351
|
-
`send_keys({ accessibility_id: 'email' }, 'me@example.com')`, `swipe('left', { accessibility_id: 'card' })`.
|
|
352
|
-
<%- else -%>
|
|
353
|
-
- Write the locator with or without braces: `click(id: 'login')` and `click({ id: 'login' })` are the same. When a
|
|
354
|
-
method takes more than the locator, put the locator in braces: `send_keys({ id: 'email' }, 'me@example.com')`.
|
|
355
|
-
<%- end -%>
|
|
356
|
-
- `timeout:` changes the wait for one call, in seconds: `<%= mobile? ? "tap({ accessibility_id: 'slow-button' }, timeout: 30)" : "click({ id: 'slow-button' }, timeout: 30)" %>`.
|
|
357
|
-
- "Still" (`stable` in error messages) means the element was in the same place on two checks in a row, 0.1 seconds
|
|
358
|
-
apart, so it is not in the middle of an animation. That is why <%= mobile? ? '`click` and `tap`' : '`click`, `hover` and `drag_and_drop`' %> need at least two checks, and `timeout: 0` never passes for them.
|
|
359
|
-
- `text`, `displayed?` and the other readers fail when the element never shows up. To check that something is
|
|
360
|
-
**not** on the <%= mobile? ? 'screen' : 'page' %>, ask for the list without waiting and check that it is empty:
|
|
361
|
-
|
|
362
|
-
```ruby
|
|
363
|
-
eventually { expect(find_elements(<%= mobile? ? "accessibility_id: 'error'" : "css: '.error'" %>, timeout: 0)).to be_empty }
|
|
364
|
-
```
|
|
365
|
-
<%- if mobile? -%>
|
|
366
|
-
- `swipe` and `scroll` with a locator wait for that element to be visible and act on it. Without a locator
|
|
367
|
-
(`swipe('up')`) they act on the whole screen and return `nil`.
|
|
368
|
-
- `scroll_to(locator)` checks for the element, scrolls once if it is not visible, and checks again, up to
|
|
369
|
-
`max_scrolls` times (10), then fails. Change the direction with `direction: 'up'`. An element that appears late
|
|
370
|
-
near the top can be scrolled past, so first wait for something on the screen, for example with `find_element`.
|
|
371
|
-
- `tap` with a block and no locator (`page.tap { |p| … }`) is still Ruby's own `tap`, which passes the object to the
|
|
372
|
-
block.
|
|
373
|
-
- `hide_keyboard`, `back` and the other Appium commands are on `driver`, and do not wait.
|
|
374
|
-
<%- end -%>
|
|
317
|
+
`by_role(role, name:, exact:, include_hidden:, level:)` takes these options:
|
|
375
318
|
|
|
376
|
-
`
|
|
377
|
-
|
|
319
|
+
- `name:` is the accessible name, matched as a part of the text. `exact: true` matches all of it.
|
|
320
|
+
- `include_hidden: true` also finds elements that are not visible (by default they are skipped).
|
|
321
|
+
- `level:` picks a heading by its level, 1 to 6.
|
|
378
322
|
|
|
379
323
|
```ruby
|
|
380
|
-
|
|
381
|
-
find_element(
|
|
382
|
-
find_element(
|
|
383
|
-
<%- else -%>
|
|
384
|
-
find_element(css: '.form').find_element(id: 'email').send_keys('me@example.com')
|
|
385
|
-
<%- end -%>
|
|
324
|
+
driver.find_element(by_role('button', name: 'Save', exact: true))
|
|
325
|
+
driver.find_element(by_role('heading', level: 1))
|
|
326
|
+
driver.find_element(by_role('dialog', include_hidden: true))
|
|
386
327
|
```
|
|
387
328
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
329
|
+
An unknown role raises an `ArgumentError`. The roles are the ARIA role names, such as `button`, `link` and `textbox`.
|
|
330
|
+
<%- end -%>
|
|
331
|
+
|
|
332
|
+
**Strict.** `find_element` fails with `Raider::AmbiguousMatch` when the locator matches more than one element. The message
|
|
333
|
+
lists the first three, so you can narrow the locator. When you want every match, use `driver.find_elements`: it never fails
|
|
334
|
+
on many matches. <%= mobile? ? 'Strict is off on native app screens, where `find_element` returns the first match.' : '`raider u strict off` turns strict off for the project.' %>
|
|
391
335
|
|
|
392
|
-
|
|
393
|
-
|
|
336
|
+
<%- if mobile? -%>
|
|
337
|
+
### Waiting
|
|
338
|
+
|
|
339
|
+
Apps load at their own speed, so the runtime waits for you. It checks every 0.1 seconds, for up to `timeout` seconds (10).
|
|
340
|
+
|
|
341
|
+
- `find_element` and `find_elements` wait for a match. `find_element` fails with a message such as
|
|
342
|
+
`no such element: { id: "x" } (waited 10s)`. `find_elements` returns `[]` at the deadline.
|
|
343
|
+
- On a native app, `click`, `send_keys` and `clear` wait until the element is displayed and enabled. `submit` is not
|
|
344
|
+
checked, and strict is off.
|
|
345
|
+
- `force: true` skips the wait before an action, for example `first_product.click(force: true)`. The element must still
|
|
346
|
+
exist.
|
|
347
|
+
- If the element is not ready in time, the action raises `Raider::NotActionable`, which names the check that failed and how
|
|
348
|
+
long it waited. On XCUITest, an element that the app reports as not displayed waits the whole `timeout` and then raises
|
|
349
|
+
it. If you know the element works, use `force: true` for that call, or `Raider.actionability = false` for the run.
|
|
350
|
+
- `timeout`, `strict` and `actionability` are set under [Settings](#settings) and in the environment:
|
|
351
|
+
`RAIDER_TIMEOUT=20`, `RAIDER_STRICT=0` and `RAIDER_ACTIONABILITY=0`. The first action in a run prints one line,
|
|
352
|
+
`[raider] actionability on`; `RAIDER_QUIET=1` silences it.
|
|
353
|
+
- Set no implicit wait on the driver. It makes the checks that something is **not** on the screen wait too.
|
|
354
|
+
<%- else -%>
|
|
355
|
+
### Waiting and actionability
|
|
356
|
+
|
|
357
|
+
Pages load at their own speed, so the runtime waits for you. It checks every 0.1 seconds, for up to `timeout` seconds (10).
|
|
358
|
+
|
|
359
|
+
- `find_element` and `find_elements` wait for a match. `find_element` fails with a message such as
|
|
360
|
+
`no such element: { id: "x" } (waited 10s)`. `find_elements` returns `[]` at the deadline.
|
|
361
|
+
- Before an action, the runtime waits until the element is ready:
|
|
362
|
+
|
|
363
|
+
| Action | Waits until the element is |
|
|
364
|
+
|--------|----------------------------|
|
|
365
|
+
| `click` | visible, enabled, not inert, stable, receives events |
|
|
366
|
+
| `send_keys` | visible, enabled, editable, not inert |
|
|
367
|
+
| `clear` | visible, enabled, editable, not inert |
|
|
368
|
+
| `submit` | visible, enabled, not inert |
|
|
369
|
+
|
|
370
|
+
- "Stable" means the element was in the same place on two checks in a row, so it is not in the middle of an animation.
|
|
371
|
+
"Receives events" means nothing else covers the point where the click lands. "Inert" means the element sits inside an
|
|
372
|
+
`inert` part of the page. "Editable" means the field is not read-only.
|
|
373
|
+
- If the element is not ready in time, the action raises `Raider::NotActionable`, which names the check that failed and how
|
|
374
|
+
long it waited. If the locator matches several elements, `Raider::AmbiguousMatch` lists them.
|
|
375
|
+
- `force: true` skips the checks for one call: `login_button.click(force: true)`. The element must still be on the page.
|
|
376
|
+
- Third-party code that calls `find_elements` for an optional lookup waits `timeout` too, unless you wrap it in
|
|
377
|
+
`Raider::Wait.immediately { }`. Examples are axe-core's frame scan and the option lookup of
|
|
378
|
+
`Selenium::WebDriver::Support::Select`, so `select_by` waits `timeout` before it raises when the option never shows up.
|
|
379
|
+
- Set no implicit wait on the driver. It makes the checks that something is **not**
|
|
380
|
+
on the page wait too.
|
|
381
|
+
- `timeout`, `strict` and `actionability` are set under [Settings](#settings) and in the environment:
|
|
382
|
+
`RAIDER_TIMEOUT=20`, `RAIDER_STRICT=0` and `RAIDER_ACTIONABILITY=0`. `raider u strict off` and
|
|
383
|
+
`raider u actionability off` write them to the file. `Raider.actionability = false` turns the checks off for the run. The first action in a run prints one line, `[raider] actionability on`; `RAIDER_QUIET=1` silences it.
|
|
384
|
+
<%- end -%>
|
|
385
|
+
|
|
386
|
+
### Assertions
|
|
387
|
+
|
|
388
|
+
The <%= mobile? ? 'screen' : 'page' %> changes after an action, so a check right after it can run too early. Raider's matchers retry until
|
|
389
|
+
the <%= mobile? ? 'screen' : 'page' %> matches or `timeout` runs out. Write them in a block, so the lookup runs again on every try:
|
|
394
390
|
|
|
395
391
|
```ruby
|
|
396
392
|
<%- if mobile? -%>
|
|
397
|
-
|
|
393
|
+
expect { pdp_page.add_to_cart_button }.to have_text('Add to Cart')
|
|
398
394
|
<%- else -%>
|
|
399
|
-
|
|
400
|
-
Selenium::WebDriver::Support::Select.new(find_element(id: 'color').native)
|
|
401
|
-
driver.action.move_to(find_element(id: 'menu').native).perform
|
|
402
|
-
driver.execute_script('arguments[0].click()', find_element(id: 'go').native)
|
|
395
|
+
expect { account_page.welcome_message }.to have_text("Welcome back #{user['name']}")
|
|
403
396
|
<%- end -%>
|
|
404
397
|
```
|
|
405
|
-
<%- if mobile? -%>
|
|
406
398
|
|
|
407
|
-
|
|
408
|
-
|
|
399
|
+
A page-object method like `<%= mobile? ? 'add_to_cart_button' : 'welcome_message' %>` finds the element each time it is called. The block calls it on every try, so an element that was
|
|
400
|
+
redrawn is found again. `expect(<%= mobile? ? 'pdp_page.add_to_cart_button' : 'account_page.welcome_message' %>)` without a block finds it once.
|
|
409
401
|
|
|
410
|
-
|
|
402
|
+
The shape is the same for every matcher: `expect { <%= mobile? ? 'pdp_page.add_to_cart_button' : 'account_page.welcome_message' %> }.to have_text(…)`, with another matcher in place of `have_text`.
|
|
411
403
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
404
|
+
| Matcher | Passes when |
|
|
405
|
+
|---------|-------------|
|
|
406
|
+
| `have_text('Save')` | the text contains it (`exact: true` for all of it); a Regexp works too |
|
|
407
|
+
| `have_value('Ann')` | the field's `value` equals it |
|
|
408
|
+
| `have_attribute('type', 'text')` | the attribute equals it; with no value, the attribute is present |
|
|
409
|
+
| `be_visible`, `be_hidden` | the element is displayed, or is not displayed or not there |
|
|
410
|
+
| `be_enabled`, `be_disabled` | the element is enabled, or is not |
|
|
411
|
+
| `be_checked` | the checkbox or radio is selected |
|
|
412
|
+
| `have_count(3)` | a list has that many items: `expect { driver.find_elements(<%= mobile? ? "accessibility_id: 'row'" : "css: 'li'" %>) }.to have_count(3)` |
|
|
413
|
+
<%- if web? -%>
|
|
414
|
+
| `have_title('Done')` | the page title equals it (or matches a Regexp) |
|
|
415
|
+
| `have_url(/#done$/)` | the whole current URL equals it (or matches a Regexp) |
|
|
416
|
+
<%- end -%>
|
|
417
|
+
| `have_element(id: 'late-p')` | `driver` can find the element; <%= mobile? ? '`have_css` and `have_xpath` are shortcuts for web pages' : "`have_css('li')` and `have_xpath('//li')` are shortcuts" %> |
|
|
415
418
|
|
|
416
|
-
|
|
417
|
-
|
|
419
|
+
Pass `wait:` to change the time for one matcher, in seconds: `have_text('Saved', wait: 5)`. A negative check passes as soon as
|
|
420
|
+
the element is gone, so `expect(driver).not_to have_element(id: 'gone')` does not wait for `timeout`.
|
|
421
|
+
|
|
422
|
+
The block form works with any value matcher. `expect { value }.to eq(...)` retries until the value matches or `timeout` runs
|
|
423
|
+
out:
|
|
418
424
|
|
|
419
425
|
```ruby
|
|
420
426
|
<%- if mobile? -%>
|
|
421
|
-
expect
|
|
427
|
+
expect { pdp_page.add_to_cart_button.text }.to eq('Add to Cart')
|
|
422
428
|
<%- else -%>
|
|
423
|
-
expect(
|
|
429
|
+
expect { driver.find_element(id: 'status').text }.to eq('answered by /api')
|
|
424
430
|
<%- end -%>
|
|
425
431
|
```
|
|
426
432
|
|
|
427
|
-
|
|
433
|
+
- The failure message is RSpec's own for the last value, with the time waited in front of it, such as `(waited 10s)`. If the last
|
|
434
|
+
try raised a lookup error, such as no such element, that error is reported instead. Inside `aggregate_failures` the
|
|
435
|
+
`(waited Ns)` text is left out.
|
|
436
|
+
- RSpec's block matchers (`raise_error`, `change`, `output`, `throw_symbol` and `yield_*`) work as in RSpec: the block runs
|
|
437
|
+
once and does not retry.
|
|
438
|
+
- `expect(value)` without a block does not retry. The value is read once, so a plain string or number that is already
|
|
439
|
+
in hand cannot change.
|
|
440
|
+
- A block with side effects runs once per poll, so keep side effects out of it. Click first, then check.
|
|
441
|
+
<%- if mobile? -%>
|
|
442
|
+
|
|
443
|
+
### Gestures
|
|
444
|
+
|
|
445
|
+
`helpers/gesture_helper.rb` holds `swipe`, `scroll` and `scroll_to`. Every page includes it. They use the platform's own
|
|
446
|
+
`mobile:` commands, so they work on iOS and Android, and they do not wait.
|
|
428
447
|
|
|
429
448
|
```ruby
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
eventually { expect(account_page.header.customer_name).to eq "Welcome back #{user['name']}" }
|
|
434
|
-
<%- end -%>
|
|
449
|
+
swipe('left', element)
|
|
450
|
+
scroll('down')
|
|
451
|
+
scroll_to({ accessibility_id: 'footer' }).click
|
|
435
452
|
```
|
|
436
453
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
`
|
|
440
|
-
|
|
441
|
-
|
|
454
|
+
- `swipe(direction, element = nil)` and `scroll(direction, element = nil)` take `'up'`, `'down'`, `'left'` or `'right'`. With
|
|
455
|
+
an element they act on it; without one, on the whole screen.
|
|
456
|
+
- `scroll_to(locator)` looks for the element without waiting, scrolls the screen and looks again, up to `max_scrolls`
|
|
457
|
+
times (10), then fails. Change the direction with `direction: 'up'`. It returns the element, so `scroll_to(...).click`
|
|
458
|
+
works. An element that appears late near the top can be scrolled past, so first wait for something on the screen.
|
|
459
|
+
- `hide_keyboard`, `back` and the other Appium commands are on `driver`, and do not wait.
|
|
460
|
+
<%- end -%>
|
|
461
|
+
|
|
442
462
|
|
|
443
463
|
### Running the tests
|
|
444
464
|
|
|
@@ -449,13 +469,8 @@ Install the gems once with `bundle install`. Then:
|
|
|
449
469
|
<%- end -%>
|
|
450
470
|
|
|
451
471
|
```bash
|
|
452
|
-
<%- if rspec? -%>
|
|
453
472
|
bundle exec rspec # every test
|
|
454
473
|
bundle exec rspec spec/<%= examples? ? (mobile? ? 'pdp_page_spec.rb' : 'login_page_spec.rb') : 'checkout_page_spec.rb' %> # one file
|
|
455
|
-
<%- else -%>
|
|
456
|
-
bundle exec cucumber # every scenario
|
|
457
|
-
bundle exec cucumber features/<%= examples? ? (mobile? ? 'home.feature' : 'login.feature') : 'checkout.feature' %> # one feature
|
|
458
|
-
<%- end -%>
|
|
459
474
|
<%- unless mobile? -%>
|
|
460
475
|
HEADLESS=true bundle exec <%= test_cmd %> # without a browser window
|
|
461
476
|
<%- end -%>
|
|
@@ -465,16 +480,11 @@ HEADLESS=true bundle exec <%= test_cmd %> # without a browser
|
|
|
465
480
|
- To run tests in parallel, each process needs its own device or simulator. This project does not set that up, so
|
|
466
481
|
run the tests one at a time unless you add it.
|
|
467
482
|
<%- else -%>
|
|
468
|
-
- In parallel, with the `parallel_tests` gem in the Gemfile: `bundle exec
|
|
483
|
+
- In parallel, with the `parallel_tests` gem in the Gemfile: `bundle exec parallel_rspec spec/`. Each process opens its own
|
|
469
484
|
browser.
|
|
470
485
|
<%- end -%>
|
|
471
|
-
<%- if rspec? -%>
|
|
472
486
|
- `RETRY_COUNT=2 bundle exec rspec` runs each test up to 2 times, so a failing test gets one more try before it
|
|
473
487
|
counts as failed.
|
|
474
|
-
<%- else -%>
|
|
475
|
-
- To run a failing scenario again before it counts as failed, change `--retry 0` in `cucumber.yml` to, for
|
|
476
|
-
example, `--retry 2`.
|
|
477
|
-
<%- end -%>
|
|
478
488
|
|
|
479
489
|
### Running in CI
|
|
480
490
|
|
|
@@ -507,43 +517,154 @@ To log in once and reuse the session across tests, see [Reusing the login](#reus
|
|
|
507
517
|
The tests connect to it at `http://127.0.0.1:4723`, Appium's default address.
|
|
508
518
|
<%- end -%>
|
|
509
519
|
|
|
510
|
-
|
|
520
|
+
<%- if network_helper? -%>
|
|
521
|
+
## Mocking network requests
|
|
511
522
|
|
|
512
|
-
|
|
523
|
+
Sometimes a test should not depend on a real server: you want an empty list, an error page, or no images at all.
|
|
524
|
+
`NetworkHelper` lets a test answer a request with your own response, or block it, before it leaves the browser.
|
|
525
|
+
It is available in every test and in the page objects, and it works on Chrome, Edge and Firefox. Safari raises
|
|
526
|
+
`NetworkHelper::Unavailable`, and so does a run with `BIDI=off` or a grid that cannot carry WebDriver BiDi.
|
|
513
527
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
528
|
+
The methods:
|
|
529
|
+
|
|
530
|
+
- `mock_request(url, ...)` answers matching requests with your own response. It takes `status:` (default 200, any
|
|
531
|
+
number from 100 to 599), `json:` or `body:` (not both), `headers:`, `content_type:` and `method:` (for example
|
|
532
|
+
`method: 'POST'`). It returns a mock, so you can look at it later.
|
|
533
|
+
- `block_request(url)` makes matching requests fail with a network error, as if the server were unreachable.
|
|
534
|
+
- `remove_mock(mock)` stops one mock. `clear_mocks` stops all of them.
|
|
535
|
+
- `mock.calls` is the list of requests the mock handled, oldest first, and `mock.called?` says if there was one. Each
|
|
536
|
+
request has `method`, `url` and `headers` (header names are lowercase).
|
|
537
|
+
|
|
538
|
+
Set a mock up before the page makes the request, usually before you visit the page:
|
|
539
|
+
|
|
540
|
+
```ruby
|
|
541
|
+
# An empty list instead of the real users
|
|
542
|
+
mock_request('**/api/users', json: { users: [] })
|
|
543
|
+
|
|
544
|
+
# A server error, to see how the page shows it
|
|
545
|
+
mock_request('**/api/users', status: 500, json: { error: 'down' })
|
|
546
|
+
|
|
547
|
+
# No images, so the page loads faster
|
|
548
|
+
block_request('**/*.png')
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
To check that the page really made the request, keep the mock and look at its calls. Put the check in a block, because
|
|
552
|
+
the request may come a moment after the click, and the block is tried again until it passes:
|
|
553
|
+
|
|
554
|
+
```ruby
|
|
555
|
+
users = mock_request('**/api/users', json: { users: [] })
|
|
556
|
+
# ...open the page that loads the users...
|
|
557
|
+
expect { users.called? }.to be(true)
|
|
558
|
+
expect { users.calls.first&.method }.to eq('GET')
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
**Matching.** A URL is a glob that is matched against the whole URL, including the query string. `**` stands for any
|
|
562
|
+
characters, `*` for any characters except `/`, and everything else is literal. So `'**/api/users'` matches
|
|
563
|
+
`https://shop.test/api/users`, but not `https://shop.test/api/users?page=2`: write `'**/api/users?*'` for that. You can
|
|
564
|
+
also pass a Regexp. When several mocks match, the one you added last wins. Pass `method:` to match only one HTTP method.
|
|
565
|
+
|
|
566
|
+
**Name the host when you can.** A glob that starts with `scheme://host`, such as `'https://shop.test/api/users'`, tells
|
|
567
|
+
the browser which requests to pause. A Regexp, or a glob that starts with `**` or has no host, makes the browser pause
|
|
568
|
+
every request while the mock is registered, and the helper lets the ones that do not match go on. That works, but it
|
|
569
|
+
is slower, so prefer `'https://shop.test/api/users'` to `'**/api/users'` on pages that load a lot.
|
|
570
|
+
|
|
571
|
+
**CORS.** When the page and the mocked URL are on different origins, the helper answers the browser's preflight
|
|
572
|
+
request and adds the CORS headers for you, unless your mock sets them itself.
|
|
573
|
+
|
|
574
|
+
**Deciding per request.** Give `mock_request` a block to choose the answer for each request. The block gets the
|
|
575
|
+
request and returns a Hash with the same options as above (`status:`, `json:`, `body:`, `headers:`, `content_type:`),
|
|
576
|
+
`:fail` to make the request fail, or `:continue` to let it go on to the real server. To let it go on with a changed
|
|
577
|
+
header, return `{ continue: { headers: { 'x-test' => '1' } } }`.
|
|
578
|
+
|
|
579
|
+
```ruby
|
|
580
|
+
mock_request('https://shop.test/api/users', method: 'POST') do |request|
|
|
581
|
+
request.headers['authorization'] ? { status: 201, json: { id: 1 } } : { status: 401 }
|
|
582
|
+
end
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
The block runs on a background thread while the browser waits, so keep it quick and do not call the driver (or any
|
|
586
|
+
page method) inside it.
|
|
587
|
+
|
|
588
|
+
**Cleanup and errors.** Mocks are cleared after each test, so one test never affects the next. If a block raises, or
|
|
589
|
+
returns something that is not a response, the request fails and the test fails with `NetworkHelper::MockError`. A bad
|
|
590
|
+
`status:` or a pattern that is neither a String nor a Regexp raises right where you call `mock_request`.
|
|
591
|
+
|
|
592
|
+
A mocked request shows up as "Mocked" in the [Trace replay](#trace-replay), so you can see which requests the test
|
|
593
|
+
answered itself.
|
|
594
|
+
|
|
595
|
+
### Removing the network helper
|
|
596
|
+
|
|
597
|
+
The network helper is ordinary code in your project, so you can take it out. To do that:
|
|
598
|
+
|
|
599
|
+
1. Delete `helpers/network_helper.rb`.
|
|
600
|
+
2. Remove `include NetworkHelper` from `page_objects/abstract/page.rb`, `config.include(NetworkHelper)` from `helpers/spec_helper.rb`, and the
|
|
601
|
+
`require_relative` lines that load `network_helper`.
|
|
602
|
+
3. In the after hook (`helpers/spec_helper.rb`), remove the `NetworkHelper.finish` line and `raise mock_error`.
|
|
603
|
+
4. In `helpers/driver_helper.rb`, remove the `NetworkHelper` half of the `bidi` condition, so it reads
|
|
604
|
+
`bidi = defined?(TraceHelper) && TraceHelper.bidi?(load_browser)`.
|
|
605
|
+
5. Delete any `mock_request` and `block_request` calls in your tests.
|
|
537
606
|
|
|
538
|
-
Note that without WaitHelper a trace records only navigation and the failure (see [Trace replay](#trace-replay)).
|
|
539
607
|
<%- end -%>
|
|
608
|
+
<%- if screenshot_helper? -%>
|
|
609
|
+
## Screenshot comparison
|
|
610
|
+
|
|
611
|
+
Use it when "does the page still look right?" is the question: a layout, a chart, a component. `match_screenshot` takes
|
|
612
|
+
a screenshot of the page and compares it with a picture you approved earlier (the baseline). If they differ, the test fails.
|
|
613
|
+
|
|
614
|
+
```ruby
|
|
615
|
+
match_screenshot('home') # the viewport
|
|
616
|
+
match_screenshot('cart/summary', element: cart_summary) # one element
|
|
617
|
+
match_screenshot('home', max_diff_pixels: 50, timeout: 5) # allow a few pixels, wait up to 5 s
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
A name is letters, digits, `-` and `_`, with `/` for folders, up to 100 characters, and can be used once per test.
|
|
621
|
+
|
|
622
|
+
**Where baselines live.** `screenshots/baselines/<browser>/<name>.png`, for example
|
|
623
|
+
`screenshots/baselines/chrome/home.png`. Commit them: they are part of your tests. Each browser has its own folder.
|
|
624
|
+
|
|
625
|
+
**First run.** With no baseline, the call saves the screenshot as the baseline and passes. Look at the new file, and
|
|
626
|
+
commit it if it is what you want. On CI (when `CI` is set or the reporter detects a CI system) a missing baseline fails the test
|
|
627
|
+
and writes nothing, so a forgotten file never goes unnoticed.
|
|
628
|
+
|
|
629
|
+
**After a change you wanted.** Run `SCREENSHOTS=update bundle exec rspec`. Every comparison that differs
|
|
630
|
+
rewrites its baseline and passes. Review the changed PNGs in your diff before you commit them.
|
|
631
|
+
|
|
632
|
+
**When it fails.** The failure message names the baseline and says how many pixels differ. The report gets three pictures,
|
|
633
|
+
`comparison:<name>:expected`, `:actual` and `:diff` (differences in red), and the trace has a "match_screenshot" step that shows the actual screenshot. The
|
|
634
|
+
comparison retries until it matches or `timeout:` (the wait timeout by default) runs out, so a page that is still settling does not fail it.
|
|
635
|
+
|
|
636
|
+
**Tolerances.** Set them for the project in `config/config.yml` under `screenshots:`, or for one call as arguments:
|
|
637
|
+
|
|
638
|
+
- `threshold` (default 0.2): how different a pixel must be to count, from 0 (exact) to 1.
|
|
639
|
+
- `max_diff_pixels` (default 0): how many pixels may differ.
|
|
640
|
+
- `max_diff_ratio` (default none): or the share of all pixels that may differ, for example `0.001`.
|
|
641
|
+
|
|
642
|
+
Anti-aliasing is not ignored (unlike Playwright's default), so for text-heavy areas raise `threshold` or `max_diff_pixels`.
|
|
643
|
+
|
|
644
|
+
A comparison passes when the differing pixels are within `max_diff_pixels` or `max_diff_ratio`. A picture with a different
|
|
645
|
+
size always fails, and the message gives both sizes.
|
|
646
|
+
|
|
647
|
+
**Generate baselines where CI runs.** Fonts and rendering differ between machines, so a baseline made on your laptop may not match CI.
|
|
648
|
+
A Retina screen is the usual case: it makes 2x pictures, and a 1x CI machine fails with a size change that names the
|
|
649
|
+
device pixel ratio. Create the baselines in the same environment that checks them (run the suite once with `SCREENSHOTS=update` there and commit the files).
|
|
650
|
+
|
|
651
|
+
### Removing screenshot comparison
|
|
652
|
+
|
|
653
|
+
The helper is ordinary code in your project, so you can take it out. To do that:
|
|
654
|
+
|
|
655
|
+
1. Delete `helpers/screenshot_helper.rb` and the `screenshots/` folder.
|
|
656
|
+
2. Remove `include ScreenshotHelper` from `page_objects/abstract/page.rb`, `config.include(ScreenshotHelper)` from `helpers/spec_helper.rb`, and the
|
|
657
|
+
`require_relative` lines that load `screenshot_helper`.
|
|
658
|
+
3. Remove the `ScreenshotHelper.reset` line from the before hook (`helpers/spec_helper.rb`).
|
|
659
|
+
4. Remove the `screenshots:` block from `config/config.yml` and `gem 'chunky_png'` from the `Gemfile`.
|
|
660
|
+
5. Delete any `match_screenshot` calls in your tests.
|
|
540
661
|
|
|
541
662
|
<%- end -%>
|
|
542
663
|
<%- if web? -%>
|
|
543
664
|
## Reusing the login
|
|
544
665
|
|
|
545
666
|
<%- if examples? -%>
|
|
546
|
-
Tag an example `:logged_in`
|
|
667
|
+
Tag an example `:logged_in` and the suite logs in once, saves the
|
|
547
668
|
session, and restores it for the next tagged example or run instead of filling in the login form again. The generated
|
|
548
669
|
account example shows it. If the saved session no longer works, the real login runs again and the state is saved anew.
|
|
549
670
|
<%- else -%>
|
|
@@ -552,21 +673,14 @@ filling in the login form again. If the saved session no longer works, the real
|
|
|
552
673
|
saved anew.
|
|
553
674
|
|
|
554
675
|
This project has no login page yet. Once you have one (`raider g page login`), add a hook so that every
|
|
555
|
-
|
|
556
|
-
the other `require_relative` lines, then add the hook
|
|
676
|
+
example tagged `:logged_in` starts logged in. In `helpers/spec_helper.rb`, require your pages next to
|
|
677
|
+
the other `require_relative` lines, then add the hook inside `RSpec.configure`:
|
|
557
678
|
|
|
558
679
|
```ruby
|
|
559
|
-
<%- if rspec? -%>
|
|
560
680
|
require_relative '../page_objects/pages/login'
|
|
561
681
|
require_relative '../page_objects/pages/dashboard'
|
|
562
682
|
|
|
563
683
|
config.before(:each, :logged_in) do
|
|
564
|
-
<%- else -%>
|
|
565
|
-
require_relative '../../page_objects/pages/login'
|
|
566
|
-
require_relative '../../page_objects/pages/dashboard'
|
|
567
|
-
|
|
568
|
-
Before('@logged_in') do
|
|
569
|
-
<%- end -%>
|
|
570
684
|
dashboard = Dashboard.new(driver)
|
|
571
685
|
SessionState.new(driver).reuse(valid: -> { dashboard.tap(&:visit).logged_in? }) do
|
|
572
686
|
login = Login.new(driver)
|
|
@@ -588,8 +702,6 @@ SessionState.new(driver, name: 'admin').reuse(valid: -> { dashboard.tap(&:visit)
|
|
|
588
702
|
end
|
|
589
703
|
```
|
|
590
704
|
|
|
591
|
-
With Watir, pass `browser.driver`, the Selenium driver underneath the browser.
|
|
592
|
-
|
|
593
705
|
- `login_state: max_age:` in `config/config.yml` is how many seconds a saved login stays valid (default 3600).
|
|
594
706
|
- `RAIDER_LOGIN_STATE=off` always does the real login and saves nothing; `RAIDER_LOGIN_STATE=refresh` ignores the saved
|
|
595
707
|
file, logs in and saves again.
|
|
@@ -599,14 +711,8 @@ With Watir, pass `browser.driver`, the Selenium driver underneath the browser.
|
|
|
599
711
|
<%- end -%>
|
|
600
712
|
## Reporting
|
|
601
713
|
|
|
602
|
-
<%- if rspec? -%>
|
|
603
714
|
Every run writes machine-readable results to `raider-results/` (including `run.json`) and a browsable report,
|
|
604
715
|
`raider-results/index.html`. Open it in a browser after a run; `bundle exec raider-reporter report` rebuilds it.
|
|
605
|
-
<%- else -%>
|
|
606
|
-
Every run writes machine-readable results to `raider-results/` (including `run.json`) and a browsable report,
|
|
607
|
-
`raider-results/index.html`. The reporter runs in plugin mode:
|
|
608
|
-
`features/support/env.rb` requires `raider_reporter/cucumber`, so no extra Cucumber formatter flag is needed.
|
|
609
|
-
<%- end -%>
|
|
610
716
|
|
|
611
717
|
To upload runs to Raider, set these environment variables before running the suite.
|
|
612
718
|
`RAIDER_TOKEN` and `RAIDER_ENDPOINT` come from your Raider Hideout server (its setup page, or `bin/rails hideout:setup` /
|
|
@@ -617,17 +723,12 @@ export RAIDER_TOKEN=your-token
|
|
|
617
723
|
export RAIDER_ENDPOINT=https://hideout.example.com
|
|
618
724
|
```
|
|
619
725
|
|
|
620
|
-
<%- if rspec? -%>
|
|
621
726
|
Without `RAIDER_TOKEN`, nothing is uploaded and the suite output is unchanged. Failure screenshots are attached to the
|
|
622
727
|
report and to Raider. See the [raider_reporter README](https://github.com/RaiderHQ/raider_reporter) for details.
|
|
623
|
-
<%- else -%>
|
|
624
|
-
Without `RAIDER_TOKEN`, nothing is uploaded and the suite output is unchanged. Failure screenshots are attached to the
|
|
625
|
-
report and to Raider. See the [raider_reporter README](https://github.com/RaiderHQ/raider_reporter) for details.
|
|
626
|
-
<%- end -%>
|
|
627
728
|
|
|
628
729
|
### Video replay
|
|
629
730
|
|
|
630
|
-
Every test is recorded, and the video is kept only for failing tests
|
|
731
|
+
Every test is recorded, and the video is kept only for failing tests (on the last retry attempt). It is attached
|
|
631
732
|
to the report as `video` and saved under `raider-results/attachments/`, next to the failure screenshot.
|
|
632
733
|
<%- if mobile? -%>
|
|
633
734
|
Appium records the device screen and the video is attached as `video.mp4`.
|
|
@@ -636,12 +737,12 @@ The video is a `video.webm` built from page screenshots with ffmpeg.
|
|
|
636
737
|
<%- end -%>
|
|
637
738
|
|
|
638
739
|
<%- if single_platform? -%>
|
|
639
|
-
- Turn video off for one run with `VIDEO=off bundle exec
|
|
740
|
+
- Turn video off for one run with `VIDEO=off bundle exec rspec`, or for the project with
|
|
640
741
|
`raider u video off`, which creates `config/config.yml` with a `video:` section (`raider u video on` turns it back
|
|
641
742
|
on).
|
|
642
743
|
<%- else -%>
|
|
643
744
|
- Video is on by default: `video:` in `config/config.yml` (`enabled: true`, `fps: 5`, `max_width: 1280`).
|
|
644
|
-
- Turn it off for one run with `VIDEO=off bundle exec
|
|
745
|
+
- Turn it off for one run with `VIDEO=off bundle exec rspec`, or for the project with
|
|
645
746
|
`raider u video off` (`raider u video on` turns it back on).
|
|
646
747
|
<%- end -%>
|
|
647
748
|
<%- unless mobile? -%>
|
|
@@ -684,13 +785,19 @@ The video is a `video.webm` built from page screenshots with ffmpeg.
|
|
|
684
785
|
<%- if trace_helper? -%>
|
|
685
786
|
### Trace replay
|
|
686
787
|
|
|
687
|
-
Every test records a trace, and the trace is kept only for failing tests
|
|
788
|
+
Every test records a trace, and the trace is kept only for failing tests (on the last retry attempt). A trace is a step-by-step
|
|
688
789
|
record of the test: each action and page load, with the locator, how long it waited, a screenshot and the page HTML
|
|
689
790
|
after it, the network requests, and the browser console. It is saved as `raider-results/attachments/<n>-trace.zip`,
|
|
690
791
|
next to the failure screenshot and video, and attached to the report as `trace`.
|
|
691
792
|
|
|
692
|
-
|
|
693
|
-
|
|
793
|
+
The trace records `click`, `send_keys`, `clear` and `submit` with their locators (`by_role` as
|
|
794
|
+
`button, name: Save`), and the time each one waited. Chained finds show the element they search in. Reads such as
|
|
795
|
+
`text`, `attribute` and `displayed?`, `find_element`, `hover`, `drag_and_drop` and matcher assertions are not steps.
|
|
796
|
+
|
|
797
|
+
- Keep the trace of passing tests too with `TRACE=on bundle exec rspec`. Capture nothing for one run with
|
|
798
|
+
`TRACE=off bundle exec rspec`, or for the project with `raider u trace off` (`raider u trace on` turns it back on).
|
|
799
|
+
`TRACE=off` turns tracing off but BiDi stays on, so [network mocking](#mocking-network-requests) still works;
|
|
800
|
+
`BIDI=off` turns BiDi off, which also drops the network and console entries from a trace.
|
|
694
801
|
`raider u trace on` and `raider u trace off` write `enabled:` under `trace:` in `config/config.yml`.
|
|
695
802
|
- Open a trace in Raider Desktop, or from the test's page on Hideout when the run is uploaded.
|
|
696
803
|
- Cost: a trace takes one screenshot and one page HTML for every step that changes the page (a click, typing,
|
|
@@ -714,8 +821,8 @@ can contain typed text too, because an assertion message is kept as it is. Reque
|
|
|
714
821
|
response bodies are never captured. Do not share or upload traces from runs that handle real credentials.
|
|
715
822
|
|
|
716
823
|
**Removing the trace helper.** The trace helper is ordinary code in your project. Delete `helpers/trace_helper.rb` and
|
|
717
|
-
the `require_relative` lines that load `trace_helper`: in
|
|
718
|
-
(in
|
|
824
|
+
the `require_relative` lines that load `trace_helper`: in `helpers/driver_helper.rb`. The other lines that use it
|
|
825
|
+
(in `helpers/spec_helper.rb` and the driver helper's listeners) only run when `TraceHelper` is defined,
|
|
719
826
|
so they can stay. Remove the `trace:` block from `config/config.yml` if you like.
|
|
720
827
|
|
|
721
828
|
<%- end -%>
|