From 409152fa1d744fffac270ff76e2090b941b30106 Mon Sep 17 00:00:00 2001 From: Matias Grunberg Date: Tue, 15 Mar 2022 23:47:57 -0300 Subject: [PATCH] add support for alternative select boxes date field helpers I'm adding another spec forcing select boxes view helper. The helper has been around since Rails 5. It's not a problem to run the test in older versions --- CHANGELOG.md | 2 +- features/capybara_javascript_drivers.feature | 47 +++++++++++++++++++ .../step_definitions/cucumber_rails_steps.rb | 6 +++ .../rails/capybara/select_dates_and_times.rb | 38 ++++++++++----- 4 files changed, 80 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d1e915f..e974e9df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ on how to contribute to Cucumber. ### Fixed -* +* Date input support in Rails 7 ([#535](https://github.com/cucumber/cucumber-rails/pull/535) [mgrunberg]) ## [v2.5.0](https://github.com/cucumber/cucumber-rails/compare/v2.4.0...v2.5.0) (2022-03-07) diff --git a/features/capybara_javascript_drivers.feature b/features/capybara_javascript_drivers.feature index 271639d3..3d6e514d 100644 --- a/features/capybara_javascript_drivers.feature +++ b/features/capybara_javascript_drivers.feature @@ -46,6 +46,53 @@ Feature: Capybara Javascript Drivers 6 steps (6 passed) """ + Scenario: Support non HTML5 date inputs + Given I have created a new Rails app and installed cucumber-rails + And I force selenium to run Firefox in headless mode + When I run `bundle exec rails g scaffold appointment name:string when:datetime` + And I force "app/views/appointments/_form.html.erb" to use select boxes for dates + And I write to "features/create_appointment.feature" with: + """ + @javascript + Feature: Create appointments + Scenario: Create an appointment using the Web Interface + Given I am on the new appointment page + When I fill in "Cucumber Trainee" for "Name" + And I select "2026-02-20 15:10:00 UTC" as the "When" date and time + And I press "Create Appointment" + Then I should see "Cucumber Trainee" + And I should see "2026-02-20 15:10:00 UTC" + """ + And I write to "features/create_appointment_steps.rb" with: + """ + Given('I am on the new appointment page') do + visit new_appointment_path + end + + When('I fill in {string} for {string}') do |value, field| + fill_in(field, with: value) + end + + When('I press {string}') do |button| + click_button(button) + end + + When('I select {string} as the {string} date and time') do |datetime, selector| + select_datetime(datetime, from: selector) + end + + Then('I should see {string}') do |text| + expect(page).to have_content(text) + end + """ + And I run `bundle exec rake db:migrate` + And I run `bundle exec rake cucumber` + Then the feature run should pass with: + """ + 1 scenario (1 passed) + 6 steps (6 passed) + """ + Scenario: Use direct DB injection Given I have created a new Rails app and installed cucumber-rails And I force selenium to run Firefox in headless mode diff --git a/features/step_definitions/cucumber_rails_steps.rb b/features/step_definitions/cucumber_rails_steps.rb index a83de25e..2d1b6890 100644 --- a/features/step_definitions/cucumber_rails_steps.rb +++ b/features/step_definitions/cucumber_rails_steps.rb @@ -67,6 +67,12 @@ step 'I append to "features/support/env.rb" with:', selenium_config end +Given('I force {string} to use select boxes for dates') do |file| + content = File.read(expand_path(file)) + + overwrite_file(file, content.gsub(/\.(datetime|time|date)_field/, '.\1_select')) +end + When('I run the cukes') do run_command_and_stop('bundle exec cucumber') end diff --git a/lib/cucumber/rails/capybara/select_dates_and_times.rb b/lib/cucumber/rails/capybara/select_dates_and_times.rb index 17079d58..1018e9cc 100644 --- a/lib/cucumber/rails/capybara/select_dates_and_times.rb +++ b/lib/cucumber/rails/capybara/select_dates_and_times.rb @@ -8,12 +8,13 @@ module SelectDatesAndTimes # Select a Rails date. Options hash must include from: +label+ def select_date(date, options) date = Date.parse(date) - if ::Rails::VERSION::MAJOR >= 7 - # Rails 7 generates date fields using input type="date". Capybara support's them + base_dom_id = get_base_dom_from_options(options) + + # Rails 7 use HTML5 input type="date" by default. If input is not present fallback to plain select boxes alternative. + # It's safe to use has_css? without waiting/retry. We already know field's label is visible + if html5_input_field_present?(base_dom_id) fill_in options[:from], with: date else - base_dom_id = get_base_dom_id_from_label_tag(options[:from]) - find(:xpath, ".//select[@id='#{base_dom_id}_1i']").select(date.year.to_s) find(:xpath, ".//select[@id='#{base_dom_id}_2i']").select(I18n.l(date, format: '%B')) find(:xpath, ".//select[@id='#{base_dom_id}_3i']").select(date.day.to_s) @@ -23,12 +24,13 @@ def select_date(date, options) # Select a Rails time. Options hash must include from: +label+ def select_time(time, options) time = Time.zone.parse(time) - if ::Rails::VERSION::MAJOR >= 7 - # Rails 7 generates date fields using input type="time". Capybara support's them + base_dom_id = get_base_dom_from_options(options) + + # Rails 7 use HTML5 input type="time" by default. If input is not present fallback to plain select boxes alternative. + # It's safe to use has_css? without waiting/retry. We already know field's label is visible + if html5_input_field_present?(base_dom_id) fill_in options[:from], with: time else - base_dom_id = get_base_dom_id_from_label_tag(options[:from]) - find(:xpath, ".//select[@id='#{base_dom_id}_4i']").select(time.hour.to_s.rjust(2, '0')) find(:xpath, ".//select[@id='#{base_dom_id}_5i']").select(time.min.to_s.rjust(2, '0')) end @@ -36,17 +38,29 @@ def select_time(time, options) # Select a Rails datetime. Options hash must include from: +label+ def select_datetime(datetime, options) - if ::Rails::VERSION::MAJOR >= 7 - # Rails 7 generates datetime fields using input type="datetime-local". Capybara support's them + base_dom_id = get_base_dom_id_from_label_tag(options[:from]) + + # Rails 7 use HTML5 input type="datetime-local" by default. If input is not present fallback to plain select boxes alternative. + # It's safe to use has_css? without waiting/retry. We already know field's label is visible + if html5_input_field_present?(base_dom_id) fill_in options[:from], with: DateTime.parse(datetime) else - select_date(datetime, options) - select_time(datetime, options) + extended_options = options.merge(base_dom_id: base_dom_id) + select_date(datetime, extended_options) + select_time(datetime, extended_options) end end private + def html5_input_field_present?(base_dom_id) + ::Rails::VERSION::MAJOR >= 7 && page.has_css?("##{base_dom_id}", wait: 0) + end + + def get_base_dom_from_options(options) + options[:base_dom_id] || get_base_dom_id_from_label_tag(options[:from]) + end + # @example "event_starts_at_" def get_base_dom_id_from_label_tag(field) find(:xpath, ".//label[contains(., '#{field}')]")['for'].gsub(/(_[1-5]i)$/, '')