Skip to content

Commit

Permalink
New API for working with windows (switching/finding/closing/opening/e…
Browse files Browse the repository at this point in the history
…tc.)
  • Loading branch information
abotalov committed May 26, 2014
1 parent 3c5dd37 commit adda9b0
Show file tree
Hide file tree
Showing 23 changed files with 1,046 additions and 99 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,21 @@ within_table('Employee') do
end
```

### Working with windows

Capybara provides some methods to ease finding and switching windows:

```ruby
facebook_window = window_opened_by do
click_button 'Like'
end
within_window facebook_window do
find('#login_email').set('[email protected]')
find('#login_password').set('qwerty')
click_button 'Submit'
end
```

### Scripting

In drivers which support it, you can easily execute JavaScript:
Expand Down
4 changes: 2 additions & 2 deletions capybara.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ Gem::Specification.new do |s|
s.add_development_dependency("cucumber", [">= 0.10.5"])
s.add_development_dependency("rake")
s.add_development_dependency("pry")

if RUBY_ENGINE == 'rbx' then
s.add_development_dependency("racc")
s.add_development_dependency("json")
s.add_development_dependency("rubysl")
end

if File.exist?("gem-private_key.pem")
s.signing_key = 'gem-private_key.pem'
end
Expand Down
3 changes: 3 additions & 0 deletions lib/capybara.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class FileNotFound < CapybaraError; end
class UnselectNotAllowed < CapybaraError; end
class NotSupportedByDriverError < CapybaraError; end
class InfiniteRedirectError < CapybaraError; end
class ScopeError < CapybaraError; end
class WindowError < CapybaraError; end

class << self
attr_accessor :asset_host, :app_host, :run_server, :default_host, :always_include_port
Expand Down Expand Up @@ -316,6 +318,7 @@ module Selenium; end
require 'capybara/helpers'
require 'capybara/session'
require 'capybara/dsl'
require 'capybara/window'
require 'capybara/server'
require 'capybara/selector'
require 'capybara/query'
Expand Down
34 changes: 33 additions & 1 deletion lib/capybara/driver/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,42 @@ def within_frame(frame_handle)
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#within_frame'
end

def within_window(handle)
def current_window_handle
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#current_window_handle'
end

def current_window_size
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#window_size'
end

def resize_current_window_to(width, height)
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#resize_window_to'
end

def close_current_window
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#close_window'
end

def window_handles
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#window_handles'
end

def open_new_window
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#open_new_window'
end

def switch_to_window(handle)
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#switch_to_window'
end

def within_window(locator)
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#within_window'
end

def no_such_window_error
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#no_such_window_error'
end

def invalid_element_errors
[]
end
Expand Down
12 changes: 8 additions & 4 deletions lib/capybara/node/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,13 @@ def reload
# Capybara will raise `Capybara::FrozenInTime`.
#
# @param [Integer] seconds Number of seconds to retry this block
# @param options [Hash]
# @option options [Array<Exception>] :errors (driver.invalid_element_errors +
# [Capybara::ElementNotFound]) exception types that cause the block to be rerun
# @return [Object] The result of the given block
# @raise [Capybara::FrozenInTime] If the return value of `Time.now` appears stuck
#
def synchronize(seconds=Capybara.default_wait_time)
def synchronize(seconds=Capybara.default_wait_time, options = {})
start_time = Time.now

if session.synchronized
Expand All @@ -82,7 +85,7 @@ def synchronize(seconds=Capybara.default_wait_time)
rescue => e
session.raise_server_error!
raise e unless driver.wait?
raise e unless catch_error?(e)
raise e unless catch_error?(e, options[:errors])
raise e if (Time.now - start_time) >= seconds
sleep(0.05)
raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if Time.now == start_time
Expand All @@ -96,8 +99,9 @@ def synchronize(seconds=Capybara.default_wait_time)

protected

def catch_error?(error)
(driver.invalid_element_errors + [Capybara::ElementNotFound]).any? do |type|
def catch_error?(error, errors = nil)
errors ||= (driver.invalid_element_errors + [Capybara::ElementNotFound])
errors.any? do |type|
error.is_a?(type)
end
end
Expand Down
37 changes: 37 additions & 0 deletions lib/capybara/rspec/matchers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,33 @@ def description
end
end

class BecomeClosed
def initialize(options)
@wait_time = Capybara::Query.new(options).wait
end

def matches?(window)
@window = window
start_time = Time.now
while window.exists? && (Time.now - start_time) < @wait_time
sleep 0.05
end
window.closed?
end

def failure_message
"expected #{@window.inspect} to become closed after #{@wait_time} seconds"
end

def failure_message_when_negated
"expected #{@window.inspect} not to become closed after #{@wait_time} seconds"
end

# RSpec 2 compatibility:
alias_method :failure_message_for_should, :failure_message
alias_method :failure_message_for_should_not, :failure_message_when_negated
end

def have_selector(*args)
HaveSelector.new(*args)
end
Expand Down Expand Up @@ -157,5 +184,15 @@ def have_select(locator, options={})
def have_table(locator, options={})
HaveSelector.new(:table, locator, options)
end

##
# Wait for window to become closed.
# @example
# expect(window).to become_closed(wait: 0.8)
# @param options [Hash] optional param
# @option options [Numeric] :wait (Capybara.default_wait_time) wait time
def become_closed(options = {})
BecomeClosed.new(options)
end
end
end
55 changes: 45 additions & 10 deletions lib/capybara/selenium/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,24 +126,56 @@ def within_frame(frame_handle)
@frame_handles[browser.window_handle].each { |fh| browser.switch_to.frame(fh) }
end

def find_window( selector )
def current_window_handle
browser.window_handle
end

def current_window_size
size = browser.manage.window.size
[size.width, size.height]
end

def resize_current_window_to(width, height)
browser.manage.window.resize_to(width, height)
end

def close_current_window
browser.close
end

def window_handles
browser.window_handles
end

def open_new_window
browser.execute_script('window.open();')
end

def switch_to_window(handle)
browser.switch_to.window handle
end

# @api private
def find_window(locator)
handles = browser.window_handles
return locator if handles.include? locator

original_handle = browser.window_handle
browser.window_handles.each do |handle|
handles.each do |handle|
browser.switch_to.window handle
if( selector == browser.execute_script("return window.name") ||
browser.title.include?(selector) ||
browser.current_url.include?(selector) ||
(selector == handle) )
if (locator == browser.execute_script("return window.name") ||
browser.title.include?(locator) ||
browser.current_url.include?(locator))
browser.switch_to.window original_handle
return handle
end
end
raise Capybara::ElementNotFound, "Could not find a window identified by #{selector}"
raise Capybara::ElementNotFound, "Could not find a window identified by #{locator}"
end

def within_window(selector, &blk)
handle = find_window( selector )
browser.switch_to.window(handle, &blk)
def within_window(locator)
handle = find_window(locator)
browser.switch_to.window(handle) { yield }
end

def quit
Expand All @@ -158,4 +190,7 @@ def invalid_element_errors
[Selenium::WebDriver::Error::StaleElementReferenceError, Selenium::WebDriver::Error::UnhandledError, Selenium::WebDriver::Error::ElementNotVisibleError]
end

def no_such_window_error
Selenium::WebDriver::Error::NoSuchWindowError
end
end
Loading

0 comments on commit adda9b0

Please sign in to comment.