Skip to content

Cuprite support#157

Open
myabc wants to merge 5 commits into
citizensadvice:mainfrom
opf:feature/cuprite-support
Open

Cuprite support#157
myabc wants to merge 5 commits into
citizensadvice:mainfrom
opf:feature/cuprite-support

Conversation

@myabc

@myabc myabc commented Nov 13, 2025

Copy link
Copy Markdown

Summary

Adds Cuprite (Ferrum/CDP-based headless Chrome) as a supported Capybara driver alongside Selenium and rack_test.

The accessibility methods (accessible_name, accessible_description, role) and the :role selector previously only worked under Selenium and rack_test. This wires up a Cuprite path for each, resolving computed values from Chrome's accessibility tree via CDP (Accessibility.getPartialAXTree), and fixes a couple of driver-behaviour differences that affected the disclosure and rich-text selectors.

All existing specs run green on Cuprite — there are no Cuprite-skipped specs.

What's included

Driver wiring

  • New Cuprite::AccessibilityComputedValue resolves a node's computed name/role/description from the CDP accessibility tree.
  • accessible_name / role read those computed values; accessible_description reuses the existing accname-1.2 description algorithm (it only depends on the generic Capybara node API, so it is driver-agnostic) — this keeps description results consistent with Selenium rather than returning Chrome's raw value.
  • Cuprite extensions are mixed in only when Cuprite is loaded, so it is not a runtime dependency of the gem.

Cross-driver behaviour fixes

  • Disclosuredetails[:open] is a boolean attribute whose raw value differs per driver (Selenium "true"/nil, Cuprite true/false). The summary toggle now normalises it before comparing, so re-toggling an already-open disclosure no longer misfires under Cuprite.
  • Rich text — Cuprite/Ferrum cannot reliably trigger a select-all chord (mod+a) inside a contenteditable, so fill_in_rich_text's clear was a no-op and text was appended instead of replaced. Under Cuprite it now moves the caret to the end and backspaces through the content; Selenium keeps the faster chord.
  • Minor robustness: the CDP resolver guards a missing accessibility tree / CDP error and degrades to an empty value, mirroring the Selenium rescue paths; accessible_name collapses whitespace to match the Selenium path.

Tooling / docs

  • CI now runs the suite against Cuprite (DRIVER=cuprite_chrome_headless) in addition to Chromium, Firefox, and rack_test.
  • README documents that Cuprite is optional and must be required before capybara_accessible_selectors for the extensions to be applied.

Load-order requirement

Because Cuprite is optional, its extensions are only installed if Capybara::Cuprite::Node is already defined at require time:

require "capybara/cuprite"
require "capybara_accessible_selectors"

This is documented in the README. A lazy/registration-based hook to remove the ordering constraint was considered but intentionally left out of scope.

Test plan

  • bundle exec rspec (default Selenium Chromium driver) — 0 failures
  • DRIVER=selenium_headless bundle exec rspec (Firefox) — 0 failures
  • DRIVER=cuprite_chrome_headless bundle exec rspec — 0 failures, 0 skips
  • DRIVER=rack_test bundle exec rspec — 0 failures
  • bundle exec rubocop

Upstream Ferrum API (follow-up)

The Cuprite path currently reaches into raw CDP (Accessibility.getPartialAXTree) because Ferrum exposes no public accessibility API. rubycdp/ferrum#595 adds a first-class page.accessibility domain plus a node.axnode convenience returning an AXNode value object (name/role/description).

Validated locally against that branch: Cuprite::AccessibilityComputedValue collapses to node.axnode&.public_send(name), dropping the raw-CDP coupling, ignored-node filtering, and hash-digging. Full suite stays green (1813 examples, 0 failures).

Once #595 ships in a Ferrum release, this PR's resolver can be simplified and the dependency constraint bumped as a follow-up.

Comment thread lib/capybara_accessible_selectors/node.rb Outdated
Comment thread spec/spec_helper.rb Outdated
Comment thread lib/capybara_accessible_selectors/node/accessible_description.rb Outdated
@myabc

myabc commented Apr 3, 2026

Copy link
Copy Markdown
Author

@seanpdoyle thanks for your feedback and apologies for dropping the ball! I'd still love to see Cuprite support. I'll see if I can address the various comments in the next week or so.

@myabc myabc force-pushed the feature/cuprite-support branch 2 times, most recently from 58bb24e to 88d327c Compare April 4, 2026 16:33
@myabc myabc marked this pull request as ready for review April 4, 2026 16:33
Copilot AI review requested due to automatic review settings April 4, 2026 16:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds initial support for running this project’s Capybara accessible selectors against the Cuprite (Ferrum/CDP) driver by introducing Cuprite-based computed accessibility queries and wiring Cuprite into the test suite.

Changes:

  • Add Cuprite driver registration in the RSpec spec helper and introduce driver-based skips for known-incompatible specs.
  • Implement Cuprite-backed accessible_name, accessible_description, and role resolution using a new AccessibilityComputedValue helper.
  • Update rich text filling to avoid sending nil/empty values, and add Cuprite to the bundle.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
