Merge pull request #1322 from jnicklas/modal

API for operation with modal dialogs
This commit is contained in:
Thomas Walpole 2014-06-30 23:24:46 -07:00
commit c589f6a814
14 changed files with 464 additions and 7 deletions

View File

@ -497,6 +497,44 @@ that this may break with more complicated expressions:
result = page.evaluate_script('4 + 4');
```
### Modals
In drivers which support it, you can accept, dismiss and respond to alerts, confirms and prompts.
You can accept or dismiss alert messages by wrapping the code that produces an alert in a block:
```ruby
accept_alert do
click_link('Show Alert')
end
```
You can accept or dismiss a confirmation by wrapping it in a block, as well:
```ruby
dismiss_confirm do
click_link('Show Confirm')
end
```
You can accept or dismiss prompts as well, and also provide text to fill in for the response:
```ruby
accept_prompt(with: 'Linus Torvalds') do
click_link('Show Prompt About Linux')
end
```
All modal methods return the message that was presented. So, you can access the prompt message
by assigning the return to a variable:
```ruby
message = accept_prompt(with: 'Linus Torvalds') do
click_link('Show Prompt About Linux')
end
expect(message).to eq('Who is the chief architect of Linux?')
```
### Debugging
It can be useful to take a snapshot of the page as it currently is and take a

View File

@ -7,6 +7,7 @@ module Capybara
class DriverNotFoundError < CapybaraError; end
class FrozenInTime < CapybaraError; end
class ElementNotFound < CapybaraError; end
class ModalNotFound < CapybaraError; end
class Ambiguous < ElementNotFound; end
class ExpectationNotMet < ElementNotFound; end
class FileNotFound < CapybaraError; end

View File

@ -90,6 +90,34 @@ class Capybara::Driver::Base
def no_such_window_error
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#no_such_window_error'
end
##
#
# Execute the block, and then accept the modal opened.
# @param type [:alert, :confirm, :prompt]
# @option options [Numeric] :wait How long to wait for the modal to appear after executing the block.
# @option options [String, Regexp] :text Text to verify is in the message shown in the modal
# @option options [String] :with Text to fill in in the case of a prompt
# @return [String] the message shown in the modal
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
#
def accept_modal(type, options={}, &blk)
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#accept_modal'
end
##
#
# Execute the block, and then dismiss the modal opened.
# @param type [:alert, :confirm, :prompt]
# @option options [Numeric] :wait How long to wait for the modal to appear after executing the block.
# @option options [String, Regexp] :text Text to verify is in the message shown in the modal
# @return [String] the message shown in the modal
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
#
def dismiss_modal(type, &blk)
raise Capybara::NotSupportedByDriverError, 'Capybara::Driver::Base#dismiss_modal'
end
def invalid_element_errors
[]

View File

@ -91,13 +91,26 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
def reset!
# Use instance variable directly so we avoid starting the browser just to reset the session
if @browser
begin @browser.manage.delete_all_cookies
rescue Selenium::WebDriver::Error::UnhandledError
# delete_all_cookies fails when we've previously gone
# to about:blank, so we rescue this error and do nothing
# instead.
begin
begin @browser.manage.delete_all_cookies
rescue Selenium::WebDriver::Error::UnhandledError
# delete_all_cookies fails when we've previously gone
# to about:blank, so we rescue this error and do nothing
# instead.
end
@browser.navigate.to("about:blank")
rescue Selenium::WebDriver::Error::UnhandledAlertError
# This error is thrown if an unhandled alert is on the page
# Firefox appears to automatically dismiss this alert, chrome does not
# We'll try to accept it
begin
@browser.switch_to.alert.accept
rescue Selenium::WebDriver::Error::NoAlertPresentError
# The alert is now gone - nothing to do
end
# try cleaning up the browser again
retry
end
@browser.navigate.to("about:blank")
end
end
@ -191,6 +204,23 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
browser.switch_to.window(handle) { yield }
end
def accept_modal(type, options={}, &blk)
yield
modal = find_modal(options)
modal.send_keys options[:with] if options[:with]
message = modal.text
modal.accept
message
end
def dismiss_modal(type, options={}, &blk)
yield
modal = find_modal(options)
message = modal.text
modal.dismiss
message
end
def quit
@browser.quit if @browser
rescue Errno::ECONNREFUSED
@ -220,4 +250,22 @@ class Capybara::Selenium::Driver < Capybara::Driver::Base
result
end
end
def find_modal(options={})
# Selenium has its own built in wait (2 seconds)for a modal to show up, so this wait is really the minimum time
# Actual wait time may be longer than specified
wait = Selenium::WebDriver::Wait.new(
timeout: (options[:wait] || Capybara.default_wait_time),
ignore: Selenium::WebDriver::Error::NoAlertPresentError)
begin
modal = wait.until do
alert = @browser.switch_to.alert
regexp = options[:text].is_a?(Regexp) ? options[:text] : Regexp.escape(options[:text].to_s)
alert.text.match(regexp) ? alert : nil
end
rescue Selenium::WebDriver::Error::TimeOutError
raise Capybara::ModalNotFound.new("Unable to find modal dialog#{" with #{options[:text]}" if options[:text]}")
end
end
end

View File

@ -46,7 +46,11 @@ module Capybara
:save_and_open_screenshot, :reset_session!, :response_headers,
:status_code, :title, :has_title?, :has_no_title?, :current_scope
]
DSL_METHODS = NODE_METHODS + SESSION_METHODS
MODAL_METHODS = [
:accept_alert, :accept_confirm, :dismiss_confirm, :accept_prompt,
:dismiss_prompt
]
DSL_METHODS = NODE_METHODS + SESSION_METHODS + MODAL_METHODS
attr_reader :mode, :app, :server
attr_accessor :synchronized
@ -526,6 +530,112 @@ module Capybara
driver.evaluate_script(script)
end
##
#
# Execute the block, accepting a alert.
#
# @overload accept_alert(text, options = {}, &blk)
# @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any prompt modal is matched
# @overload accept_alert(options = {}, &blk)
# @option options [Numeric] :wait How long to wait for the modal to appear after executing the block.
# @return [String] the message shown in the modal
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
#
def accept_alert(text_or_options=nil, options={}, &blk)
if text_or_options.is_a? Hash
options=text_or_options
else
options[:text]=text_or_options
end
driver.accept_modal(:alert, options, &blk)
end
##
#
# Execute the block, accepting a confirm.
#
# @overload accept_confirm(text, options = {}, &blk)
# @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any prompt modal is matched
# @overload accept_confirm(options = {}, &blk)
# @option options [Numeric] :wait How long to wait for the modal to appear after executing the block.
# @return [String] the message shown in the modal
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
#
def accept_confirm(text_or_options=nil, options={}, &blk)
if text_or_options.is_a? Hash
options=text_or_options
else
options[:text]=text_or_options
end
driver.accept_modal(:confirm, options, &blk)
end
##
#
# Execute the block, dismissing a confirm.
#
# @overload dismiss_confirm(text, options = {}, &blk)
# @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any prompt modal is matched
# @overload dismiss_confirm(options = {}, &blk)
# @option options [Numeric] :wait How long to wait for the modal to appear after executing the block.
# @return [String] the message shown in the modal
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
#
def dismiss_confirm(text_or_options=nil, options={}, &blk)
if text_or_options.is_a? Hash
options=text_or_options
else
options[:text]=text_or_options
end
driver.dismiss_modal(:confirm, options, &blk)
end
##
#
# Execute the block, accepting a prompt, optionally responding to the prompt.
#
# @overload accept_prompt(text, options = {}, &blk)
# @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any prompt modal is matched
# @overload accept_prompt(options = {}, &blk)
# @option options [String] :with Response to provide to the prompt
# @option options [Numeric] :wait How long to wait for the prompt to appear after executing the block.
# @return [String] the message shown in the modal
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
#
def accept_prompt(text_or_options=nil, options={}, &blk)
if text_or_options.is_a? Hash
options=text_or_options
else
options[:text]=text_or_options
end
driver.accept_modal(:prompt, options, &blk)
end
##
#
# Execute the block, dismissing a prompt.
#
# @overload dismiss_prompt(text, options = {}, &blk)
# @param text [String, Regexp] Text or regex to match against the text in the modal. If not provided any prompt modal is matched
# @overload dismiss_prompt(options = {}, &blk)
# @option options [Numeric] :wait How long to wait for the prompt to appear after executing the block.
# @return [String] the message shown in the modal
# @raise [Capybara::ModalNotFound] if modal dialog hasn't been found
#
def dismiss_prompt(text_or_options=nil, options={}, &blk)
if text_or_options.is_a? Hash
options=text_or_options
else
options[:text]=text_or_options
end
driver.dismiss_modal(:prompt, options, &blk)
end
##
#
# Save a snapshot of the page.

View File

@ -67,4 +67,37 @@ $(function() {
e.preventDefault();
$(this).after('<a id="has-been-right-clicked" href="#">Has been right clicked</a>');
});
$('#open-alert').click(function() {
alert('Alert opened');
$(this).attr('opened', 'true');
});
$('#open-delayed-alert').click(function() {
var link = this;
setTimeout(function() {
alert('Delayed alert opened');
$(link).attr('opened', 'true');
}, 250);
});
$('#open-slow-alert').click(function() {
var link = this;
setTimeout(function() {
alert('Delayed alert opened');
$(link).attr('opened', 'true');
}, 3000);
});
$('#open-confirm').click(function() {
if(confirm('Confirm opened')) {
$(this).attr('confirmed', 'true');
} else {
$(this).attr('confirmed', 'false');
}
});
$('#open-prompt').click(function() {
var response = prompt('Prompt opened');
if(response === null) {
$(this).attr('response', 'dismissed');
} else {
$(this).attr('response', response);
}
});
});

View File

@ -0,0 +1,58 @@
Capybara::SpecHelper.spec '#accept_alert', :requires => [:modals] do
before do
@session.visit('/with_js')
end
it "should accept the alert" do
@session.accept_alert do
@session.click_link('Open alert')
end
expect(@session).to have_xpath("//a[@id='open-alert' and @opened='true']")
end
it "should accept the alert if the text matches" do
@session.accept_alert 'Alert opened' do
@session.click_link('Open alert')
end
expect(@session).to have_xpath("//a[@id='open-alert' and @opened='true']")
end
it "should not accept the alert if the text doesnt match" do
expect do
@session.accept_alert 'Incorrect Text' do
@session.click_link('Open alert')
end
end.to raise_error(Capybara::ModalNotFound)
# @session.accept_alert {} # clear the alert so browser continues to function
end
it "should return the message presented" do
message = @session.accept_alert do
@session.click_link('Open alert')
end
expect(message).to eq('Alert opened')
end
context "with an asynchronous alert" do
it "should accept the alert" do
@session.accept_alert do
@session.click_link('Open delayed alert')
end
expect(@session).to have_xpath("//a[@id='open-delayed-alert' and @opened='true']")
end
it "should return the message presented" do
message = @session.accept_alert do
@session.click_link('Open delayed alert')
end
expect(message).to eq('Delayed alert opened')
end
it "should allow to adjust the delay" do
@session.accept_alert wait: 4 do
@session.click_link('Open slow alert')
end
expect(@session).to have_xpath("//a[@id='open-slow-alert' and @opened='true']")
end
end
end

View File

@ -0,0 +1,19 @@
Capybara::SpecHelper.spec '#accept_confirm', :requires => [:modals] do
before do
@session.visit('/with_js')
end
it "should accept the confirm" do
@session.accept_confirm do
@session.click_link('Open confirm')
end
expect(@session).to have_xpath("//a[@id='open-confirm' and @confirmed='true']")
end
it "should return the message presented" do
message = @session.accept_confirm do
@session.click_link('Open confirm')
end
expect(message).to eq('Confirm opened')
end
end

View File

@ -0,0 +1,49 @@
Capybara::SpecHelper.spec '#accept_prompt', :requires => [:modals] do
before do
@session.visit('/with_js')
end
it "should accept the prompt with no message" do
@session.accept_prompt do
@session.click_link('Open prompt')
end
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='']")
end
it "should return the message presented" do
message = @session.accept_prompt do
@session.click_link('Open prompt')
end
expect(message).to eq('Prompt opened')
end
it "should accept the prompt with a response" do
@session.accept_prompt with: 'the response' do
@session.click_link('Open prompt')
end
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='the response']")
end
it "should accept the prompt if the message matches" do
@session.accept_prompt 'Prompt opened', with: 'matched' do
@session.click_link('Open prompt')
end
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='matched']")
end
it "should not accept the prompt if the message doesn't match" do
expect do
@session.accept_prompt 'Incorrect Text', with: 'not matched' do
@session.click_link('Open prompt')
end
end.to raise_error(Capybara::ModalNotFound)
end
it "should return the message presented" do
message = @session.accept_prompt with: 'the response' do
@session.click_link('Open prompt')
end
expect(message).to eq('Prompt opened')
end
end

View File

@ -0,0 +1,35 @@
Capybara::SpecHelper.spec '#dismiss_confirm', :requires => [:modals] do
before do
@session.visit('/with_js')
end
it "should dismiss the confirm" do
@session.dismiss_confirm do
@session.click_link('Open confirm')
end
expect(@session).to have_xpath("//a[@id='open-confirm' and @confirmed='false']")
end
it "should dismiss the confirm if the message matches" do
@session.dismiss_confirm 'Confirm opened' do
@session.click_link('Open confirm')
end
expect(@session).to have_xpath("//a[@id='open-confirm' and @confirmed='false']")
end
it "should not dismiss the confirm if the message doesn't match" do
expect do
@session.dismiss_confirm 'Incorrect Text' do
@session.click_link('Open confirm')
end
end.to raise_error(Capybara::ModalNotFound)
end
it "should return the message presented" do
message = @session.dismiss_confirm do
@session.click_link('Open confirm')
end
expect(message).to eq('Confirm opened')
end
end

View File

@ -0,0 +1,19 @@
Capybara::SpecHelper.spec '#dismiss_prompt', :requires => [:modals] do
before do
@session.visit('/with_js')
end
it "should dismiss the prompt" do
@session.dismiss_prompt do
@session.click_link('Open prompt')
end
expect(@session).to have_xpath("//a[@id='open-prompt' and @response='dismissed']")
end
it "should return the message presented" do
message = @session.dismiss_prompt do
@session.click_link('Open prompt')
end
expect(message).to eq('Prompt opened')
end
end

View File

@ -72,6 +72,23 @@
<p id="click-test">Click me</p>
<p>
<a href="#" id="open-alert">Open alert</a>
</p>
<p>
<a href="#" id="open-delayed-alert">Open delayed alert</a>
<a href="#" id="open-slow-alert">Open slow alert</a>
</p>
<p>
<a href="#" id="open-confirm">Open confirm</a>
</p>
<p>
<a href="#" id="open-prompt">Open prompt</a>
</p>
<script type="text/javascript">
// a javascript comment
var aVar = 123;

View File

@ -7,6 +7,7 @@ end
Capybara::SpecHelper.run_specs TestClass.new, "DSL", :capybara_skip => [
:js,
:modals,
:screenshot,
:frames,
:windows,

View File

@ -6,6 +6,7 @@ end
Capybara::SpecHelper.run_specs TestSessions::RackTest, "RackTest", :capybara_skip => [
:js,
:modals,
:screenshot,
:frames,
:windows,