Back to Repositories

Testing Frame Navigation and Context Management in Capybara

This test suite validates frame handling functionality in Capybara, focusing on the within_frame feature for interacting with nested iframes and frame content. The tests ensure proper frame context switching and element access across multiple frame scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of frame interactions including:

  • Single frame navigation and content verification
  • Multiple nested frame handling
  • Frame scope management and reset
  • Dynamic frame removal scenarios
  • Frame selection using different locator strategies

Implementation Analysis

The testing approach utilizes Capybara’s within_frame DSL to validate frame context switching. The implementation demonstrates various frame selection methods including element references, CSS selectors, and named frames. Tests verify both synchronous and asynchronous frame operations with particular attention to scope management.

Technical Details

  • Testing Framework: RSpec with Capybara
  • Key Features: Frame navigation, element location, async operations
  • Test Prerequisites: :frames and :js capabilities
  • Primary Methods: within_frame, find, has_selector?

Best Practices Demonstrated

The test suite exemplifies robust frame testing practices including:

  • Isolation of frame contexts
  • Proper cleanup and scope management
  • Handling of dynamic frame removal
  • Multiple selector strategy support
  • Error scenario validation

teamcapybara/capybara

lib/capybara/spec/session/frame/within_frame_spec.rb

            
# frozen_string_literal: true

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

  it 'should find the div in frameOne' do
    @session.within_frame('frameOne') do
      expect(@session.find("//*[@id='divInFrameOne']").text).to eql 'This is the text of divInFrameOne'
    end
  end

  it 'should find the div in FrameTwo' do
    @session.within_frame('frameTwo') do
      expect(@session.find("//*[@id='divInFrameTwo']").text).to eql 'This is the text of divInFrameTwo'
    end
  end

  it 'should find the text div in the main window after finding text in frameOne' do
    @session.within_frame('frameOne') do
      expect(@session.find("//*[@id='divInFrameOne']").text).to eql 'This is the text of divInFrameOne'
    end
    expect(@session.find("//*[@id='divInMainWindow']").text).to eql 'This is the text for divInMainWindow'
  end

  it 'should find the text div in the main window after finding text in frameTwo' do
    @session.within_frame('frameTwo') do
      expect(@session.find("//*[@id='divInFrameTwo']").text).to eql 'This is the text of divInFrameTwo'
    end
    expect(@session.find("//*[@id='divInMainWindow']").text).to eql 'This is the text for divInMainWindow'
  end

  it 'should return the result of executing the block' do
    expect(@session.within_frame('frameOne') { 'return value' }).to eql 'return value'
  end

  it 'should find the div given Element' do
    element = @session.find(:id, 'frameOne')
    @session.within_frame element do
      expect(@session.find("//*[@id='divInFrameOne']").text).to eql 'This is the text of divInFrameOne'
    end
  end

  it 'should find the div given selector and locator' do
    @session.within_frame(:css, '#frameOne') do
      expect(@session.find("//*[@id='divInFrameOne']").text).to eql 'This is the text of divInFrameOne'
    end
  end

  it 'should default to the :frame selector kind when only options passed' do
    @session.within_frame(name: 'my frame one') do
      expect(@session.find("//*[@id='divInFrameOne']").text).to eql 'This is the text of divInFrameOne'
    end
  end

  it 'should default to the :frame selector when no options passed' do
    container = @session.find(:css, '#divInMainWindow')
    @session.within(container) do
      # Ensure only one frame in scope
      @session.within_frame do
        expect(@session).to have_css('body#parentBody')
      end
    end
    expect do
      # Multiple frames in scope here
      # rubocop:disable Style/Semicolon
      @session.within_frame { ; }
      # rubocop:enable Style/Semicolon
    end.to raise_error Capybara::Ambiguous
  end

  it 'should find multiple nested frames' do
    @session.within_frame 'parentFrame' do
      @session.within_frame 'childFrame' do
        @session.within_frame 'grandchildFrame1' do
          # dummy
        end
        @session.within_frame 'grandchildFrame2' do
          # dummy
        end
      end
    end
  end

  it 'should reset scope when changing frames' do
    @session.within(:css, '#divInMainWindow') do
      @session.within_frame 'innerParentFrame' do
        expect(@session.has_selector?(:css, 'iframe#childFrame')).to be true
      end
    end
  end

  it 'works if the frame is closed', requires: %i[frames js] do
    @session.within_frame 'parentFrame' do
      @session.within_frame 'childFrame' do
        @session.click_link 'Close Window Now'
      end
      expect(@session).to have_selector(:css, 'body#parentBody')
      expect(@session).not_to have_selector(:css, '#childFrame')
    end
  end

  it 'works if the frame is closed with a slight delay', requires: %i[frames js] do
    @session.within_frame 'parentFrame' do
      @session.within_frame 'childFrame' do
        @session.click_link 'Close Window Soon'
        sleep 1
      end
      expect(@session).to have_selector(:css, 'body#parentBody')
      expect(@session).not_to have_selector(:css, '#childFrame')
    end
  end
end