From 31db40fae8b0e7b7ca03c851037bcb9067fd09ec Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Tue, 27 Oct 2026 06:15:14 +0100 Subject: [PATCH] Update tests for new content layout of index tables Use unreleased and custom :table_row selector features. --- Gemfile | 4 ++- Gemfile.lock | 26 +++++++++++-------- test/application_system_test_case.rb | 14 +++++++++-- test/system/units_test.rb | 37 +++++++++++++++++----------- 4 files changed, 54 insertions(+), 27 deletions(-) diff --git a/Gemfile b/Gemfile index 82753cd..fd5ced4 100644 --- a/Gemfile +++ b/Gemfile @@ -40,7 +40,9 @@ end group :test do # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] - gem "capybara" + # TODO: move back from git after this PR is included: + # https://github.com/teamcapybara/capybara/pull/2862 + gem "capybara", git: "https://github.com/teamcapybara/capybara", branch: "table_row-optional-header-and-integer-locator" gem "selenium-webdriver" end diff --git a/Gemfile.lock b/Gemfile.lock index 1b473a8..7d43450 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,6 +6,21 @@ GIT i18n (1.15.2) concurrent-ruby (~> 1.0) +GIT + remote: https://github.com/teamcapybara/capybara + revision: 3e9a2bff144fc5dc980e7f5c9dce31a13e3cad31 + branch: table_row-optional-header-and-integer-locator + specs: + capybara (3.40.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.11) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + GEM remote: https://rubygems.org/ specs: @@ -92,15 +107,6 @@ GEM builder (3.3.0) byebug (13.0.0) reline (>= 0.6.0) - capybara (3.40.0) - addressable - matrix - mini_mime (>= 0.1.3) - nokogiri (~> 1.11) - rack (>= 1.6.0) - rack-test (>= 0.6.3) - regexp_parser (>= 1.5, < 3.0) - xpath (~> 3.2) concurrent-ruby (1.3.8) connection_pool (3.0.2) crass (1.0.7) @@ -318,7 +324,7 @@ PLATFORMS DEPENDENCIES byebug - capybara + capybara! devise (~> 5.0.4) i18n! importmap-rails diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb index 972340d..d6881c6 100644 --- a/test/application_system_test_case.rb +++ b/test/application_system_test_case.rb @@ -73,6 +73,11 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase rows_conditions = rows.map { |row| match_row(row) }.reduce(:&) xpath[match_row_count(rows.size)][rows_conditions] end + + # Override to count only data rows, to make it consistent with #match_row. + def match_row_count(size) + XPath.descendant(:tr).where(XPath.descendant(:td)).count.equals(size) + end end # This regex has to be only strict enough to properly reconstruct number. It @@ -87,6 +92,7 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase # Based on Capybara :table and :table_row selectors. Capybara.add_selector(:table_cell, locator_type: [String, Symbol]) do xpath do |locator| + # TODO: make sure to only include visible column headers column = XPath.string.n.is(locator.to_s) header_xp = XPath.ancestor(:table)[1].descendant(:tr)[1].descendant(:th)[column] position_xp = XPath.position.equals(header_xp.preceding_sibling.count.plus(1)) @@ -97,13 +103,15 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase value ? xpath.where(XPath.descendant(:input, :select, :textarea)) : xpath end - node_filter(:with) do |node, expected| + node_filter(:value) do |node, expected| value = case expected when Float node.text.match(NUMBER) { |m| "#{m['base'] || '1'}e#{m['exp']&.lstrip}".to_f } + when Integer + node.to_i else - node.text + node.text.presence || nil end (expected == value).tap do |result| add_error("Expected value to be #{expected.inspect}" \ @@ -114,6 +122,8 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase Capybara.modify_selector(:table_row) do # Beware: filters of #boolean? type are called within different context. + # TODO: add in separate FilterSet and import using #filter_set? + # TODO: replace node_filter with expression_filter `:focus-within`? node_filter(:with_focus, :boolean) do |node, value| node.has_selector?(':focus') == value end diff --git a/test/system/units_test.rb b/test/system/units_test.rb index ccc6c6f..60234c7 100644 --- a/test/system/units_test.rb +++ b/test/system/units_test.rb @@ -10,6 +10,7 @@ require "application_system_test_case" # * user with single unit, # * user with no units. # Users need to be active and confirmed. Units w/ and w/o description. +# TODO: use :table_row instead of css locators (tbody etc.), optionally without locator class UnitsTest < ApplicationSystemTestCase def sign_in(...) @@ -35,7 +36,7 @@ class UnitsTest < ApplicationSystemTestCase sign_in if @user.units.empty? - assert_selector :table, rows: [[t('units.no_items')]] + assert_selector :table, caption: t('units.index.no_items'), rows: [] else column = column(:symbol) assert_selector :table, rows_with: @user.units.map { |u| {column => u.symbol} } @@ -56,6 +57,8 @@ class UnitsTest < ApplicationSystemTestCase attributes = [:symbol, :description] attributes << :multiplier if action == :new_subunit + # TODO: verify form location and focus + # action == :new_subunit ? first(:table_row) : :table_row, after: {column(:symbol) => ...} within :table_row, {}, with_focus: true do attributes.map! do |name| field = find(:table_cell, column(name)).find(:fillable_field) @@ -90,13 +93,16 @@ class UnitsTest < ApplicationSystemTestCase multiplier = attributes.delete(:multiplier).to_f if action == :new_subunit within :table_row, attributes.transform_keys { |k| column(k) } do - assert_selector :table_cell, column(:multiplier), with: multiplier if multiplier + assert_selector :table_cell, column(:multiplier), value: multiplier end end assert_no_selector :link, disabled: true, exact_text: Regexp.union(link_labels.values) + # TODO: 50/50 open new form and check if alert is no longer visible? #91 + refresh + # TODO: change to assert_selector :table, rows: ... assert_equal new_symbols, list_symbols end @@ -132,23 +138,24 @@ class UnitsTest < ApplicationSystemTestCase sign_in(user: users.select { |u| u.confirmed? && u.units.many? }.sample) link = all(:link_or_button, exact_text: t('units.unit.destroy')).sample - symbol = link.ancestor('tr').first(:link).text - index = link.ancestor('tbody').all('tr').index { |e| e.first(:link).text == symbol } - unit = @user.units.find_by(symbol: symbol) + symbol = link.ancestor(:table_row).find(:table_cell, column(:symbol)).text + index = all(:table_cell, column(:symbol)).map(&:text).index(symbol) + unit = @user.units.find_sole_by(symbol: symbol) link.click assert_selector '.flash.notice' if unit.base_id? find_link(unit.base.symbol).ancestor('tr').click_on(t('units.unit.new_subunit')) - fill_in 'unit[multiplier]', with: unit.multiplier + find(:table_cell, column(:multiplier), fillable: true) + .fill_in with: unit.multiplier else click_on t('units.index.new_unit') end - fill_in 'unit[symbol]', with: unit.symbol + find(:table_cell, column(:symbol), fillable: true).fill_in with: unit.symbol click_on t('helpers.submit.create') - within "tbody > tr:nth-child(#{index+1})" do - assert_selector :link, exact_text: symbol + within :table_row, index+1 do + assert_selector :table_cell, column(:symbol), exact_text: symbol end end @@ -186,9 +193,11 @@ class UnitsTest < ApplicationSystemTestCase click_on t(:cancel) assert_no_selector '.flash.alert' + # TODO: change to assert_selector :table, rows: ... assert_equal symbols, list_symbols refresh assert_no_selector '.flash.alert' + # TODO: change to assert_selector :table, rows: ... assert_equal symbols, list_symbols end @@ -235,11 +244,11 @@ class UnitsTest < ApplicationSystemTestCase end # NOTE: extend with any add/edit link - test "new form closes on escape key" do + test "new and edit forms close on escape key" do sign_in click_on t('units.index.new_unit') - first('tbody > tr').all(:field).sample.send_keys :escape - within 'tbody' do + first(:table_row).all(:field).sample.send_keys :escape + within :table do assert_no_selector :fillable_field end end @@ -272,8 +281,8 @@ class UnitsTest < ApplicationSystemTestCase end end - assert_selector :table, rows: [[t('units.no_items')]] + assert_selector :table, caption: t('units.index.no_items'), rows: [] refresh - assert_selector :table, rows: [[t('units.no_items')]] + assert_selector :table, caption: t('units.index.no_items'), rows: [] end end