Back to Repositories

Testing Scroll Position Management Implementation in teamcapybara/capybara

This test suite validates the scroll functionality in Capybara, covering comprehensive scroll behavior for both viewport and scrollable elements. It ensures precise scrolling capabilities with different alignment options and specific positioning requirements.

Test Coverage Overview

The test suite provides extensive coverage of scrolling functionality in Capybara, including:

  • Viewport scrolling with top, bottom, and center alignment
  • Element-specific scrolling within scrollable containers
  • Precise positioning validation using getBoundingClientRect()
  • Offset-based scrolling with relative positioning

Implementation Analysis

The testing approach employs Capybara’s session management and JavaScript evaluation capabilities. Tests utilize precise mathematical calculations to verify scroll positions, with tolerance accounting for browser variations.

Key patterns include:
  • Browser-agnostic scroll position detection
  • Dynamic viewport calculations
  • Scrollable element manipulation

Technical Details

Testing tools and setup include:
  • Capybara test framework with JavaScript support
  • Browser DOM API integration
  • RSpec expectation matchers
  • Custom scrolling implementation requiring [:scroll] capability

Best Practices Demonstrated

The test suite exemplifies high-quality testing practices through:

  • Comprehensive edge case coverage
  • Precise numerical validations with appropriate tolerances
  • Clear test isolation and setup
  • Consistent verification patterns

teamcapybara/capybara

lib/capybara/spec/session/scroll_spec.rb

            
# frozen_string_literal: true

Capybara::SpecHelper.spec '#scroll_to', requires: [:scroll] do
  before do
    @session.visit('/scroll')
  end

  it 'can scroll an element to the top of the viewport' do
    el = @session.find(:css, '#scroll')
    @session.scroll_to(el, align: :top)
    expect(el.evaluate_script('this.getBoundingClientRect().top')).to be_within(1).of(0)
  end

  it 'can scroll an element to the bottom of the viewport' do
    el = @session.find(:css, '#scroll')
    @session.scroll_to(el, align: :bottom)
    el_bottom = el.evaluate_script('this.getBoundingClientRect().bottom')
    viewport_bottom = el.evaluate_script('document.documentElement.clientHeight')
    expect(el_bottom).to be_within(1).of(viewport_bottom)
  end

  it 'can scroll an element to the center of the viewport' do
    el = @session.find(:css, '#scroll')
    @session.scroll_to(el, align: :center)
    el_center = el.evaluate_script('(function(rect){return (rect.top + rect.bottom)/2})(this.getBoundingClientRect())')
    viewport_bottom = el.evaluate_script('document.documentElement.clientHeight')
    expect(el_center).to be_within(2).of(viewport_bottom / 2)
  end

  it 'can scroll the window to the vertical top' do
    @session.scroll_to :bottom
    @session.scroll_to :top
    expect(@session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')).to eq [0, 0]
  end

  it 'can scroll the window to the vertical bottom' do
    @session.scroll_to :bottom
    max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.documentElement.clientHeight')
    scrolled_location_x, scrolled_location_y = @session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')
    expect(scrolled_location_x).to be_within(0.5).of(0)
    expect(scrolled_location_y).to be_within(0.5).of(max_scroll)
  end

  it 'can scroll the window to the vertical center' do
    @session.scroll_to :center
    max_scroll = @session.evaluate_script('document.documentElement.scrollHeight - document.documentElement.clientHeight')
    expect(@session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')).to eq [0, max_scroll / 2]
  end

  it 'can scroll the window to specific location' do
    @session.scroll_to 100, 100
    expect(@session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')).to eq [100, 100]
  end

  it 'can scroll an element to the top of the scrolling element' do
    scrolling_element = @session.find(:css, '#scrollable')
    el = scrolling_element.find(:css, '#inner')
    scrolling_element.scroll_to(el, align: :top)
    scrolling_element_top = scrolling_element.evaluate_script('this.getBoundingClientRect().top')
    expect(el.evaluate_script('this.getBoundingClientRect().top')).to be_within(3).of(scrolling_element_top)
  end

  it 'can scroll an element to the bottom of the scrolling element' do
    scrolling_element = @session.find(:css, '#scrollable')
    el = scrolling_element.find(:css, '#inner')
    scrolling_element.scroll_to(el, align: :bottom)
    el_bottom = el.evaluate_script('this.getBoundingClientRect().bottom')
    scroller_bottom = scrolling_element.evaluate_script('this.getBoundingClientRect().top + this.clientHeight')
    expect(el_bottom).to be_within(1).of(scroller_bottom)
  end

  it 'can scroll an element to the center of the scrolling element' do
    scrolling_element = @session.find(:css, '#scrollable')
    el = scrolling_element.find(:css, '#inner')
    scrolling_element.scroll_to(el, align: :center)
    el_center = el.evaluate_script('(function(rect){return (rect.top + rect.bottom)/2})(this.getBoundingClientRect())')
    scrollable_center = scrolling_element.evaluate_script('(this.clientHeight / 2) + this.getBoundingClientRect().top')
    expect(el_center).to be_within(1).of(scrollable_center)
  end

  it 'can scroll the scrolling element to the top' do
    scrolling_element = @session.find(:css, '#scrollable')
    scrolling_element.scroll_to :bottom
    scrolling_element.scroll_to :top
    expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [0, 0]
  end

  it 'can scroll the scrolling element to the bottom' do
    scrolling_element = @session.find(:css, '#scrollable')
    scrolling_element.scroll_to :bottom
    max_scroll = scrolling_element.evaluate_script('this.scrollHeight - this.clientHeight')
    expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [0, max_scroll]
  end

  it 'can scroll the scrolling element to the vertical center' do
    scrolling_element = @session.find(:css, '#scrollable')
    scrolling_element.scroll_to :center
    max_scroll = scrolling_element.evaluate_script('this.scrollHeight - this.clientHeight')
    expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [0, max_scroll / 2]
  end

  it 'can scroll the scrolling element to specific location' do
    scrolling_element = @session.find(:css, '#scrollable')
    scrolling_element.scroll_to 100, 100
    expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [100, 100]
  end

  it 'can scroll the window by a specific amount' do
    @session.scroll_to(:current, offset: [50, 75])
    expect(@session.evaluate_script('[window.scrollX || window.pageXOffset, window.scrollY || window.pageYOffset]')).to eq [50, 75]
  end

  it 'can scroll the scroll the scrolling element by a specific amount' do
    scrolling_element = @session.find(:css, '#scrollable')
    scrolling_element.scroll_to 100, 100
    scrolling_element.scroll_to(:current, offset: [-50, 50])
    expect(scrolling_element.evaluate_script('[this.scrollLeft, this.scrollTop]')).to eq [50, 150]
  end
end