spec/spec_helper.rb Optionally requires Cuprite and registers a Cuprite headless driver for specs.
spec/selectors/rich_text_spec.rb Skips specific rich-text behavior tests for Cuprite.
spec/selectors/disclosure_spec.rb Skips specific disclosure toggle/select tests for Cuprite.
spec/capybara/node/element/accessible_description_spec.rb Skips specific accessible-description expectations for Cuprite.
lib/capybara_accessible_selectors/selectors/rich_text.rb Avoids sending nil text and removes Selenium platform dependency for modifier key detection.
lib/capybara_accessible_selectors/rspec/matchers/have_validation_errors.rb Adjusts outerHTML retrieval to support drivers without attribute('outerHTML').
lib/capybara_accessible_selectors/node/role.rb Adds Cuprite-specific role resolution.
lib/capybara_accessible_selectors/node/accessible_name.rb Adds Cuprite-specific accessible name resolution.
lib/capybara_accessible_selectors/node/accessible_description.rb Adds Cuprite-specific accessible description resolution.
lib/capybara_accessible_selectors/node.rb Includes Cuprite node extensions when Cuprite is already loaded.
lib/capybara_accessible_selectors/cuprite/accessibility_computed_value.rb New helper to query computed accessibility values via CDP.
Gemfile / Gemfile.lock Adds Cuprite dependency and updates the lockfile.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/capybara_accessible_selectors/node/role.rb Outdated
Comment thread lib/capybara_accessible_selectors/node/accessible_name.rb Outdated
Comment thread lib/capybara_accessible_selectors/node/accessible_description.rb Outdated
Comment thread lib/capybara_accessible_selectors/cuprite/accessibility_computed_value.rb Outdated
Comment thread lib/capybara_accessible_selectors/node.rb
@myabc myabc force-pushed the feature/cuprite-support branch 2 times, most recently from 89ae084 to 810bde3 Compare April 4, 2026 17:12
# So we can't just use backspace
input.send_keys [command_modifier, "a"], :backspace if input.text != "" && clear
input.send_keys with
input.send_keys with if with

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: would be good to fix upstream!

Comment thread spec/spec_helper.rb
Comment on lines +11 to +15
begin
require "capybara/cuprite"
rescue LoadError
# Cuprite is optional
end

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is necessary - it might be ok to make Cuprite (and all Capybara drivers) test dependencies.

@myabc myabc force-pushed the feature/cuprite-support branch from 810bde3 to 88f299b Compare June 27, 2026 18:24
# The accname-1.2 description algorithm is driver-agnostic (it only uses
# the generic Capybara node API), so reuse it instead of Chrome's raw
# computed description, keeping results consistent with Selenium
Selenium::AccessibleDescription.resolve(self) || ""

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

N.B. we might consider renaming this module to Capybara::AccessibleDescription

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 15 changed files in this pull request and generated 2 comments.

myabc added 4 commits June 27, 2026 20:44
Add Cuprite (Chrome DevTools Protocol) as an alternative browser driver.
Uses Chrome's Accessibility.getPartialAXTree CDP command to resolve
accessible name, description, and role values.

- Add CupriteNodeExtensions with defined? guard for optional dependency
- Add AccessibilityComputedValue resolver for CDP accessibility tree
- Handle Chrome's non-standard PascalCase role identifiers
- Guard Cuprite require/registration in spec_helper
- Use RUBY_PLATFORM for platform detection (no hard Selenium dependency)
- Use evaluate_script for outerHTML access across drivers
- Add send_keys nil guards for rich text

Fixes citizensadvice#156
Annotate specs with skip_driver: :cuprite_chrome where Cuprite's
send_keys handling of modifier keys differs from Selenium, or where
the <details> element interaction behaves differently.

- 2 disclosure toggle/select specs (details element interaction)
- 8 rich text fill/replace/within specs (modifier key handling)
details[:open] is a boolean attribute whose raw value differs per driver
(Selenium returns "true"/nil, Cuprite returns true/false). The summary
toggle compared it against the desired state as a string, so on Cuprite a
re-toggle of an already-open disclosure misfired and closed it.

Normalise the value before comparing, and un-skip the two disclosure
specs that exercised re-toggling.
fill_in_rich_text cleared existing content with a select-all chord
(mod+a) followed by backspace. Cuprite/Ferrum cannot reliably trigger a
select-all chord inside a contenteditable, so the clear was a no-op and
new text was appended instead of replacing.

For Cuprite, move the caret to the end and backspace through the content
instead (matching the approach used elsewhere for Ferrum); keep the
chord for Selenium. Un-skip the eight rich text specs that exercised
clearing or replacing content.
@myabc myabc force-pushed the feature/cuprite-support branch from 4be2e5b to 8442774 Compare June 27, 2026 18:44
@myabc

myabc commented Jun 27, 2026

Copy link
Copy Markdown
Author

@seanpdoyle please give this a look when you get a chance! With the help of Claude I was able to improve robustness and work through the remaining skipped specs. All specs should now pass on Cuprite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants