teamcapybara/capybara · error · ArgumentError

:cols must be an Array of Arrays

Error message

:cols must be an Array of Arrays

What it means

The :table selector's :cols expression filter describes table contents column-by-column: it expects an Array whose elements are themselves Arrays (one inner array per column; the filter transposes them into rows to build XPath conditions). If any element is not an Array - strings, a flat list, or a nested scalar - the filter raises ArgumentError demanding 'an Array of Arrays'.

Source

Thrown at lib/capybara/selector/definition/table.rb:46

      else
        cells_xp = col.reduce(nil) do |prev_cell, cell_str|
          cell_condition = XPath.string.n.is(cell_str)

          if prev_cell
            prev_cell = XPath.ancestor(:tr)[1].preceding_sibling(:tr).join(prev_cell)
            cell_condition &= (prev_cell & prev_col_position?(prev_cell))
          end

          XPath.descendant(:td)[cell_condition]
        end
        XPath.descendant(:tr).join(cells_xp)
      end
    end.reduce(:&)
    xpath[col_conditions]
  end

  expression_filter(:cols, valid_values: [Array]) do |xpath, cols|
    raise ArgumentError, ':cols must be an Array of Arrays' unless cols.all?(Array)

    rows = cols.transpose
    col_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
    xpath[match_row_count(rows.size)][col_conditions]
  end

  expression_filter(:with_rows, valid_values: [Array]) do |xpath, rows|
    rows_conditions = rows.map { |row| match_row(row) }.reduce(:&)
    xpath[rows_conditions]
  end

  expression_filter(:rows, valid_values: [Array]) do |xpath, rows|
    rows_conditions = rows.map { |row| match_row(row, match_size: true) }.reduce(:&)
    xpath[match_row_count(rows.size)][rows_conditions]
  end

  describe_expression_filters do |caption: nil, **|
    " with caption \"#{caption}\"" if caption

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Wrap each column in its own array: cols: [%w[Name John], %w[Age 30]] (each inner array is one column's cell contents, top to bottom).
  2. If your data is row-oriented, use the row-wise filters instead: find(:table, 'Users', rows: [%w[Name Age], %w[John 30]]) or with_rows:.
  3. For a single column, still nest it: cols: [%w[Total]] or cols: [['Total', 'Sum']].
  4. Validate fixture-derived structures with cols.all?(Array) (or an arity/type check) before the query so failures point at the data.

Example fix

# before
find(:table, 'Users', cols: %w[Name Age])

# after
find(:table, 'Users', cols: [%w[Name John], %w[Age 30]])
Defensive patterns

Strategy: validation

Validate before calling

cols = [%w[Name John], %w[Age 30]]
raise ArgumentError, ':cols must be an Array of Arrays' unless cols.is_a?(Array) && cols.all?(Array)
find(:table, 'Users', cols: cols)

Type guard

def cols_shape?(value)
  value.is_a?(Array) && value.all?(Array)
end

Prevention

When it happens

Trigger: find(:table, 'Users', cols: %w[Name Age]); find(:table, 'Matrix', cols: 'Name'); find(:table, 'Users', cols: [['Name', 'Age'], 'foo']) - the first two (and the third's last element) fail cols.all?(Array).

Common situations: Passing row-oriented data to :cols by confusing it with the sibling :rows / :with_rows filters (which accept flat inner values row-wise); forgetting the outer wrapping for a single column; generating the structure from JSON/YAML fixtures whose nesting differs.

Understand the failure class

Background: Invalid argument type errors: "must be of type string", "expected X, got Y", and ERR_INVALID_ARG_TYPE explained — this error's family across 15 libraries.

Related errors


AI-assisted analysis of teamcapybara/capybara@15b5fdb76e (2026-08-21). Data as JSON: /api/errors/186162546560f6c0. Report an issue: GitHub.