Skip to content

Commit

Permalink
Implement multiple expression types for fillable_field selector type …
Browse files Browse the repository at this point in the history
…and add some tests
  • Loading branch information
twalpole committed Feb 23, 2020
1 parent 8d30c00 commit b69a1f3
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 19 deletions.
4 changes: 3 additions & 1 deletion lib/capybara/selector/definition/datalist_input.rb
Expand Up @@ -19,7 +19,9 @@

expression_filter(:with_options) do |expr, options|
options.inject(expr) do |xpath, option|
xpath[XPath.attr(:list) == XPath.anywhere(:datalist)[expression_for(:datalist_option, option, format: :xpath)].attr(:id)]
xpath[
XPath.attr(:list) == XPath.anywhere(:datalist)[expression_for(:datalist_option, option, format: :xpath)
].attr(:id)]
end
end

Expand Down
14 changes: 0 additions & 14 deletions lib/capybara/selector/definition/field.rb
Expand Up @@ -62,20 +62,6 @@

filter_set(:_field) # checked/unchecked/disabled/multiple/name/placeholder

expression_filter(:name) do |expr, val|
if default_format == :css
::Capybara::Selector::CSSBuilder.new(expr).add_attribute_conditions(name: val)
else
expr[XPath.attr(:name) == val]
end
end
expression_filter(:placeholder) do |expr, val|
if default_format == :css
::Capybara::Selector::CSSBuilder.new(expr).add_attribute_conditions(placeholder: val)
else
expr[XPath.attr(:placeholder) == val]
end
end
expression_filter(:readonly, :boolean, format: :css) do |expr, val|
::Capybara::Selector::CSS.split(expr).map do |css_fragment|
if val
Expand Down
46 changes: 43 additions & 3 deletions lib/capybara/selector/definition/fillable_field.rb
Expand Up @@ -9,12 +9,52 @@
locate_field(xpath, locator, **options)
end

css do |_locator, **_options|
invalid_types = %w[submit image radio checkbox hidden file]
invalid_attributes = invalid_types.map { |type| ":not([type=#{type}])" }.join
"input#{invalid_attributes}, textarea"
end

locator_filter(skip_if: nil, format: :css) do |node, locator, exact:, **_|
optional_checks = +''
optional_checks << "(field.getAttribute('aria-label') == locator)||" if enable_aria_label
optional_checks << "(field.getAttribute('#{test_id}') == locator)||" if test_id

match_js = <<~JS
(function(field, locator){
return (
(field.id == locator) ||
(field.name == locator) ||
(field.placeholder == locator)||
#{optional_checks}
Array.from(field.labels || []).some(function(label){
return label.innerText#{exact ? '== locator' : '.includes(locator)'};
})
);
})(this, arguments[0])
JS
node.evaluate_script(match_js, locator)
end

expression_filter(:type) do |expr, type|
type = type.to_s
if type == 'textarea'
expr.self(type.to_sym)
case default_format
when :css
if type == 'textarea'
::Capybara::Selector::CSS.split(expr).select do |css_fragment|
css_fragment.start_with? type
end.join(',')
else
::Capybara::Selector::CSSBuilder.new(expr).add_attribute_conditions(type: type)
end
when :xpath
if type == 'textarea'
expr.self(type.to_sym)
else
expr[XPath.attr(:type) == type]
end
else
expr[XPath.attr(:type) == type]
raise ArgumentError, "Unknown format type: #{default_format}"
end
end

Expand Down
72 changes: 72 additions & 0 deletions lib/capybara/spec/session/shadow_spec.rb
@@ -0,0 +1,72 @@
# frozen_string_literal: true

Capybara::SpecHelper.spec 'shadow', requires: %i[js shadow] do
before { @session.visit('/form') }

it 'can check for fields in the shadow dom' do
form = @session.first(:css, 'form')
shadow = @session.evaluate_script <<~JS, form
(function(form){
var shadow_host = document.createElement('div');
var shadow = shadow_host.attachShadow({mode: 'open'});
shadow.appendChild(form);
document.documentElement.appendChild(shadow_host);
return shadow;
}).apply(this, arguments)
JS

