teamcapybara/capybara · error · ArgumentError

Invalid CSS Selector - Block end '#{final}' not found

Error message

Invalid CSS Selector - Block end '#{final}' not found

What it means

Capybara::Selector::CSS is the small parser Capybara uses to split/extend CSS selectors (for example when appending class or attribute conditions to a CSS locator). While scanning a bracket/paren block it consumes characters until the expected closing delimiter; hitting end-of-input first means the selector is unbalanced, and it raises ArgumentError naming the missing 'block end' character (e.g. ']' for an attribute selector or ')' for a functional pseudo-class).

Source

Thrown at lib/capybara/selector/css.rb:83

        def parse_paren(strio)
          parse_block('(', ')', strio)
        end

        def parse_block(start, final, strio)
          block = start
          while (char = strio.getc)
            case char
            when final
              return block + char
            when '\\'
              block += char + strio.getc
            when '"', "'"
              block += parse_string(char, strio)
            else
              block += char
            end
          end
          raise ArgumentError, "Invalid CSS Selector - Block end '#{final}' not found"
        end

        def parse_string(quote, strio)
          string = quote
          while (char = strio.getc)
            string += char
            case char
            when quote
              return string
            when '\\'
              string += strio.getc
            end
          end
          raise ArgumentError, 'Invalid CSS Selector - string end not found'
        end
      end
    end
  end

View on GitHub (pinned to 15b5fdb76e)

Solutions

  1. Balance the selector - every '(' and '[' opened must be closed in order.
  2. When building selectors from data, pass values as option hashes (class:, id:, text:) so Capybara escapes them, instead of interpolating raw strings into CSS.
  3. If a selector comes from external data, validate/parse it first (e.g. Nokogiri::CSS.parse) to fail with a clearer message before Capybara's splitting stage.
  4. Check for smart quotes or stray escapes when the selector text was copied out of a browser console or email.

Example fix

# before
find(:css, "div[data-id='42'")

# after
find(:css, "div[data-id='42']")
Defensive patterns

Strategy: validation

Validate before calling

require 'nokogiri'

def balanced_css?(selector)
  Nokogiri::CSS.parse(selector)
  true
rescue Nokogiri::CSS::SyntaxError
  false
end

selector = "div[data-id='42'"
raise ArgumentError, "unbalanced CSS: #{selector}" unless balanced_css?(selector)

Type guard

def balanced_delimiters?(sel)
  stack = []
  sel.each_char do |c|
    stack.push(c) if %w[( [ {].include?(c)
    return false if %w[) ] }].include?(c) && stack.pop != { ')' => '(', ']' => '[', '}' => '{' }[c]
  end
  stack.empty?
end

Prevention

When it happens

Trigger: find(:css, 'div[data-id=\'42\''); find(:css, 'a:not(.external'); find(:css, 'tr:nth-child(2'); dynamically concatenated selectors where an interpolation dropped the closing bracket; class/attribute option values containing unbalanced brackets that get embedded into the CSS.

Common situations: Selector strings built by string interpolation or slicing (truncation cuts the tail); copy-paste from HTML/ERB that loses a character; data-driven selectors from fixtures or CSV; regex captures fed into find(:css, ...).

Related errors


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