mirror of
https://github.com/teamcapybara/capybara.git
synced 2022-11-09 12:08:07 -05:00
Merge pull request #1322 from jnicklas/modal
API for operation with modal dialogs
This commit is contained in:
commit
c589f6a814
14 changed files with 464 additions and 7 deletions
38
README.md
38
README.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
[]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
58
lib/capybara/spec/session/accept_alert_spec.rb
Normal file
58
lib/capybara/spec/session/accept_alert_spec.rb
Normal 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
|
19
lib/capybara/spec/session/accept_confirm_spec.rb
Normal file
19
lib/capybara/spec/session/accept_confirm_spec.rb
Normal 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
|
49
lib/capybara/spec/session/accept_prompt_spec.rb
Normal file
49
lib/capybara/spec/session/accept_prompt_spec.rb
Normal 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
|
35
lib/capybara/spec/session/dismiss_confirm_spec.rb
Normal file
35
lib/capybara/spec/session/dismiss_confirm_spec.rb
Normal 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
|
19
lib/capybara/spec/session/dismiss_prompt_spec.rb
Normal file
19
lib/capybara/spec/session/dismiss_prompt_spec.rb
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -7,6 +7,7 @@ end
|
|||
|
||||
Capybara::SpecHelper.run_specs TestClass.new, "DSL", :capybara_skip => [
|
||||
:js,
|
||||
:modals,
|
||||
:screenshot,
|
||||
:frames,
|
||||
:windows,
|
||||
|
|
|
@ -6,6 +6,7 @@ end
|
|||
|
||||
Capybara::SpecHelper.run_specs TestSessions::RackTest, "RackTest", :capybara_skip => [
|
||||
:js,
|
||||
:modals,
|
||||
:screenshot,
|
||||
:frames,
|
||||
:windows,
|
||||
|
|
Loading…
Reference in a new issue