Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

set datetimes via keystrokes based on detected locale in Selenium driver #2119

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Naming/UncommunicativeMethodParamName:
- 'x'
- 'y'
- 'on'
- 'dt'

Style/ParallelAssignment:
Enabled: false
Expand Down
69 changes: 58 additions & 11 deletions lib/capybara/selenium/node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ def set(value, **options)
when 'file'
set_file(value)
when 'date'
set_date(value)
set_date(value, options)
when 'time'
set_time(value)
set_time(value, options)
when 'datetime-local'
set_datetime_local(value)
set_datetime_local(value, options)
else
set_text(value, options)
end
Expand Down Expand Up @@ -259,30 +259,44 @@ def scroll_to_center
end
end

def set_date(value) # rubocop:disable Naming/AccessorMethodName
def set_date(value, as_keys: false, **)
value = SettableValue.new(value)
return set_text(value) unless value.dateable?
return set_as_keystrokes(value, :date) if as_keys

# TODO: this would be better if locale can be detected and correct keystrokes sent
update_value_js(value.to_date_str)
end

def set_time(value) # rubocop:disable Naming/AccessorMethodName
def set_time(value, as_keys: false, **)
value = SettableValue.new(value)
return set_text(value) unless value.timeable?
return set_as_keystrokes(value, :time) if as_keys

# TODO: this would be better if locale can be detected and correct keystrokes sent
update_value_js(value.to_time_str)
end

def set_datetime_local(value) # rubocop:disable Naming/AccessorMethodName
def set_datetime_local(value, as_keys: false, **)
value = SettableValue.new(value)
return set_text(value) unless value.timeable?
return set_as_keystrokes(value, :datetime) if as_keys

# TODO: this would be better if locale can be detected and correct keystrokes sent
update_value_js(value.to_datetime_str)
end

def set_as_keystrokes(val, type)
send_keys(keystrokes_for_datetime(val, type))
end

def locale
driver.execute_script(<<~JS).to_sym.downcase
return (window.navigator && (
(window.navigator.languages && window.navigator.languages[0]) ||
window.navigator.language ||
window.navigator.userLanguage
));
JS
end

def update_value_js(value)
driver.execute_script(<<-JS, self, value)
if (document.activeElement !== arguments[0]){
Expand Down Expand Up @@ -357,6 +371,35 @@ def each_key(keys)
end
end

def keystrokes_for_datetime(dt, type)
format = LOCALE_KEYSTROKES[locale][type]
if format.is_a? String
dt.strftime(format)
else
format.call(dt, self)
end
end

LOCALE_KEYSTROKES = {
'en-us': {
datetime: lambda { |dt, node|
if node[:step].empty?
dt.strftime '%m%d%Y%t%I%M%p'
else
dt.strftime '%m%d%Y%t%I%M%S%p'
end
},
time: lambda { |dt, node|
if node[:step].empty?
dt.strftime '%I%M%p'
else
dt.strftime '%I%M%S%p'
end
},
date: '%m%d%Y'
}
}.freeze

# SettableValue encapsulates time/date field formatting
class SettableValue
attr_reader :value
Expand All @@ -382,11 +425,15 @@ def timeable?
end

def to_time_str
value.to_time.strftime('%H:%M')
value.to_time.strftime('%H:%M:%S')
end

def to_datetime_str
value.to_time.strftime('%Y-%m-%dT%H:%M')
value.to_time.strftime('%Y-%m-%dT%H:%M:%S')
end

def strftime(format)
value.strftime format
end
end
private_constant :SettableValue
Expand Down
6 changes: 6 additions & 0 deletions lib/capybara/selenium/nodes/marionette_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ def click_with_options(click_options)
super
end

def set_as_keystrokes(val, type)
# send_keys doesn't work for datetime widgets in FF - use Actions API
click(x: 5, y: 5)
_send_keys(keystrokes_for_datetime(val, type)).perform
end

def _send_keys(keys, actions = browser_action, down_keys = ModifierKeysStack.new)
case keys
when :control, :left_control, :right_control,
Expand Down
15 changes: 15 additions & 0 deletions lib/capybara/spec/session/fill_in_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,27 @@
expect(Time.parse(results).strftime('%r')).to eq time.strftime('%r')
end

it 'should fill in a time input with seconds' do
time = Time.new(2018, 3, 9, 15, 26, 19)
@session.fill_in('form_time_with_seconds', with: time)
@session.click_button('awesome')
results = extract_results(@session)['time_with_seconds']
expect(Time.parse(results).strftime('%r')).to eq time.strftime('%r')
end

it 'should fill in a datetime input' do
dt = Time.new(2018, 3, 13, 9, 53)
@session.fill_in('form_datetime', with: dt)
@session.click_button('awesome')
expect(Time.parse(extract_results(@session)['datetime'])).to eq dt
end

it 'should fill in a datetime input with seconds' do
dt = Time.new(2018, 3, 13, 9, 53, 13)
@session.fill_in('form_datetime_with_seconds', with: dt)
@session.click_button('awesome')
expect(Time.parse(extract_results(@session)['datetime_with_seconds'])).to eq dt
end
end

it 'should handle HTML in a textarea' do
Expand Down
2 changes: 2 additions & 0 deletions lib/capybara/spec/views/form.erb
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,8 @@ New line after and before textarea tag
<input type="date" name="form[date]" id="form_date"/>
<input type="time" name="form[time]" id="form_time"/>
<input type="datetime-local" name="form[datetime]" id="form_datetime">
<input type="time" name="form[time_with_seconds]" id="form_time_with_seconds" step="1">
<input type="datetime-local" name="form[datetime_with_seconds]" id="form_datetime_with_seconds" step="1">
</p>

<p>
Expand Down