Skip to content

Commit

Permalink
set datetimes via keystrokes based on detected locale in Selenium driver
Browse files Browse the repository at this point in the history
  • Loading branch information
twalpole committed Nov 6, 2018
1 parent 671f871 commit fb04799
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 118 deletions.
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
Loading

0 comments on commit fb04799

Please sign in to comment.