expect(shadow).to have_field('Dog', selector_format: :css)
expect(shadow).not_to have_field('Monkey', selector_format: :css)

expect(shadow).to have_field('First Name', with: 'John', selector_format: :css)
expect(shadow).to have_field('First Name', with: /^Joh/, selector_format: :css)
expect(shadow).not_to have_field('Random', with: 'John', selector_format: :css)

expect(shadow).not_to have_field('First Name', with: 'Peter', selector_format: :css)
expect(shadow).not_to have_field('First Name', with: /eter$/, selector_format: :css)

shadow.fill_in('First Name', with: 'Jonas', selector_format: :css, fill_options: { clear: :backspace })
expect(shadow).to have_field('First Name', with: 'Jonas', selector_format: :css)
expect(shadow).to have_field('First Name', with: /ona/, selector_format: :css)

shadow.fill_in('First Name', with: 'Jonas', selector_format: :css, fill_options: { clear: :backspace })
expect(shadow).not_to have_field('First Name', with: 'John', selector_format: :css)
expect(shadow).not_to have_field('First Name', with: /John|Paul|George|Ringo/, selector_format: :css)

# shadow.fill_in('First Name', with: 'Thomas', selector_format: :css, fill_options: { clear: :backspace})
# expect do
# expect(shadow).to have_field('First Name', with: 'Jonas', selector_format: :css)
# end.to raise_exception(RSpec::Expectations::ExpectationNotMetError, /Expected value to be "Jonas" but was "Thomas"/)
#
# expect do
# expect(shadow).to have_field('First Name', readonly: true, selector_format: :css)
# end.to raise_exception(RSpec::Expectations::ExpectationNotMetError, /Expected readonly true but it wasn't/)
#
# # inherited boolean node filter
# expect do
# expect(shadow).to have_field('form_pets_cat', checked: true, selector_format: :css)
# end.to raise_exception(RSpec::Expectations::ExpectationNotMetError, /Expected checked true but it wasn't/)
#
# expect(shadow).to have_field('First Name', type: 'text', selector_format: :css)
#
# expect(shadow).not_to have_field('First Name', type: 'textarea', selector_format: :css)
#
# expect(shadow).to have_field('form[data]', with: 'TWTW', type: 'hidden', selector_format: :css)
#
# expect(shadow).to have_field('Html5 Multiple Email', multiple: true, selector_format: :css)
#
# expect(shadow).not_to have_field('Html5 Multiple Email', multiple: false, selector_format: :css)
#
# shadow.fill_in 'required', with: 'something', selector_format: :css, fill_options: { clear: :backspace}
# shadow.fill_in 'length', with: 'abcd', selector_format: :css, fill_options: { clear: :backspace}
#
# expect(shadow).to have_field('required', valid: true, selector_format: :css)
# expect(shadow).to have_field('length', valid: true, selector_format: :css)
#
# expect(shadow).not_to have_field('required', valid: true, selector_format: :css)
# expect(shadow).to have_field('required', valid: false, selector_format: :css)
#
# shadow.fill_in 'length', with: 'abc', selector_format: :css, fill_options: { clear: :backspace}
# expect(shadow).not_to have_field('length', valid: true, selector_format: :css)
end
end
1 change: 1 addition & 0 deletions spec/rack_test_spec.rb
Expand Up @@ -26,6 +26,7 @@ module TestSessions
css
scroll
spatial
shadow
]
Capybara::SpecHelper.run_specs TestSessions::RackTest, 'RackTest', capybara_skip: skipped_tests do |example|
case example.metadata[:full_description]
Expand Down
2 changes: 1 addition & 1 deletion spec/selenium_spec_chrome.rb
Expand Up @@ -58,7 +58,7 @@ module TestSessions
Chrome = Capybara::Session.new(CHROME_DRIVER, TestApp)
end

skipped_tests = %i[response_headers status_code trigger]
skipped_tests = %i[response_headers status_code trigger shadow]

Capybara::SpecHelper.log_selenium_driver_version(Selenium::WebDriver::Chrome) if ENV['CI']

Expand Down

0 comments on commit b69a1f3

Please sign in to comment.