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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/reek.yml +5 -1
  3. data/.github/workflows/release.yml +2 -1
  4. data/.github/workflows/system_tests.yml +1 -1
  5. data/.reek.yml +13 -0
  6. data/README.md +44 -4
  7. data/lib/commands/utility_commands.rb +1 -1
  8. data/lib/generators/automation/automation_generator.rb +15 -10
  9. data/lib/generators/automation/templates/appium_caps.tt +2 -4
  10. data/lib/generators/automation/templates/home.tt +1 -2
  11. data/lib/generators/automation/templates/login.tt +6 -6
  12. data/lib/generators/automation/templates/page.tt +10 -3
  13. data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -6
  14. data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -6
  15. data/lib/generators/automation/templates/pdp.tt +3 -2
  16. data/lib/generators/cucumber/cucumber_generator.rb +15 -2
  17. data/lib/generators/cucumber/templates/feature.tt +5 -0
  18. data/lib/generators/cucumber/templates/partials/driver_world.tt +9 -0
  19. data/lib/generators/cucumber/templates/partials/mobile_steps.tt +1 -1
  20. data/lib/generators/cucumber/templates/partials/selenium_env.tt +20 -0
  21. data/lib/generators/cucumber/templates/partials/watir_env.tt +16 -0
  22. data/lib/generators/cucumber/templates/partials/web_steps.tt +8 -0
  23. data/lib/generators/generator.rb +15 -4
  24. data/lib/generators/helper_generator.rb +15 -0
  25. data/lib/generators/invoke_generators.rb +23 -0
  26. data/lib/generators/menu_generator.rb +26 -14
  27. data/lib/generators/rspec/rspec_generator.rb +10 -3
  28. data/lib/generators/rspec/templates/spec.tt +27 -1
  29. data/lib/generators/template_renderer/template_error.rb +8 -8
  30. data/lib/generators/templates/common/git_ignore.tt +4 -1
  31. data/lib/generators/templates/common/partials/mobile_config.tt +1 -0
  32. data/lib/generators/templates/common/partials/web_config.tt +5 -2
  33. data/lib/generators/templates/common/read_me.tt +508 -0
  34. data/lib/generators/templates/helpers/partials/selenium_driver.tt +1 -3
  35. data/lib/generators/templates/helpers/raider_element.tt +159 -0
  36. data/lib/generators/templates/helpers/session_state.tt +246 -0
  37. data/lib/generators/templates/helpers/spec_helper.tt +28 -0
  38. data/lib/generators/templates/helpers/wait_helper.tt +470 -0
  39. data/lib/ruby_raider/ffmpeg.rb +7 -7
  40. data/lib/ruby_raider.rb +2 -0
  41. data/lib/scaffolding/project_detector.rb +6 -0
  42. data/lib/scaffolding/scaffolding.rb +4 -0
  43. data/lib/scaffolding/templates/component.tt +4 -0
  44. data/lib/scaffolding/templates/page_object.tt +8 -0
  45. data/lib/version +1 -1
  46. data/ruby_raider.gemspec +1 -0
  47. data/sig/generators/generator.rbs +2 -0
  48. data/sig/scaffolding/project_detector.rbs +1 -0
  49. data/sig/scaffolding/scaffolding.rbs +1 -0
  50. data/spec/commands/raider_commands_spec.rb +24 -0
  51. data/spec/fixtures/watir_master/cucumber_watir/Readme.md.snapshot +175 -0
  52. data/spec/fixtures/watir_master/cucumber_watir/features/step_definitions/login_steps.rb.snapshot +24 -0
  53. data/spec/fixtures/watir_master/cucumber_watir/page_objects/abstract/page.rb.snapshot +39 -0
  54. data/spec/fixtures/watir_master/cucumber_watir/page_objects/pages/login.rb.snapshot +33 -0
  55. data/spec/fixtures/watir_master/rspec_watir/Readme.md.snapshot +174 -0
  56. data/spec/fixtures/watir_master/rspec_watir/page_objects/abstract/page.rb.snapshot +39 -0
  57. data/spec/fixtures/watir_master/rspec_watir/page_objects/pages/login.rb.snapshot +33 -0
  58. data/spec/fixtures/watir_master/rspec_watir/spec/login_page_spec.rb.snapshot +49 -0
  59. data/spec/integration/commands/scaffolding_commands_spec.rb +49 -0
  60. data/spec/integration/commands/utility_commands_spec.rb +8 -0
  61. data/spec/integration/content/azure_content_spec.rb +4 -0
  62. data/spec/integration/content/ci_content_spec.rb +11 -0
  63. data/spec/integration/content/common_content_spec.rb +27 -0
  64. data/spec/integration/content/config_content_spec.rb +62 -0
  65. data/spec/integration/content/cucumber_reporter_content_spec.rb +25 -0
  66. data/spec/integration/content/helper_content_spec.rb +111 -0
  67. data/spec/integration/content/page_content_spec.rb +75 -4
  68. data/spec/integration/content/readme_content_spec.rb +149 -0
  69. data/spec/integration/content/syntax_validation_spec.rb +38 -0
  70. data/spec/integration/content/test_content_spec.rb +82 -0
  71. data/spec/integration/end_to_end_spec.rb +73 -28
  72. data/spec/integration/generators/automation_generator_spec.rb +50 -0
  73. data/spec/integration/generators/common_generator_spec.rb +20 -0
  74. data/spec/integration/generators/config_features_spec.rb +2 -2
  75. data/spec/integration/generators/cucumber_generator_spec.rb +36 -0
  76. data/spec/integration/generators/helpers_generator_spec.rb +60 -0
  77. data/spec/integration/generators/invoke_generators_spec.rb +70 -0
  78. data/spec/integration/generators/rspec_generator_spec.rb +20 -0
  79. data/spec/integration/helpers/session_state_spec.rb +474 -0
  80. data/spec/integration/helpers/wait_helper_mobile_spec.rb +281 -0
  81. data/spec/integration/helpers/wait_helper_spec.rb +496 -0
  82. data/spec/integration/helpers/wait_helper_support.rb +183 -0
  83. data/spec/integration/settings_helper.rb +4 -1
  84. data/spec/integration/spec_helper.rb +2 -2
  85. data/spec/menus/menu_generator_spec.rb +64 -2
  86. data/spec/scaffolding/scaffold_project_detector_spec.rb +12 -0
  87. metadata +31 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a901c209fc30681088e9acb4d7c5fb315990db7346128a63533e0e70ef8c58f
4
- data.tar.gz: 1e99b0e973091657bfc10b98d5de1b730d9f7972a2446c56b7a56ddd786f9392
3
+ metadata.gz: 3dd26171963a22a6a95ac06de57c7baf3e462a8d0d71049b88b99d40552c94c0
4
+ data.tar.gz: 9826176205e621abddb2ddd583aa3bdee28eacd3c5e7d00a606bda9b2ba443e1
5
5
  SHA512:
6
- metadata.gz: 757ad8b61b6c3605d17739690e895198f2ad4c0aadbe4144fc1df77ec743afefd8095472c07e4d2ba4246c515d8038c052d096badc8cfbb04c432218c68ddb45
7
- data.tar.gz: 2e9d2bfb4e714c98052cb6df654be058d37fe40476aa93ce20e9319aeea5bbc24abe4359299b7a9f71f0c204ad19599865f1396794393e634b7200ed29a80bdb
6
+ metadata.gz: 3f0b129f291c538614a488c47f3ce71971f24060b60719203df43a2a19cc82309141cc846c867b644bb54832e9bbee2390fee04c2bb1db7bcec0c1f200d3eecb
7
+ data.tar.gz: 2d5e307edb7b5fdc1fbb0f1c0a1c830c6489fd8c7a11b3c08827fb3df9c49e7f1e23493a9165f23a5b2cbd7cd465913486fe174c79b7668731b7e5c7359e3e49
@@ -15,10 +15,14 @@ jobs:
15
15
  with:
16
16
  ruby-version: '3.3'
17
17
  bundler-cache: true
18
-
18
+
19
19
  - name: reek
20
20
  uses: reviewdog/action-reek@v1
21
21
  with:
22
22
  reek_version: gemfile
23
23
  reporter: github-pr-review # Default is github-pr-check
24
24
  fail_on_error: true
25
+
26
+ - name: reek (whole repo)
27
+ # With a path: bare `reek` reads stdin when there is no terminal, which checks nothing in CI
28
+ run: bundle exec reek .
@@ -61,7 +61,8 @@ jobs:
61
61
  run: bundle exec rubocop
62
62
 
63
63
  - name: Run Reek
64
- run: bundle exec reek
64
+ # With a path: bare `reek` reads stdin when there is no terminal, which checks nothing in CI
65
+ run: bundle exec reek .
65
66
 
66
67
  - name: Build gem
67
68
  run: |
@@ -39,7 +39,7 @@ jobs:
39
39
  run: bundle exec rspec spec/system/selenium_spec.rb --format documentation
40
40
 
41
41
  watir:
42
- name: Watir frameworks
42
+ name: Watir frameworks (deprecated)
43
43
  needs: setup
44
44
  runs-on: ubuntu-latest
45
45
  steps:
data/.reek.yml CHANGED
@@ -1,3 +1,7 @@
1
+ # setup-ruby's bundler-cache installs gems into vendor/bundle inside the checkout
2
+ exclude_paths:
3
+ - vendor
4
+
1
5
  detectors:
2
6
  Attribute:
3
7
  enabled: false
@@ -49,3 +53,12 @@ detectors:
49
53
 
50
54
  UtilityFunction:
51
55
  enabled: false
56
+
57
+ # Baseline of warnings that predate whole-repo reek in CI (2026-10-31). Remove an
58
+ # entry when its code is reworked.
59
+ MissingSafeMethod:
60
+ exclude:
61
+ - 'ScaffoldingCommands' # validate_project! is a Thor command guard; a non-raising twin has no caller
62
+ TooManyConstants:
63
+ exclude:
64
+ - 'RubyRaider::Ffmpeg' # pinned version, release URL and manifest constants
data/README.md CHANGED
@@ -33,10 +33,12 @@ Ruby Raider is a CLI gem for scaffolding and generating UI test automation frame
33
33
 
34
34
  ### Web Testing
35
35
 
36
- | Test Framework | Selenium | Watir |
37
- |----------------|----------|-------|
38
- | RSpec | ✅ | ✅ |
39
- | Cucumber | ✅ | ✅ |
36
+ | Test Framework | Selenium | Watir (deprecated) |
37
+ |----------------|----------|--------------------|
38
+ | RSpec | ✅ | ✅ |
39
+ | Cucumber | ✅ | ✅ |
40
+
41
+ Watir is deprecated and will be removed in the next major version of ruby_raider (4.0); new projects should use Selenium. It is no longer offered in the interactive menu and is available only through `-p automation:watir` until 4.0. See [Migrating from Watir](#migrating-from-watir).
40
42
 
41
43
  ### Mobile Testing (Appium)
42
44
 
@@ -61,6 +63,9 @@ Every generated project comes with:
61
63
  - **Page Object Model** — Base page and component classes with example pages
62
64
  - **Test examples** — Working login tests against the [Raider Test Store](https://raider-test-site.onrender.com/)
63
65
  - **Raider reporting** — RSpec and Cucumber projects include [raider_reporter](https://github.com/RaiderHQ/raider_reporter), which writes results to `raider-results/` and uploads runs to your Raider Hideout server when `RAIDER_TOKEN` and `RAIDER_ENDPOINT` are set; generated CI runs the suite through `raider-reporter exec` with `RAIDER_QUARANTINE`, so quarantined tests do not fail the build; an opt-in `raider-gate` job (`RAIDER_GATE=1`) fails CI when the Raider quality gate fails
66
+ - **Waiting** — generated Selenium and Appium projects wait for elements by themselves (`WaitHelper` and `RaiderElement`): `click`, `send_keys`, `text` and the rest poll until the element is ready, and `eventually { … }` retries assertions, so there is no implicit wait and no `sleep`; `timeout` in `config.yml` sets how long to wait
67
+ - **Example tests or a skeleton (`examples:false`)** — start from the working login examples, or pass `examples:false` for an empty skeleton with the helpers, config, CI and README (see [Getting Started](#getting-started))
68
+ - **Login reuse** — generated web projects (Selenium or Watir) log in once and restore the saved session for examples tagged `:logged_in` (RSpec) or `@logged_in` (Cucumber); `RAIDER_LOGIN_STATE=off|refresh` and `login_state: max_age:` control it, and the state stays in git-ignored `.raider/auth/`
64
69
  - **Failure video replay** — Every test is recorded and the video is kept only for failing tests, attached to the report as `video` (WebM from page screenshots for Selenium and Watir, Appium's own MP4 for mobile, which needs `ffmpeg` on the Appium server host for iOS and for Android recordings over 180 s). Turn it off per run with `VIDEO=off`, or per project with `raider u video off`; ffmpeg is downloaded once, SHA-256 checked and cached in `~/.cache/raider`. The RaiderHQ `ffmpeg-7.1` build that the download uses is not published yet, so for now set `RAIDER_FFMPEG_PATH` to an ffmpeg with libvpx to get web videos
65
70
  - **GitHub Actions CI** — Ready-to-use test pipeline that caches ffmpeg for failure videos
66
71
  - **Rake tasks** — Default, smoke, and regression tasks with tag-based filtering
@@ -117,6 +122,41 @@ raider new my_project -p framework:rspec automation:selenium ci:azure
117
122
  Azure Pipelines for mobile (Appium) projects is not available yet: with `ci:azure` no pipeline file is generated and a notice is printed.
118
123
  Azure Repos ignores `pr:` triggers, so add the pipeline as a Build validation branch policy to get pull request builds.
119
124
 
125
+ Generate an empty skeleton, without the example pages and tests (the menu asks `Do you want example tests?`):
126
+
127
+ ```bash
128
+ raider new my_project -p framework:rspec automation:selenium examples:false
129
+ ```
130
+
131
+ The skeleton keeps the helpers, config, CI and README, and its empty suite passes; grow it with `raider g`. Watir projects always include the examples.
132
+
133
+ ## Migrating from Watir
134
+
135
+ Watir is deprecated; to move a Watir project to Selenium, generate a Selenium project and port your pages. The common mappings:
136
+
137
+ | Watir | Selenium project (waiting layer) |
138
+ |-------|----------------------------------|
139
+ | `browser.goto(url)` | `driver.navigate.to(url)` |
140
+ | `browser.text_field(id: 'x').set(v)` | `clear({ id: 'x' })` and `send_keys({ id: 'x' }, v)` |
141
+ | `browser.button(id: 'x').click` | `click({ id: 'x' })` |
142
+ | `Watir.default_timeout = n` | `timeout: n` in `config/config.yml` |
143
+
144
+ ## Adopting waiting in an existing project
145
+
146
+ Gem upgrades do not rewrite generated code, so existing projects keep their implicit wait until you opt in:
147
+
148
+ 1. Copy both `helpers/wait_helper.rb` and `helpers/raider_element.rb` from a freshly generated project into your
149
+ `helpers/` folder. Keep them side by side: `wait_helper.rb` loads the other with `require_relative 'raider_element'`.
150
+ 2. Include it in `Page`, the spec helper, or the World, after loading it with `require_relative`, for example
151
+ `require_relative '../../helpers/wait_helper'` at the top of `page_objects/abstract/page.rb`.
152
+ 3. Set how long it waits: add `timeout: 10` to `config/config.yml`. Single-platform mobile projects (iOS or Android
153
+ only) have no `config/config.yml`, so pass `timeout:` to a call, or create `config/config.yml` with that line;
154
+ without either, it waits 10 seconds.
155
+ 4. Remove the `implicit_wait` line.
156
+ 5. Move flaky spots to `click`, `send_keys` and `eventually` first.
157
+
158
+ Keep in mind that keeping the implicit wait while using the layer makes every `find` miss cost up to `timeout` seconds per poll cycle, so remove it once you have moved over.
159
+
120
160
  ## Commands
121
161
 
122
162
  ###### Anything between square brackets ([...]) is where your input goes
@@ -90,7 +90,7 @@ class UtilityCommands < Thor
90
90
 
91
91
  desc 'start_appium', 'It starts the appium server'
92
92
  def start_appium
93
- system 'appium --base-path /wd/hub'
93
+ system 'appium'
94
94
  end
95
95
 
96
96
  desc 'desktop', 'Downloads the Raider Desktop GUI application'
@@ -4,31 +4,36 @@ require_relative '../generator'
4
4
 
5
5
  class AutomationGenerator < Generator
6
6
  def generate_automation_files
7
+ generate_appium_settings if mobile?
8
+ generate_abstract_component unless mobile?
9
+ examples? ? generate_examples : generate_empty_folders
10
+ generate_abstract_page
11
+ end
12
+
13
+ private
14
+
15
+ def generate_examples
7
16
  if mobile?
8
- generate_appium_settings
9
17
  generate_home_page
10
18
  generate_pdp_page
11
19
  else
12
- generate_components
20
+ generate_header_component
13
21
  generate_model_files
14
22
  generate_pages
15
23
  end
16
-
17
- generate_abstract_page
18
24
  end
19
25
 
20
- private
26
+ # A skeleton keeps the folders for the pages and components it will grow with `raider g`
27
+ def generate_empty_folders
28
+ create_file("#{name}/page_objects/pages/.keep", '')
29
+ create_file("#{name}/page_objects/components/.keep", '') unless mobile?
30
+ end
21
31
 
22
32
  def generate_pages
23
33
  generate_login_page
24
34
  generate_account_page
25
35
  end
26
36
 
27
- def generate_components
28
- generate_abstract_component
29
- generate_header_component
30
- end
31
-
32
37
  def generate_model_files
33
38
  generate_model_factory
34
39
  generate_model_data
@@ -18,14 +18,13 @@ browserstack:
18
18
  platformName: Android
19
19
  os_version: '9.0'
20
20
  deviceName: Google Pixel 3
21
- app: app: <%= ENV['APP_URL'] %>
21
+ app: <%= ENV['APP_URL'] %>
22
22
  browserstack.user: <%= ENV['BROWSERSTACK_USER'] %>
23
23
  browserstack.key: <%= ENV['BROWSERSTACK_KEY'] %>
24
24
  project: 'RaiderTestApp'
25
25
  name: 'RaiderTestApp-Android'
26
26
  <% elsif ios? %>platformName: iOS
27
27
  appium:options:
28
- url: http://localhost:4723/wd/hub
29
28
  platformVersion: '17.0'
30
29
  deviceName: iPhone 15
31
30
  automationName: XCUITest
@@ -43,7 +42,6 @@ browserstack:
43
42
  name: 'RaiderTestApp-IOS'
44
43
  <% elsif android? %>platformName: Android
45
44
  appium:options:
46
- url: http://localhost:4723/wd/hub
47
45
  platformVersion: '14'
48
46
  automationName: UiAutomator2
49
47
  deviceName: Pixel 8 API 34
@@ -53,7 +51,7 @@ browserstack:
53
51
  platformName: Android
54
52
  os_version: '9.0'
55
53
  deviceName: Google Pixel 3
56
- app: app: <%= ENV['APP_URL'] %>
54
+ app: <%= ENV['APP_URL'] %>
57
55
  browserstack.user: <%= ENV['BROWSERSTACK_USER'] %>
58
56
  browserstack.key: <%= ENV['BROWSERSTACK_KEY'] %>
59
57
  project: 'RaiderTestApp'
@@ -3,11 +3,10 @@
3
3
  require_relative '../abstract/page'
4
4
 
5
5
  class Home < Page
6
-
7
6
  # Actions
8
7
 
9
8
  def go_to_product_detail
10
- driver.wait { first_product }.click
9
+ tap(first_product)
11
10
  end
12
11
 
13
12
  private
@@ -11,9 +11,9 @@ class Login < Page
11
11
 
12
12
  <%- if selenium_based? -%>
13
13
  def login(username, password)
14
- username_field.send_keys username
15
- password_field.send_keys password
16
- login_button.click
14
+ send_keys(username_field, username)
15
+ send_keys(password_field, password)
16
+ click(login_button)
17
17
  end
18
18
  alias log_as login
19
19
  <%- else -%>
@@ -30,7 +30,7 @@ class Login < Page
30
30
 
31
31
  def username_field
32
32
  <%- if selenium_based? -%>
33
- driver.find_element(id: 'loginFrm_loginname')
33
+ { id: 'loginFrm_loginname' }
34
34
  <%- else -%>
35
35
  browser.text_field(id: 'loginFrm_loginname')
36
36
  <%- end -%>
@@ -38,7 +38,7 @@ class Login < Page
38
38
 
39
39
  def password_field
40
40
  <%- if selenium_based? -%>
41
- driver.find_element(id: 'loginFrm_password')
41
+ { id: 'loginFrm_password' }
42
42
  <%- else -%>
43
43
  browser.text_field(id: 'loginFrm_password')
44
44
  <%- end -%>
@@ -46,7 +46,7 @@ class Login < Page
46
46
 
47
47
  def login_button
48
48
  <%- if selenium_based? -%>
49
- driver.find_element(xpath: "//button[@title='Login']")
49
+ { xpath: "//button[@title='Login']" }
50
50
  <%- else -%>
51
51
  browser.button(xpath: "//button[@title='Login']")
52
52
  <%- end -%>
@@ -1,11 +1,17 @@
1
- <%- if web? -%>
1
+ <%- if web? && examples? -%>
2
2
  require_relative '../components/header'
3
3
  <%- end -%>
4
+ <%- if wait_helper? -%>
5
+ require_relative '../../helpers/wait_helper'
6
+ <%- end -%>
4
7
 
5
8
  class Page
6
9
  <%- if cross_platform? -%>
7
10
  include AppiumHelper
8
11
  <%- end -%>
12
+ <%- if wait_helper? -%>
13
+ include WaitHelper
14
+ <%- end -%>
9
15
  <%=- partial('initialize_selector', trim: false) -%>
10
16
  <%=- partial('visit_method', trim: false) -%>
11
17
  <%=- partial('url_methods', trim: false) -%>
@@ -13,14 +19,15 @@ class Page
13
19
  def to_s
14
20
  self.class.to_s.sub('Page', ' Page')
15
21
  end
22
+ <%- if selenium_based? && examples? -%>
16
23
 
17
- <%- if selenium_based? -%>
18
24
  # Components
19
25
 
20
26
  def header
21
- Header.new(driver.find_element(css: '.welcome-msg'))
27
+ Header.new(find_element(css: '.welcome-msg'))
22
28
  end
23
29
  <%- elsif watir? -%>
30
+
24
31
  # Components
25
32
 
26
33
  def header
@@ -1,8 +1,6 @@
1
- <%- if ios? -%>
2
- driver.find_element(accessibility_id: 'product-item-1')
3
- <%- elsif android? -%>
4
- driver.find_element(accessibility_id: 'product-item-1')
1
+ <%- if cross_platform? -%>
2
+ { ios: { accessibility_id: 'product-item-1' },
3
+ android: { accessibility_id: 'product-item-1' } }
5
4
  <%- else -%>
6
- element(ios: { accessibility_id: 'product-item-1' },
7
- android: { accessibility_id: 'product-item-1' })
5
+ { accessibility_id: 'product-item-1' }
8
6
  <%- end -%>
@@ -1,8 +1,6 @@
1
- <%- if ios? -%>
2
- driver.find_element(accessibility_id: 'add-to-cart-button')
3
- <%- elsif android? -%>
4
- driver.find_element(accessibility_id: 'add-to-cart-button')
1
+ <%- if cross_platform? -%>
2
+ { ios: { accessibility_id: 'add-to-cart-button' },
3
+ android: { accessibility_id: 'add-to-cart-button' } }
5
4
  <%- else -%>
6
- element(ios: { accessibility_id: 'add-to-cart-button' },
7
- android: { accessibility_id: 'add-to-cart-button' })
5
+ { accessibility_id: 'add-to-cart-button' }
8
6
  <%- end -%>
@@ -1,11 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../abstract/page'
2
4
 
3
5
  class Pdp < Page
4
-
5
6
  # Actions
6
7
 
7
8
  def add_to_cart_text
8
- driver.wait { add_to_cart_button }.text
9
+ text(add_to_cart_button)
9
10
  end
10
11
 
11
12
  private
@@ -4,14 +4,27 @@ require_relative '../generator'
4
4
 
5
5
  class CucumberGenerator < Generator
6
6
  def generate_feature
7
+ return unless examples?
8
+
7
9
  template('feature.tt', "#{name}/features/#{template_name}.feature")
8
10
  end
9
11
 
10
12
  def generate_steps
13
+ return unless examples?
14
+
11
15
  template('steps.tt', "#{name}/features/step_definitions/#{template_name}_steps.rb")
12
16
  end
13
17
 
18
+ # A skeleton keeps an empty step_definitions/ for the steps it will grow with `raider g steps`
19
+ def generate_empty_steps_folder
20
+ return if examples?
21
+
22
+ create_file("#{name}/features/step_definitions/.keep", '')
23
+ end
24
+
14
25
  def generate_user_factory
26
+ return unless examples?
27
+
15
28
  template('user_factory.tt', "#{name}/models/user_factory.rb")
16
29
  end
17
30
 
@@ -28,13 +41,13 @@ class CucumberGenerator < Generator
28
41
  end
29
42
 
30
43
  def generate_accessibility_feature
31
- return unless axe_addon? && web?
44
+ return unless axe_addon? && web? && examples?
32
45
 
33
46
  template('accessibility_feature.tt', "#{name}/features/accessibility.feature")
34
47
  end
35
48
 
36
49
  def generate_accessibility_steps
37
- return unless axe_addon? && web?
50
+ return unless axe_addon? && web? && examples?
38
51
 
39
52
  template('accessibility_steps.tt', "#{name}/features/step_definitions/accessibility_steps.rb")
40
53
  end
@@ -6,6 +6,11 @@ Scenario: A user can login
6
6
  When I login with my credentials
7
7
  Then I'm logged in
8
8
 
9
+ @logged_in
10
+ Scenario: A logged-in user opens the account page
11
+ Given I'm a registered user
12
+ Then I'm logged in
13
+
9
14
  <%- else -%>
10
15
  Feature: Home Page
11
16
 
@@ -1,9 +1,18 @@
1
1
  require_relative '../../helpers/driver_helper'
2
+ <%- if wait_helper? -%>
3
+ require_relative '../../helpers/wait_helper'
4
+ <%- end -%>
2
5
  <%- if selenium_based? -%>
6
+ <%- if examples? -%>
3
7
  require_relative '../../models/user_factory'
8
+ <%- end -%>
4
9
 
5
10
  World(DriverHelper)
11
+ World(WaitHelper)
6
12
  <%- else -%>
7
13
 
8
14
  World(DriverHelper)
15
+ <%- if wait_helper? -%>
16
+ World(WaitHelper)
17
+ <%- end -%>
9
18
  <%- end -%>
@@ -16,5 +16,5 @@ end
16
16
 
17
17
  Then("I'm redirected to the product details page") do
18
18
  pdp_page = Pdp.new(driver)
19
- expect(pdp_page.add_to_cart_text).to eq 'Add to Cart'
19
+ eventually { expect(pdp_page.add_to_cart_text).to eq 'Add to Cart' }
20
20
  end
@@ -8,6 +8,12 @@ require 'raider_reporter/cucumber'
8
8
  require 'tmpdir'
9
9
  require 'yaml'
10
10
  require_relative '../../helpers/video_helper'
11
+ require_relative '../../helpers/session_state'
12
+ <%- if examples? -%>
13
+ require_relative '../../models/user_factory'
14
+ require_relative '../../page_objects/pages/account'
15
+ require_relative '../../page_objects/pages/login'
16
+ <%- end -%>
11
17
 
12
18
  Before do
13
19
  viewport = YAML.load_file('config/config.yml')['viewport']
@@ -20,6 +26,20 @@ Before do
20
26
  @video_recorder.start
21
27
  end
22
28
 
29
+ <%- if examples? -%>
30
+ Before('@logged_in') do
31
+ user = UserFactory.for('registered user')
32
+ account = Account.new(driver)
33
+ SessionState.new(driver).reuse(
34
+ valid: -> { account.tap(&:visit).header.customer_name == "Welcome back #{user['name']}" }
35
+ ) do
36
+ login = Login.new(driver)
37
+ login.visit
38
+ login.login(user['username'], user['password'])
39
+ end
40
+ end
41
+
42
+ <%- end -%>
23
43
  After do |scenario|
24
44
  video_path = @video_recorder&.stop(keep: scenario.failed?)
25
45
  RaiderReporter.attach('video', video_path, type: :webm) if video_path
@@ -4,6 +4,10 @@ require 'raider_reporter/cucumber'
4
4
  require 'tmpdir'
5
5
  require 'yaml'
6
6
  require_relative '../../helpers/video_helper'
7
+ require_relative '../../helpers/session_state'
8
+ require_relative '../../models/user_factory'
9
+ require_relative '../../page_objects/pages/account'
10
+ require_relative '../../page_objects/pages/login'
7
11
 
8
12
  Before do
9
13
  viewport = YAML.load_file('config/config.yml')['viewport']
@@ -16,6 +20,18 @@ Before do
16
20
  @video_recorder.start
17
21
  end
18
22
 
23
+ Before('@logged_in') do
24
+ user = UserFactory.for('registered user')
25
+ account = Account.new(browser)
26
+ SessionState.new(browser.driver).reuse(
27
+ valid: -> { account.tap(&:visit).header.customer_name == "Welcome back #{user['name']}" }
28
+ ) do
29
+ login = Login.new(browser)
30
+ login.visit
31
+ login.login(user['username'], user['password'])
32
+ end
33
+ end
34
+
19
35
  After do |scenario|
20
36
  video_path = @video_recorder&.stop(keep: scenario.failed?)
21
37
  RaiderReporter.attach('video', video_path, type: :webm) if video_path
@@ -9,6 +9,10 @@ Given("I'm a {user} on the login page") do |user|
9
9
  @login_page.visit
10
10
  end
11
11
 
12
+ Given("I'm a {user}") do |user|
13
+ @user = user
14
+ end
15
+
12
16
  When('I login with my credentials') do
13
17
  @login_page.login(@user['username'], @user['password'])
14
18
  end
@@ -16,5 +20,9 @@ end
16
20
  Then("I'm logged in") do
17
21
  account_page = <% if watir? %>Account.new(browser)<% else %>Account.new(driver)<% end %>
18
22
  account_page.visit
23
+ <%- if watir? -%>
19
24
  expect(account_page.header.customer_name).to eq "Welcome back #{@user['name']}"
25
+ <%- else -%>
26
+ eventually { expect(account_page.header.customer_name).to eq "Welcome back #{@user['name']}" }
27
+ <%- end -%>
20
28
  end
@@ -60,16 +60,27 @@ class Generator < Thor::Group
60
60
  !azure?
61
61
  end
62
62
 
63
+ # The automation arg only, so a project named "watir" or "selenium" keeps its own automation
63
64
  def selenium?
64
- args.include?('selenium')
65
+ automation == 'selenium'
65
66
  end
66
67
 
67
68
  def watir?
68
- args.include?('watir')
69
+ automation == 'watir'
70
+ end
71
+
72
+ # Selenium and Appium projects get the auto-waiting WaitHelper; Watir keeps its own waits
73
+ def wait_helper?
74
+ !watir?
75
+ end
76
+
77
+ # False for `raider new -p examples:false`: an empty skeleton without the example pages, tests and wiring
78
+ def examples?
79
+ !args.drop(3).include?('no_examples')
69
80
  end
70
81
 
71
82
  def web?
72
- (args & %w[selenium watir]).count.positive?
83
+ selenium? || watir?
73
84
  end
74
85
 
75
86
  def axe_addon?
@@ -77,7 +88,7 @@ class Generator < Thor::Group
77
88
  end
78
89
 
79
90
  def selenium_based?
80
- args.include?('selenium')
91
+ selenium?
81
92
  end
82
93
 
83
94
  def ruby_raider_version_requirement
@@ -9,6 +9,8 @@ class HelpersGenerator < Generator
9
9
  generate_appium_helper
10
10
  generate_spec_helper
11
11
  generate_video_helper
12
+ generate_session_state
13
+ generate_wait_helper
12
14
  end
13
15
 
14
16
  private
@@ -35,6 +37,19 @@ class HelpersGenerator < Generator
35
37
  template('helpers/video_helper.tt', "#{name}/helpers/video_helper.rb")
36
38
  end
37
39
 
40
+ def generate_session_state
41
+ return if mobile?
42
+
43
+ template('helpers/session_state.tt', "#{name}/helpers/session_state.rb")
44
+ end
45
+
46
+ def generate_wait_helper
47
+ return unless wait_helper?
48
+
49
+ template('helpers/wait_helper.tt', "#{name}/helpers/wait_helper.rb")
50
+ template('helpers/raider_element.tt', "#{name}/helpers/raider_element.rb")
51
+ end
52
+
38
53
  def generate_appium_helper
39
54
  return unless cross_platform?
40
55
 
@@ -11,6 +11,9 @@ module InvokeGenerators
11
11
  FRAMEWORKS = %w[rspec cucumber].freeze
12
12
  AUTOMATIONS = %w[selenium watir ios android cross_platform].freeze
13
13
 
14
+ WATIR_NOTICE = 'Watir is deprecated and will be removed in the next major version of ruby_raider; ' \
15
+ 'new projects should use Selenium.'.freeze
16
+
14
17
  module_function
15
18
 
16
19
  def generate_framework(structure = {})
@@ -33,6 +36,8 @@ module InvokeGenerators
33
36
  })
34
37
  end
35
38
  print_mobile_azure_notice(structure)
39
+ print_watir_notice(structure)
40
+ print_watir_examples_notice(structure)
36
41
  end
37
42
 
38
43
  def normalise_options(structure)
@@ -60,6 +65,12 @@ module InvokeGenerators
60
65
  puts 'Azure Pipelines template for mobile projects is not available yet; see the README.'
61
66
  end
62
67
 
68
+ def print_watir_notice(structure)
69
+ return unless structure[:automation] == 'watir'
70
+
71
+ puts WATIR_NOTICE
72
+ end
73
+
63
74
  def add_generator(generators, *gens)
64
75
  gens.each { |generator| generators.push generator }
65
76
  end
@@ -81,9 +92,21 @@ module InvokeGenerators
81
92
  flags = []
82
93
  flags << 'axe_addon' if structure[:accessibility] && !mobile_automation?(structure[:automation])
83
94
  flags << 'azure' if structure[:ci].to_s == 'azure'
95
+ flags << 'no_examples' if skeleton?(structure)
84
96
  flags
85
97
  end
86
98
 
99
+ # Watir templates are frozen, so a Watir project always gets the example tests
100
+ def skeleton?(structure)
101
+ structure[:examples] == false && structure[:automation] != 'watir'
102
+ end
103
+
104
+ def print_watir_examples_notice(structure)
105
+ return unless structure[:examples] == false && structure[:automation] == 'watir'
106
+
107
+ puts 'Watir projects always include the example tests; examples:false applies to Selenium and Appium projects.'
108
+ end
109
+
87
110
  def mobile_automation?(automation)
88
111
  %w[ios android cross_platform].include?(automation)
89
112
  end