mirror of
https://github.com/thoughtbot/capybara-webkit
synced 2023-03-27 23:22:28 -04:00
e0172bfcd9
This adds support for the full Capybara 2.3.0 API. There are two known incompatibilities: * Selenium supports outerWidth and outerHeight, which we cannot, because we dont' have an actual OS window. * Selenium raises errors after interacting with a closed window. We focus the next available window after closing. This commit adds the following: * Implement Driver#close_window * Implement Driver#current_window_handle * Implement Driver#maximize_window * Implement Driver#open_new_window * Implement Driver#no_such_window_error * Implement Driver#resize_window_to * Implement Driver#switch_to_window * Implement Driver#window_size * Implement Driver#go_back * Implement Driver#go_forward * Support change events when clearing a text input * Support setting contentEditable elements * Support window.close() in JavaScript * Don't return text from hidden elements * Skip Capybara specs which use outerWidth, outerHeight * Don't use Qt object ownership to manage windows
2497 lines
74 KiB
Ruby
2497 lines
74 KiB
Ruby
# -*- encoding: UTF-8 -*-
|
|
|
|
require 'spec_helper'
|
|
require 'capybara/webkit/driver'
|
|
require 'base64'
|
|
|
|
describe Capybara::Webkit::Driver do
|
|
include AppRunner
|
|
|
|
def visit(url, driver=driver)
|
|
driver.visit("#{AppRunner.app_host}#{url}")
|
|
end
|
|
|
|
context "iframe app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/" do
|
|
if params[:iframe] == "true"
|
|
redirect '/iframe'
|
|
else
|
|
<<-HTML
|
|
<html>
|
|
<head>
|
|
<style type="text/css">
|
|
#display_none { display: none }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<iframe id="f" src="/?iframe=true"></iframe>
|
|
<script type="text/javascript">
|
|
document.write("<p id='greeting'>hello</p>");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
end
|
|
|
|
get '/iframe' do
|
|
headers 'X-Redirected' => 'true'
|
|
<<-HTML
|
|
<html>
|
|
<head>
|
|
<title>Title</title>
|
|
<style type="text/css">
|
|
#display_none { display: none }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<script type="text/javascript">
|
|
document.write("<p id='farewell'>goodbye</p>");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
before do
|
|
visit("/")
|
|
end
|
|
|
|
it "finds frames by index" do
|
|
driver.within_frame(0) do
|
|
driver.find_xpath("//*[contains(., 'goodbye')]").should_not be_empty
|
|
end
|
|
end
|
|
|
|
it "finds frames by id" do
|
|
driver.within_frame("f") do
|
|
driver.find_xpath("//*[contains(., 'goodbye')]").should_not be_empty
|
|
end
|
|
end
|
|
|
|
it "finds frames by element" do
|
|
frame = driver.find_xpath('//iframe').first
|
|
element = double(Capybara::Node::Base, base: frame)
|
|
driver.within_frame(element) do
|
|
driver.find_xpath("//*[contains(., 'goodbye')]").should_not be_empty
|
|
end
|
|
end
|
|
|
|
it "raises error for missing frame by index" do
|
|
expect { driver.within_frame(1) { } }.
|
|
to raise_error(Capybara::Webkit::InvalidResponseError)
|
|
end
|
|
|
|
it "raise_error for missing frame by id" do
|
|
expect { driver.within_frame("foo") { } }.
|
|
to raise_error(Capybara::Webkit::InvalidResponseError)
|
|
end
|
|
|
|
it "returns an attribute's value" do
|
|
driver.within_frame("f") do
|
|
driver.find_xpath("//p").first["id"].should eq "farewell"
|
|
end
|
|
end
|
|
|
|
it "returns an attribute's innerHTML" do
|
|
driver.find_xpath('//body').first.inner_html.should =~ %r{<iframe.*</iframe>.*<script.*</script>.*}m
|
|
end
|
|
|
|
it "receive an attribute's innerHTML" do
|
|
driver.find_xpath('//body').first.inner_html = 'foobar'
|
|
driver.find_xpath("//body[contains(., 'foobar')]").should_not be_empty
|
|
end
|
|
|
|
it "returns a node's text" do
|
|
driver.within_frame("f") do
|
|
driver.find_xpath("//p").first.visible_text.should eq "goodbye"
|
|
end
|
|
end
|
|
|
|
it "returns the current URL" do
|
|
driver.within_frame("f") do
|
|
driver.current_url.should eq driver_url(driver, "/iframe")
|
|
end
|
|
end
|
|
|
|
it "evaluates Javascript" do
|
|
driver.within_frame("f") do
|
|
result = driver.evaluate_script(%<document.getElementById('farewell').innerText>)
|
|
result.should eq "goodbye"
|
|
end
|
|
end
|
|
|
|
it "executes Javascript" do
|
|
driver.within_frame("f") do
|
|
driver.execute_script(%<document.getElementById('farewell').innerHTML = 'yo'>)
|
|
driver.find_xpath("//p[contains(., 'yo')]").should_not be_empty
|
|
end
|
|
end
|
|
|
|
it "returns focus to parent" do
|
|
original_url = driver.current_url
|
|
|
|
driver.within_frame("f") {}
|
|
|
|
driver.current_url.should eq original_url
|
|
end
|
|
|
|
it "returns the headers for the page" do
|
|
driver.within_frame("f") do
|
|
driver.response_headers['X-Redirected'].should eq "true"
|
|
end
|
|
end
|
|
|
|
it "returns the status code for the page" do
|
|
driver.within_frame("f") do
|
|
driver.status_code.should eq 200
|
|
end
|
|
end
|
|
|
|
it "returns the document title" do
|
|
driver.within_frame("f") do
|
|
driver.title.should eq "Title"
|
|
end
|
|
end
|
|
end
|
|
|
|
context "error iframe app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/inner-not-found" do
|
|
invalid_response
|
|
end
|
|
|
|
get "/" do
|
|
<<-HTML
|
|
<html>
|
|
<body>
|
|
<iframe src="/inner-not-found"></iframe>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
it "raises error whose message references the actual missing url" do
|
|
expect { visit("/") }.to raise_error(Capybara::Webkit::InvalidResponseError, /inner-not-found/)
|
|
end
|
|
end
|
|
|
|
context "redirect app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
enable :sessions
|
|
|
|
get '/target' do
|
|
headers 'X-Redirected' => (session.delete(:redirected) || false).to_s
|
|
"<p>#{env['CONTENT_TYPE']}</p>"
|
|
end
|
|
|
|
get '/form' do
|
|
<<-HTML
|
|
<html>
|
|
<body>
|
|
<form action="/redirect" method="POST" enctype="multipart/form-data">
|
|
<input name="submit" type="submit" />
|
|
</form>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
post '/redirect' do
|
|
redirect '/target'
|
|
end
|
|
|
|
get '/redirect-me' do
|
|
if session[:redirected]
|
|
redirect '/target'
|
|
else
|
|
session[:redirected] = true
|
|
redirect '/redirect-me'
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
it "should redirect without content type" do
|
|
visit("/form")
|
|
driver.find_xpath("//input").first.click
|
|
driver.find_xpath("//p").first.visible_text.should eq ""
|
|
end
|
|
|
|
it "returns the current URL when changed by pushState after a redirect" do
|
|
visit("/redirect-me")
|
|
driver.current_url.should eq driver_url(driver, "/target")
|
|
driver.execute_script("window.history.pushState({}, '', '/pushed-after-redirect')")
|
|
driver.current_url.should eq driver_url(driver, "/pushed-after-redirect")
|
|
end
|
|
|
|
it "returns the current URL when changed by replaceState after a redirect" do
|
|
visit("/redirect-me")
|
|
driver.current_url.should eq driver_url(driver, "/target")
|
|
driver.execute_script("window.history.replaceState({}, '', '/replaced-after-redirect')")
|
|
driver.current_url.should eq driver_url(driver, "/replaced-after-redirect")
|
|
end
|
|
|
|
it "should make headers available through response_headers" do
|
|
visit('/redirect-me')
|
|
driver.response_headers['X-Redirected'].should eq "true"
|
|
visit('/target')
|
|
driver.response_headers['X-Redirected'].should eq "false"
|
|
end
|
|
|
|
it "should make the status code available through status_code" do
|
|
visit('/redirect-me')
|
|
driver.status_code.should eq 200
|
|
visit('/target')
|
|
driver.status_code.should eq 200
|
|
end
|
|
end
|
|
|
|
context "css app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/" do
|
|
headers "Content-Type" => "text/css"
|
|
"css"
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "renders unsupported content types gracefully" do
|
|
driver.html.should =~ /css/
|
|
end
|
|
|
|
it "sets the response headers with respect to the unsupported request" do
|
|
driver.response_headers["Content-Type"].should eq "text/css"
|
|
end
|
|
|
|
it "does not wrap the content in HTML tags" do
|
|
driver.html.should_not =~ /<html>/
|
|
end
|
|
end
|
|
|
|
context "html app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
<title>Hello HTML</title>
|
|
</head>
|
|
<body>
|
|
<h1>This Is HTML!</h1>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "does not strip HTML tags" do
|
|
driver.html.should =~ /<html>/
|
|
end
|
|
end
|
|
|
|
context "binary content app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get '/' do
|
|
headers 'Content-Type' => 'application/octet-stream'
|
|
"Hello\xFF\xFF\xFF\xFFWorld"
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "should return the binary content" do
|
|
src = driver.html.force_encoding('binary')
|
|
src.should eq "Hello\xFF\xFF\xFF\xFFWorld".force_encoding('binary')
|
|
end
|
|
end
|
|
|
|
context "hello app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
<title>Title</title>
|
|
<style type="text/css">
|
|
#display_none { display: none }
|
|
#visibility_hidden { visibility: hidden }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class='normalize'>Spaces not normalized </div>
|
|
<div id="display_none">
|
|
<div id="invisible">Can't see me</div>
|
|
</div>
|
|
<div id="visibility_hidden">
|
|
<div id="invisible_with_visibility">Can't see me too</div>
|
|
</div>
|
|
<div id="hidden-text">
|
|
Some of this text is <em style="display:none">hidden!</em>
|
|
</div>
|
|
<div id="hidden-ancestor" style="display: none">
|
|
<div>Hello</div>
|
|
</div>
|
|
<input type="text" disabled="disabled"/>
|
|
<input id="checktest" type="checkbox" checked="checked"/>
|
|
<script type="text/javascript">
|
|
document.write("<p id='greeting'>he" + "llo</p>");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "doesn't return text if the ancestor is hidden" do
|
|
visit("/")
|
|
|
|
driver.find_css("#hidden-ancestor div").first.text.should eq ''
|
|
end
|
|
|
|
it "handles anchor tags" do
|
|
visit("#test")
|
|
driver.find_xpath("//*[contains(., 'hello')]").should_not be_empty
|
|
visit("#test")
|
|
driver.find_xpath("//*[contains(., 'hello')]").should_not be_empty
|
|
end
|
|
|
|
it "finds content after loading a URL" do
|
|
driver.find_xpath("//*[contains(., 'hello')]").should_not be_empty
|
|
end
|
|
|
|
it "has an empty page after reseting" do
|
|
driver.reset!
|
|
driver.find_xpath("//*[contains(., 'hello')]").should be_empty
|
|
end
|
|
|
|
it "has a blank location after reseting" do
|
|
driver.reset!
|
|
driver.current_url.should eq "about:blank"
|
|
end
|
|
|
|
it "raises an error for an invalid xpath query" do
|
|
expect { driver.find_xpath("totally invalid salad") }.
|
|
to raise_error(Capybara::Webkit::InvalidResponseError, /xpath/i)
|
|
end
|
|
|
|
it "raises an error for an invalid xpath query within an element" do
|
|
expect { driver.find_xpath("//body").first.find_xpath("totally invalid salad") }.
|
|
to raise_error(Capybara::Webkit::InvalidResponseError, /xpath/i)
|
|
end
|
|
|
|
it "returns an attribute's value" do
|
|
driver.find_xpath("//p").first["id"].should eq "greeting"
|
|
end
|
|
|
|
it "parses xpath with quotes" do
|
|
driver.find_xpath('//*[contains(., "hello")]').should_not be_empty
|
|
end
|
|
|
|
it "returns a node's visible text" do
|
|
driver.find_xpath("//*[@id='hidden-text']").first.visible_text.should eq "Some of this text is"
|
|
end
|
|
|
|
it "normalizes a node's text" do
|
|
driver.find_xpath("//div[contains(@class, 'normalize')]").first.visible_text.should eq "Spaces not normalized"
|
|
end
|
|
|
|
it "returns all of a node's text" do
|
|
driver.find_xpath("//*[@id='hidden-text']").first.all_text.should eq "Some of this text is hidden!"
|
|
end
|
|
|
|
it "returns the current URL" do
|
|
visit "/hello/world?success=true"
|
|
driver.current_url.should eq driver_url(driver, "/hello/world?success=true")
|
|
end
|
|
|
|
it "returns the current URL when changed by pushState" do
|
|
driver.execute_script("window.history.pushState({}, '', '/pushed')")
|
|
driver.current_url.should eq driver_url(driver, "/pushed")
|
|
end
|
|
|
|
it "returns the current URL when changed by replaceState" do
|
|
driver.execute_script("window.history.replaceState({}, '', '/replaced')")
|
|
driver.current_url.should eq driver_url(driver, "/replaced")
|
|
end
|
|
|
|
it "does not double-encode URLs" do
|
|
visit("/hello/world?success=%25true")
|
|
driver.current_url.should =~ /success=\%25true/
|
|
end
|
|
|
|
it "returns the current URL with encoded characters" do
|
|
visit("/hello/world?success[value]=true")
|
|
current_url = Rack::Utils.unescape(driver.current_url)
|
|
current_url.should include('success[value]=true')
|
|
end
|
|
|
|
it "visits a page with an anchor" do
|
|
visit("/hello#display_none")
|
|
driver.current_url.should =~ /hello#display_none/
|
|
end
|
|
|
|
it "evaluates Javascript and returns a string" do
|
|
result = driver.evaluate_script(%<document.getElementById('greeting').innerText>)
|
|
result.should eq "hello"
|
|
end
|
|
|
|
it "evaluates Javascript and returns an array" do
|
|
result = driver.evaluate_script(%<["hello", "world"]>)
|
|
result.should eq %w(hello world)
|
|
end
|
|
|
|
it "evaluates Javascript and returns an int" do
|
|
result = driver.evaluate_script(%<123>)
|
|
result.should eq 123
|
|
end
|
|
|
|
it "evaluates Javascript and returns a float" do
|
|
result = driver.evaluate_script(%<1.5>)
|
|
result.should eq 1.5
|
|
end
|
|
|
|
it "evaluates Javascript and returns null" do
|
|
result = driver.evaluate_script(%<(function () {})()>)
|
|
result.should eq nil
|
|
end
|
|
|
|
it "evaluates Infinity and returns null" do
|
|
result = driver.evaluate_script(%<Infinity>)
|
|
result.should eq nil
|
|
end
|
|
|
|
it "evaluates Javascript and returns an object" do
|
|
result = driver.evaluate_script(%<({ 'one' : 1 })>)
|
|
result.should eq 'one' => 1
|
|
end
|
|
|
|
it "evaluates Javascript and returns true" do
|
|
result = driver.evaluate_script(%<true>)
|
|
result.should === true
|
|
end
|
|
|
|
it "evaluates Javascript and returns false" do
|
|
result = driver.evaluate_script(%<false>)
|
|
result.should === false
|
|
end
|
|
|
|
it "evaluates Javascript and returns an escaped string" do
|
|
result = driver.evaluate_script(%<'"'>)
|
|
result.should === "\""
|
|
end
|
|
|
|
it "evaluates Javascript with multiple lines" do
|
|
result = driver.evaluate_script("[1,\n2]")
|
|
result.should eq [1, 2]
|
|
end
|
|
|
|
it "executes Javascript" do
|
|
driver.execute_script(%<document.getElementById('greeting').innerHTML = 'yo'>)
|
|
driver.find_xpath("//p[contains(., 'yo')]").should_not be_empty
|
|
end
|
|
|
|
it "raises an error for failing Javascript" do
|
|
expect { driver.execute_script(%<invalid salad>) }.
|
|
to raise_error(Capybara::Webkit::InvalidResponseError)
|
|
end
|
|
|
|
it "doesn't raise an error for Javascript that doesn't return anything" do
|
|
lambda { driver.execute_script(%<(function () { "returns nothing" })()>) }.
|
|
should_not raise_error
|
|
end
|
|
|
|
it "returns a node's tag name" do
|
|
driver.find_xpath("//p").first.tag_name.should eq "p"
|
|
end
|
|
|
|
it "reads disabled property" do
|
|
driver.find_xpath("//input").first.should be_disabled
|
|
end
|
|
|
|
it "reads checked property" do
|
|
driver.find_xpath("//input[@id='checktest']").first.should be_checked
|
|
end
|
|
|
|
it "finds visible elements" do
|
|
driver.find_xpath("//p").first.should be_visible
|
|
driver.find_xpath("//*[@id='invisible']").first.should_not be_visible
|
|
driver.find_xpath("//*[@id='invisible_with_visibility']").first.should_not be_visible
|
|
end
|
|
|
|
it "returns the document title" do
|
|
driver.title.should eq "Title"
|
|
end
|
|
|
|
it "finds elements by CSS" do
|
|
driver.find_css("p").first.visible_text.should eq "hello"
|
|
end
|
|
end
|
|
|
|
context "svg app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<body>
|
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" height="100">
|
|
<text x="10" y="25" fill="navy" font-size="15" id="navy_text">In the navy!</text>
|
|
</svg>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "should handle text for svg elements" do
|
|
driver.find_xpath("//*[@id='navy_text']").first.visible_text.should eq "In the navy!"
|
|
end
|
|
end
|
|
|
|
context "console messages app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
|
</head>
|
|
<body>
|
|
<script type="text/javascript">
|
|
console.log("hello");
|
|
console.log("hello again");
|
|
console.log("hello\\nnewline");
|
|
console.log("𝄞");
|
|
oops
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "collects messages logged to the console" do
|
|
url = driver_url(driver, "/")
|
|
message = driver.console_messages.first
|
|
message.should include :source => url, :message => "hello"
|
|
[6, 7].should include message[:line_number]
|
|
driver.console_messages.length.should eq 5
|
|
end
|
|
|
|
it "logs errors to the console" do
|
|
driver.error_messages.length.should eq 1
|
|
end
|
|
|
|
it "supports multi-line console messages" do
|
|
message = driver.console_messages[2]
|
|
message[:message].should eq "hello\nnewline"
|
|
end
|
|
|
|
it "empties the array when reset" do
|
|
driver.reset!
|
|
driver.console_messages.should be_empty
|
|
end
|
|
|
|
it "supports console messages from an unknown source" do
|
|
driver.execute_script("console.log('hello')")
|
|
driver.console_messages.last[:message].should eq 'hello'
|
|
driver.console_messages.last[:source].should be_nil
|
|
driver.console_messages.last[:line_number].should be_nil
|
|
end
|
|
|
|
it "escapes unicode console messages" do
|
|
driver.console_messages[3][:message].should eq '𝄞'
|
|
end
|
|
end
|
|
|
|
context "javascript dialog interaction" do
|
|
context "on an alert app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
</head>
|
|
<body>
|
|
<script type="text/javascript">
|
|
alert("Alert Text\\nGoes Here");
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "should let me read my alert messages" do
|
|
driver.alert_messages.first.should eq "Alert Text\nGoes Here"
|
|
end
|
|
|
|
it "empties the array when reset" do
|
|
driver.reset!
|
|
driver.alert_messages.should be_empty
|
|
end
|
|
end
|
|
|
|
context "on a confirm app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
</head>
|
|
<body>
|
|
<script type="text/javascript">
|
|
function test_dialog() {
|
|
if(confirm("Yes?"))
|
|
console.log("hello");
|
|
else
|
|
console.log("goodbye");
|
|
}
|
|
</script>
|
|
<input type="button" onclick="test_dialog()" name="test"/>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "should default to accept the confirm" do
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "hello"
|
|
end
|
|
|
|
it "can dismiss the confirm" do
|
|
driver.dismiss_js_confirms!
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "goodbye"
|
|
end
|
|
|
|
it "can accept the confirm explicitly" do
|
|
driver.dismiss_js_confirms!
|
|
driver.accept_js_confirms!
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "hello"
|
|
end
|
|
|
|
it "should collect the javascript confirm dialog contents" do
|
|
driver.find_xpath("//input").first.click
|
|
driver.confirm_messages.first.should eq "Yes?"
|
|
end
|
|
|
|
it "empties the array when reset" do
|
|
driver.find_xpath("//input").first.click
|
|
driver.reset!
|
|
driver.confirm_messages.should be_empty
|
|
end
|
|
|
|
it "resets to the default of accepting confirms" do
|
|
driver.dismiss_js_confirms!
|
|
driver.reset!
|
|
visit("/")
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "hello"
|
|
end
|
|
|
|
it "supports multi-line confirmation messages" do
|
|
driver.execute_script("confirm('Hello\\nnewline')")
|
|
driver.confirm_messages.first.should eq "Hello\nnewline"
|
|
end
|
|
|
|
end
|
|
|
|
context "on a prompt app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
</head>
|
|
<body>
|
|
<script type="text/javascript">
|
|
function test_dialog() {
|
|
var response = prompt("Your name?", "John Smith");
|
|
if(response != null)
|
|
console.log("hello " + response);
|
|
else
|
|
console.log("goodbye");
|
|
}
|
|
</script>
|
|
<input type="button" onclick="test_dialog()" name="test"/>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "should default to dismiss the prompt" do
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "goodbye"
|
|
end
|
|
|
|
it "can accept the prompt without providing text" do
|
|
driver.accept_js_prompts!
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "hello John Smith"
|
|
end
|
|
|
|
it "can accept the prompt with input" do
|
|
driver.js_prompt_input = "Capy"
|
|
driver.accept_js_prompts!
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "hello Capy"
|
|
end
|
|
|
|
it "can return to dismiss the prompt after accepting prompts" do
|
|
driver.accept_js_prompts!
|
|
driver.dismiss_js_prompts!
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "goodbye"
|
|
end
|
|
|
|
it "should let me remove the prompt input text" do
|
|
driver.js_prompt_input = "Capy"
|
|
driver.accept_js_prompts!
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "hello Capy"
|
|
driver.js_prompt_input = nil
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.last[:message].should eq "hello John Smith"
|
|
end
|
|
|
|
it "should collect the javascript prompt dialog contents" do
|
|
driver.find_xpath("//input").first.click
|
|
driver.prompt_messages.first.should eq "Your name?"
|
|
end
|
|
|
|
it "empties the array when reset" do
|
|
driver.find_xpath("//input").first.click
|
|
driver.reset!
|
|
driver.prompt_messages.should be_empty
|
|
end
|
|
|
|
it "returns the prompt action to dismiss on reset" do
|
|
driver.accept_js_prompts!
|
|
driver.reset!
|
|
visit("/")
|
|
driver.find_xpath("//input").first.click
|
|
driver.console_messages.first[:message].should eq "goodbye"
|
|
end
|
|
|
|
it "supports multi-line prompt messages" do
|
|
driver.execute_script("prompt('Hello\\nnewline')")
|
|
driver.prompt_messages.first.should eq "Hello\nnewline"
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
context "form app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html><body>
|
|
<form action="/" method="GET">
|
|
<input type="text" name="foo" value="bar"/>
|
|
<input type="text" name="maxlength_foo" value="bar" maxlength="10"/>
|
|
<input type="text" id="disabled_input" disabled="disabled"/>
|
|
<input type="text" id="readonly_input" readonly="readonly" value="readonly"/>
|
|
<input type="checkbox" name="checkedbox" value="1" checked="checked"/>
|
|
<input type="checkbox" name="uncheckedbox" value="2"/>
|
|
<select name="animal">
|
|
<option id="select-option-monkey">Monkey</option>
|
|
<option id="select-option-capybara" selected="selected">Capybara</option>
|
|
</select>
|
|
<select name="disabled" disabled="disabled">
|
|
<option id="select-option-disabled">Disabled</option>
|
|
</select>
|
|
<select name="toppings" multiple="multiple">
|
|
<optgroup label="Mediocre Toppings">
|
|
<option selected="selected" id="topping-apple">Apple</option>
|
|
<option selected="selected" id="topping-banana">Banana</option>
|
|
</optgroup>
|
|
<optgroup label="Best Toppings">
|
|
<option selected="selected" id="topping-cherry">Cherry</option>
|
|
</optgroup>
|
|
</select>
|
|
<select name="guitars" multiple>
|
|
<option selected="selected" id="fender">Fender</option>
|
|
<option selected="selected" id="gibson">Gibson</option>
|
|
</select>
|
|
<textarea id="only-textarea">what a wonderful area for text</textarea>
|
|
<input type="radio" id="only-radio" value="1"/>
|
|
<button type="reset">Reset Form</button>
|
|
</form>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "returns a textarea's value" do
|
|
driver.find_xpath("//textarea").first.value.should eq "what a wonderful area for text"
|
|
end
|
|
|
|
it "returns a text input's value" do
|
|
driver.find_xpath("//input").first.value.should eq "bar"
|
|
end
|
|
|
|
it "returns a select's value" do
|
|
driver.find_xpath("//select").first.value.should eq "Capybara"
|
|
end
|
|
|
|
it "sets an input's value" do
|
|
input = driver.find_xpath("//input").first
|
|
input.set("newvalue")
|
|
input.value.should eq "newvalue"
|
|
end
|
|
|
|
it "sets an input's value greater than the max length" do
|
|
input = driver.find_xpath("//input[@name='maxlength_foo']").first
|
|
input.set("allegories (poems)")
|
|
input.value.should eq "allegories"
|
|
end
|
|
|
|
it "sets an input's value equal to the max length" do
|
|
input = driver.find_xpath("//input[@name='maxlength_foo']").first
|
|
input.set("allegories")
|
|
input.value.should eq "allegories"
|
|
end
|
|
|
|
it "sets an input's value less than the max length" do
|
|
input = driver.find_xpath("//input[@name='maxlength_foo']").first
|
|
input.set("poems")
|
|
input.value.should eq "poems"
|
|
end
|
|
|
|
it "sets an input's nil value" do
|
|
input = driver.find_xpath("//input").first
|
|
input.set(nil)
|
|
input.value.should eq ""
|
|
end
|
|
|
|
it "sets a select's value" do
|
|
select = driver.find_xpath("//select").first
|
|
select.set("Monkey")
|
|
select.value.should eq "Monkey"
|
|
end
|
|
|
|
it "sets a textarea's value" do
|
|
textarea = driver.find_xpath("//textarea").first
|
|
textarea.set("newvalue")
|
|
textarea.value.should eq "newvalue"
|
|
end
|
|
|
|
let(:monkey_option) { driver.find_xpath("//option[@id='select-option-monkey']").first }
|
|
let(:capybara_option) { driver.find_xpath("//option[@id='select-option-capybara']").first }
|
|
let(:animal_select) { driver.find_xpath("//select[@name='animal']").first }
|
|
let(:apple_option) { driver.find_xpath("//option[@id='topping-apple']").first }
|
|
let(:banana_option) { driver.find_xpath("//option[@id='topping-banana']").first }
|
|
let(:cherry_option) { driver.find_xpath("//option[@id='topping-cherry']").first }
|
|
let(:toppings_select) { driver.find_xpath("//select[@name='toppings']").first }
|
|
let(:guitars_select) { driver.find_xpath("//select[@name='guitars']").first }
|
|
let(:fender_option) { driver.find_xpath("//option[@id='fender']").first }
|
|
let(:reset_button) { driver.find_xpath("//button[@type='reset']").first }
|
|
|
|
context "a select element's selection has been changed" do
|
|
before do
|
|
animal_select.value.should eq "Capybara"
|
|
monkey_option.select_option
|
|
end
|
|
|
|
it "returns the new selection" do
|
|
animal_select.value.should eq "Monkey"
|
|
end
|
|
|
|
it "does not modify the selected attribute of a new selection" do
|
|
monkey_option['selected'].should be_nil
|
|
end
|
|
|
|
it "returns the old value when a reset button is clicked" do
|
|
reset_button.click
|
|
|
|
animal_select.value.should eq "Capybara"
|
|
end
|
|
end
|
|
|
|
context "a multi-select element's option has been unselected" do
|
|
before do
|
|
toppings_select.value.should include("Apple", "Banana", "Cherry")
|
|
|
|
apple_option.unselect_option
|
|
end
|
|
|
|
it "does not return the deselected option" do
|
|
toppings_select.value.should_not include("Apple")
|
|
end
|
|
|
|
it "returns the deselected option when a reset button is clicked" do
|
|
reset_button.click
|
|
|
|
toppings_select.value.should include("Apple", "Banana", "Cherry")
|
|
end
|
|
end
|
|
|
|
context "a multi-select (with empty multiple attribute) element's option has been unselected" do
|
|
before do
|
|
guitars_select.value.should include("Fender", "Gibson")
|
|
|
|
fender_option.unselect_option
|
|
end
|
|
|
|
it "does not return the deselected option" do
|
|
guitars_select.value.should_not include("Fender")
|
|
end
|
|
end
|
|
|
|
it "reselects an option in a multi-select" do
|
|
apple_option.unselect_option
|
|
banana_option.unselect_option
|
|
cherry_option.unselect_option
|
|
|
|
toppings_select.value.should eq []
|
|
|
|
apple_option.select_option
|
|
banana_option.select_option
|
|
cherry_option.select_option
|
|
|
|
toppings_select.value.should include("Apple", "Banana", "Cherry")
|
|
end
|
|
|
|
let(:checked_box) { driver.find_xpath("//input[@name='checkedbox']").first }
|
|
let(:unchecked_box) { driver.find_xpath("//input[@name='uncheckedbox']").first }
|
|
|
|
it "knows a checked box is checked" do
|
|
checked_box['checked'].should be_true
|
|
end
|
|
|
|
it "knows a checked box is checked using checked?" do
|
|
checked_box.should be_checked
|
|
end
|
|
|
|
it "knows an unchecked box is unchecked" do
|
|
unchecked_box['checked'].should_not be_true
|
|
end
|
|
|
|
it "knows an unchecked box is unchecked using checked?" do
|
|
unchecked_box.should_not be_checked
|
|
end
|
|
|
|
it "checks an unchecked box" do
|
|
unchecked_box.set(true)
|
|
unchecked_box.should be_checked
|
|
end
|
|
|
|
it "unchecks a checked box" do
|
|
checked_box.set(false)
|
|
checked_box.should_not be_checked
|
|
end
|
|
|
|
it "leaves a checked box checked" do
|
|
checked_box.set(true)
|
|
checked_box.should be_checked
|
|
end
|
|
|
|
it "leaves an unchecked box unchecked" do
|
|
unchecked_box.set(false)
|
|
unchecked_box.should_not be_checked
|
|
end
|
|
|
|
let(:enabled_input) { driver.find_xpath("//input[@name='foo']").first }
|
|
let(:disabled_input) { driver.find_xpath("//input[@id='disabled_input']").first }
|
|
|
|
it "knows a disabled input is disabled" do
|
|
disabled_input['disabled'].should be_true
|
|
end
|
|
|
|
it "knows a not disabled input is not disabled" do
|
|
enabled_input['disabled'].should_not be_true
|
|
end
|
|
|
|
it "does not modify a readonly input" do
|
|
readonly_input = driver.find_css("#readonly_input").first
|
|
readonly_input.set('enabled')
|
|
readonly_input.value.should eq 'readonly'
|
|
end
|
|
|
|
it "should see enabled options in disabled select as disabled" do
|
|
driver.find_css("#select-option-disabled").first.should be_disabled
|
|
end
|
|
end
|
|
|
|
context "dom events" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html><body>
|
|
<a href='#' class='watch'>Link</a>
|
|
<ul id="events"></ul>
|
|
<script type="text/javascript">
|
|
var events = document.getElementById("events");
|
|
var recordEvent = function (event) {
|
|
var element = document.createElement("li");
|
|
element.innerHTML = event.type;
|
|
events.appendChild(element);
|
|
};
|
|
|
|
var elements = document.getElementsByClassName("watch");
|
|
for (var i = 0; i < elements.length; i++) {
|
|
var element = elements[i];
|
|
element.addEventListener("mousedown", recordEvent);
|
|
element.addEventListener("mouseup", recordEvent);
|
|
element.addEventListener("click", recordEvent);
|
|
element.addEventListener("dblclick", recordEvent);
|
|
element.addEventListener("contextmenu", recordEvent);
|
|
}
|
|
</script>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
let(:watch) { driver.find_xpath("//a").first }
|
|
let(:fired_events) { driver.find_xpath("//li").map(&:visible_text) }
|
|
|
|
it "triggers mouse events" do
|
|
watch.click
|
|
fired_events.should eq %w(mousedown mouseup click)
|
|
end
|
|
|
|
it "triggers double click" do
|
|
# check event order at http://www.quirksmode.org/dom/events/click.html
|
|
watch.double_click
|
|
fired_events.should eq %w(mousedown mouseup click mousedown mouseup click dblclick)
|
|
end
|
|
|
|
it "triggers right click" do
|
|
watch.right_click
|
|
fired_events.should eq %w(mousedown contextmenu mouseup)
|
|
end
|
|
end
|
|
|
|
context "form events app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html><body>
|
|
<form action="/" method="GET">
|
|
<input class="watch" type="email"/>
|
|
<input class="watch" type="number"/>
|
|
<input class="watch" type="password"/>
|
|
<input class="watch" type="search"/>
|
|
<input class="watch" type="tel"/>
|
|
<input class="watch" type="text" value="original"/>
|
|
<input class="watch" type="url"/>
|
|
<textarea class="watch"></textarea>
|
|
<input class="watch" type="checkbox"/>
|
|
<input class="watch" type="radio"/>
|
|
</form>
|
|
<ul id="events"></ul>
|
|
<script type="text/javascript">
|
|
var events = document.getElementById("events");
|
|
var recordEvent = function (event) {
|
|
var element = document.createElement("li");
|
|
element.innerHTML = event.type;
|
|
events.appendChild(element);
|
|
};
|
|
|
|
var elements = document.getElementsByClassName("watch");
|
|
for (var i = 0; i < elements.length; i++) {
|
|
var element = elements[i];
|
|
element.addEventListener("focus", recordEvent);
|
|
element.addEventListener("keydown", recordEvent);
|
|
element.addEventListener("keypress", recordEvent);
|
|
element.addEventListener("keyup", recordEvent);
|
|
element.addEventListener("input", recordEvent);
|
|
element.addEventListener("change", recordEvent);
|
|
element.addEventListener("blur", recordEvent);
|
|
element.addEventListener("mousedown", recordEvent);
|
|
element.addEventListener("mouseup", recordEvent);
|
|
element.addEventListener("click", recordEvent);
|
|
}
|
|
</script>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
let(:newtext) { '12345' }
|
|
|
|
let(:keyevents) do
|
|
(%w{focus} +
|
|
newtext.length.times.collect { %w{keydown keypress input keyup} }
|
|
).flatten
|
|
end
|
|
|
|
let(:textevents) { keyevents + %w(change blur) }
|
|
|
|
%w(email number password search tel text url).each do | field_type |
|
|
it "triggers text input events on inputs of type #{field_type}" do
|
|
driver.find_xpath("//input[@type='#{field_type}']").first.set(newtext)
|
|
driver.find_xpath("//body").first.click
|
|
driver.find_xpath("//li").map(&:visible_text).should eq textevents
|
|
end
|
|
end
|
|
|
|
it "triggers events for cleared inputs" do
|
|
driver.find_xpath("//input[@type='text']").first.set('')
|
|
driver.find_xpath("//body").first.click
|
|
driver.find_xpath("//li").map(&:visible_text).should include('change')
|
|
end
|
|
|
|
it "triggers textarea input events" do
|
|
driver.find_xpath("//textarea").first.set(newtext)
|
|
driver.find_xpath("//li").map(&:visible_text).should eq keyevents
|
|
end
|
|
|
|
it "triggers radio input events" do
|
|
driver.find_xpath("//input[@type='radio']").first.set(true)
|
|
driver.find_xpath("//li").map(&:visible_text).should eq %w(mousedown focus mouseup change click)
|
|
end
|
|
|
|
it "triggers checkbox events" do
|
|
driver.find_xpath("//input[@type='checkbox']").first.set(true)
|
|
driver.find_xpath("//li").map(&:visible_text).should eq %w(mousedown focus mouseup change click)
|
|
end
|
|
end
|
|
|
|
context "mouse app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
<style type="text/css">
|
|
#hover { max-width: 30em; }
|
|
#hover span { line-height: 1.5; }
|
|
#hover span:hover + .hidden { display: block; }
|
|
.hidden { display: none; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="change">Change me</div>
|
|
<div id="mouseup">Push me</div>
|
|
<div id="mousedown">Release me</div>
|
|
<div id="hover">
|
|
<span>This really long paragraph has a lot of text and will wrap. This sentence ensures that we have four lines of text.</span>
|
|
<div class="hidden">Text that only shows on hover.</div>
|
|
</div>
|
|
<form action="/" method="GET">
|
|
<select id="change_select" name="change_select">
|
|
<option value="1" id="option-1" selected="selected">one</option>
|
|
<option value="2" id="option-2">two</option>
|
|
</select>
|
|
</form>
|
|
<script type="text/javascript">
|
|
document.getElementById("change_select").
|
|
addEventListener("change", function () {
|
|
this.className = "triggered";
|
|
});
|
|
document.getElementById("change").
|
|
addEventListener("change", function () {
|
|
this.className = "triggered";
|
|
});
|
|
document.getElementById("mouseup").
|
|
addEventListener("mouseup", function () {
|
|
this.className = "triggered";
|
|
});
|
|
document.getElementById("mousedown").
|
|
addEventListener("mousedown", function () {
|
|
this.className = "triggered";
|
|
});
|
|
</script>
|
|
<a href="/next">Next</a>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "hovers an element" do
|
|
driver.find_css("#hover").first.visible_text.should_not =~ /Text that only shows on hover/
|
|
driver.find_css("#hover span").first.hover
|
|
driver.find_css("#hover").first.visible_text.should =~ /Text that only shows on hover/
|
|
end
|
|
|
|
it "hovers an element off the screen" do
|
|
driver.resize_window(200, 200)
|
|
driver.evaluate_script(<<-JS)
|
|
var element = document.getElementById('hover');
|
|
element.style.position = 'absolute';
|
|
element.style.left = '200px';
|
|
JS
|
|
driver.find_css("#hover").first.visible_text.should_not =~ /Text that only shows on hover/
|
|
driver.find_css("#hover span").first.hover
|
|
driver.find_css("#hover").first.visible_text.should =~ /Text that only shows on hover/
|
|
end
|
|
|
|
it "clicks an element" do
|
|
driver.find_xpath("//a").first.click
|
|
driver.current_url =~ %r{/next$}
|
|
end
|
|
|
|
it "fires a mouse event" do
|
|
driver.find_xpath("//*[@id='mouseup']").first.trigger("mouseup")
|
|
driver.find_xpath("//*[@class='triggered']").should_not be_empty
|
|
end
|
|
|
|
it "fires a non-mouse event" do
|
|
driver.find_xpath("//*[@id='change']").first.trigger("change")
|
|
driver.find_xpath("//*[@class='triggered']").should_not be_empty
|
|
end
|
|
|
|
it "fires a change on select" do
|
|
select = driver.find_xpath("//select").first
|
|
select.value.should eq "1"
|
|
option = driver.find_xpath("//option[@id='option-2']").first
|
|
option.select_option
|
|
select.value.should eq "2"
|
|
driver.find_xpath("//select[@class='triggered']").should_not be_empty
|
|
end
|
|
|
|
it "fires drag events" do
|
|
draggable = driver.find_xpath("//*[@id='mousedown']").first
|
|
container = driver.find_xpath("//*[@id='mouseup']").first
|
|
|
|
draggable.drag_to(container)
|
|
|
|
driver.find_xpath("//*[@class='triggered']").size.should eq 1
|
|
end
|
|
end
|
|
|
|
context "nesting app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html><body>
|
|
<div id="parent">
|
|
<div class="find">Expected</div>
|
|
</div>
|
|
<div class="find">Unexpected</div>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "evaluates nested xpath expressions" do
|
|
parent = driver.find_xpath("//*[@id='parent']").first
|
|
parent.find_xpath("./*[@class='find']").map(&:visible_text).should eq %w(Expected)
|
|
end
|
|
|
|
it "finds elements by CSS" do
|
|
parent = driver.find_css("#parent").first
|
|
parent.find_css(".find").first.visible_text.should eq "Expected"
|
|
end
|
|
end
|
|
|
|
context "slow app" do
|
|
it "waits for a request to load" do
|
|
result = ""
|
|
driver = driver_for_app do
|
|
get "/result" do
|
|
sleep(0.5)
|
|
result << "finished"
|
|
""
|
|
end
|
|
|
|
get "/" do
|
|
%{<html><body><a href="/result">Go</a></body></html>}
|
|
end
|
|
end
|
|
visit("/", driver)
|
|
driver.find_xpath("//a").first.click
|
|
result.should eq "finished"
|
|
end
|
|
end
|
|
|
|
context "error app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/error" do
|
|
invalid_response
|
|
end
|
|
|
|
get "/" do
|
|
<<-HTML
|
|
<html><body>
|
|
<form action="/error"><input type="submit"/></form>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "raises a webkit error for the requested url" do
|
|
expect {
|
|
driver.find_xpath("//input").first.click
|
|
wait_for_error_to_complete
|
|
driver.find_xpath("//body")
|
|
}.
|
|
to raise_error(Capybara::Webkit::InvalidResponseError, %r{/error})
|
|
end
|
|
|
|
def wait_for_error_to_complete
|
|
sleep(0.5)
|
|
end
|
|
end
|
|
|
|
context "slow error app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/error" do
|
|
sleep(1)
|
|
invalid_response
|
|
end
|
|
|
|
get "/" do
|
|
<<-HTML
|
|
<html><body>
|
|
<form action="/error"><input type="submit"/></form>
|
|
<p>hello</p>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "raises a webkit error and then continues" do
|
|
driver.find_xpath("//input").first.click
|
|
expect { driver.find_xpath("//p") }.to raise_error(Capybara::Webkit::InvalidResponseError)
|
|
visit("/")
|
|
driver.find_xpath("//p").first.visible_text.should eq "hello"
|
|
end
|
|
end
|
|
|
|
context "popup app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/" do
|
|
sleep(0.5)
|
|
return <<-HTML
|
|
<html><body>
|
|
<script type="text/javascript">
|
|
alert("alert");
|
|
confirm("confirm");
|
|
prompt("prompt");
|
|
</script>
|
|
<p>success</p>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "doesn't crash from alerts" do
|
|
driver.find_xpath("//p").first.visible_text.should eq "success"
|
|
end
|
|
end
|
|
|
|
context "custom header" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/" do
|
|
<<-HTML
|
|
<html><body>
|
|
<p id="user-agent">#{env['HTTP_USER_AGENT']}</p>
|
|
<p id="x-capybara-webkit-header">#{env['HTTP_X_CAPYBARA_WEBKIT_HEADER']}</p>
|
|
<p id="accept">#{env['HTTP_ACCEPT']}</p>
|
|
<a href="/">/</a>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
before do
|
|
driver.header('user-agent', 'capybara-webkit/custom-user-agent')
|
|
driver.header('x-capybara-webkit-header', 'x-capybara-webkit-header')
|
|
driver.header('accept', 'text/html')
|
|
visit('/')
|
|
end
|
|
|
|
it "can set user_agent" do
|
|
driver.find_xpath('id("user-agent")').first.visible_text.should eq 'capybara-webkit/custom-user-agent'
|
|
driver.evaluate_script('navigator.userAgent').should eq 'capybara-webkit/custom-user-agent'
|
|
end
|
|
|
|
it "keep user_agent in next page" do
|
|
driver.find_xpath("//a").first.click
|
|
driver.find_xpath('id("user-agent")').first.visible_text.should eq 'capybara-webkit/custom-user-agent'
|
|
driver.evaluate_script('navigator.userAgent').should eq 'capybara-webkit/custom-user-agent'
|
|
end
|
|
|
|
it "can set custom header" do
|
|
driver.find_xpath('id("x-capybara-webkit-header")').first.visible_text.should eq 'x-capybara-webkit-header'
|
|
end
|
|
|
|
it "can set Accept header" do
|
|
driver.find_xpath('id("accept")').first.visible_text.should eq 'text/html'
|
|
end
|
|
|
|
it "can reset all custom header" do
|
|
driver.reset!
|
|
visit('/')
|
|
driver.find_xpath('id("user-agent")').first.visible_text.should_not eq 'capybara-webkit/custom-user-agent'
|
|
driver.evaluate_script('navigator.userAgent').should_not eq 'capybara-webkit/custom-user-agent'
|
|
driver.find_xpath('id("x-capybara-webkit-header")').first.visible_text.should be_empty
|
|
driver.find_xpath('id("accept")').first.visible_text.should_not eq 'text/html'
|
|
end
|
|
end
|
|
|
|
context "no response app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html><body>
|
|
<form action="/error"><input type="submit"/></form>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "raises a webkit error for the requested url" do
|
|
make_the_server_go_away
|
|
expect {
|
|
driver.find_xpath("//body")
|
|
}.
|
|
to raise_error(Capybara::Webkit::NoResponseError, %r{response})
|
|
make_the_server_come_back
|
|
end
|
|
|
|
def make_the_server_come_back
|
|
driver.browser.instance_variable_get(:@connection).unstub(:gets)
|
|
driver.browser.instance_variable_get(:@connection).unstub(:puts)
|
|
driver.browser.instance_variable_get(:@connection).unstub(:print)
|
|
end
|
|
|
|
def make_the_server_go_away
|
|
driver.browser.instance_variable_get(:@connection).stub(:gets).and_return(nil)
|
|
driver.browser.instance_variable_get(:@connection).stub(:puts)
|
|
driver.browser.instance_variable_get(:@connection).stub(:print)
|
|
end
|
|
end
|
|
|
|
context "custom font app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
<style type="text/css">
|
|
p { font-family: "Verdana"; }
|
|
p:before { font-family: "Verdana"; }
|
|
p:after { font-family: "Verdana"; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<p id="text">Hello</p>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
let(:font_family) do
|
|
driver.evaluate_script(<<-SCRIPT)
|
|
var element = document.getElementById("text");
|
|
element.ownerDocument.defaultView.getComputedStyle(element, null).getPropertyValue("font-family");
|
|
SCRIPT
|
|
end
|
|
|
|
it "ignores custom fonts" do
|
|
font_family.should eq "Arial"
|
|
end
|
|
|
|
it "ignores custom fonts before an element" do
|
|
font_family.should eq "Arial"
|
|
end
|
|
|
|
it "ignores custom fonts after an element" do
|
|
font_family.should eq "Arial"
|
|
end
|
|
end
|
|
|
|
context "cookie-based app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/" do
|
|
headers 'Set-Cookie' => 'cookie=abc; domain=127.0.0.1; path=/'
|
|
<<-HTML
|
|
<html><body>
|
|
<p id="cookie">#{request.cookies["cookie"] || ""}</p>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
def echoed_cookie
|
|
driver.find_xpath('id("cookie")').first.visible_text
|
|
end
|
|
|
|
it "remembers the cookie on second visit" do
|
|
echoed_cookie.should eq ""
|
|
visit "/"
|
|
echoed_cookie.should eq "abc"
|
|
end
|
|
|
|
it "uses a custom cookie" do
|
|
driver.browser.set_cookie 'cookie=abc; domain=127.0.0.1; path=/'
|
|
visit "/"
|
|
echoed_cookie.should eq "abc"
|
|
end
|
|
|
|
it "clears cookies" do
|
|
driver.browser.clear_cookies
|
|
visit "/"
|
|
echoed_cookie.should eq ""
|
|
end
|
|
|
|
it "allows enumeration of cookies" do
|
|
cookies = driver.browser.get_cookies
|
|
|
|
cookies.size.should eq 1
|
|
|
|
cookie = Hash[cookies[0].split(/\s*;\s*/).map { |x| x.split("=", 2) }]
|
|
cookie["cookie"].should eq "abc"
|
|
cookie["domain"].should include "127.0.0.1"
|
|
cookie["path"].should eq "/"
|
|
end
|
|
|
|
it "allows reading access to cookies using a nice syntax" do
|
|
driver.cookies["cookie"].should eq "abc"
|
|
end
|
|
end
|
|
|
|
context "remove node app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<div id="parent">
|
|
<p id="removeMe">Hello</p>
|
|
</div>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
before { set_automatic_reload false }
|
|
after { set_automatic_reload true }
|
|
|
|
def set_automatic_reload(value)
|
|
if Capybara.respond_to?(:automatic_reload)
|
|
Capybara.automatic_reload = value
|
|
end
|
|
end
|
|
|
|
it "allows removed nodes when reloading is disabled" do
|
|
node = driver.find_xpath("//p[@id='removeMe']").first
|
|
driver.evaluate_script("document.getElementById('parent').innerHTML = 'Magic'")
|
|
node.visible_text.should eq 'Hello'
|
|
end
|
|
end
|
|
|
|
context "app with a lot of HTML tags" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
<title>My eBook</title>
|
|
<meta class="charset" name="charset" value="utf-8" />
|
|
<meta class="author" name="author" value="Firstname Lastname" />
|
|
</head>
|
|
<body>
|
|
<div id="toc">
|
|
<table>
|
|
<thead id="head">
|
|
<tr><td class="td1">Chapter</td><td>Page</td></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td>Intro</td><td>1</td></tr>
|
|
<tr><td>Chapter 1</td><td class="td2">1</td></tr>
|
|
<tr><td>Chapter 2</td><td>1</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<h1 class="h1">My first book</h1>
|
|
<p class="p1">Written by me</p>
|
|
<div id="intro" class="intro">
|
|
<p>Let's try out XPath</p>
|
|
<p class="p2">in capybara-webkit</p>
|
|
</div>
|
|
|
|
<h2 class="chapter1">Chapter 1</h2>
|
|
<p>This paragraph is fascinating.</p>
|
|
<p class="p3">But not as much as this one.</p>
|
|
|
|
<h2 class="chapter2">Chapter 2</h2>
|
|
<p>Let's try if we can select this</p>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "builds up node paths correctly" do
|
|
cases = {
|
|
"//*[contains(@class, 'author')]" => "/html/head/meta[2]",
|
|
"//*[contains(@class, 'td1')]" => "/html/body/div[@id='toc']/table/thead[@id='head']/tr/td[1]",
|
|
"//*[contains(@class, 'td2')]" => "/html/body/div[@id='toc']/table/tbody/tr[2]/td[2]",
|
|
"//h1" => "/html/body/h1",
|
|
"//*[contains(@class, 'chapter2')]" => "/html/body/h2[2]",
|
|
"//*[contains(@class, 'p1')]" => "/html/body/p[1]",
|
|
"//*[contains(@class, 'p2')]" => "/html/body/div[@id='intro']/p[2]",
|
|
"//*[contains(@class, 'p3')]" => "/html/body/p[3]",
|
|
}
|
|
|
|
cases.each do |xpath, path|
|
|
nodes = driver.find_xpath(xpath)
|
|
nodes.size.should eq 1
|
|
nodes[0].path.should eq path
|
|
end
|
|
end
|
|
end
|
|
|
|
context "css overflow app" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<head>
|
|
<style type="text/css">
|
|
#overflow { overflow: hidden }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="overflow">Overflow</div>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "handles overflow hidden" do
|
|
driver.find_xpath("//div[@id='overflow']").first.visible_text.should eq "Overflow"
|
|
end
|
|
end
|
|
|
|
context "javascript redirect app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get '/redirect' do
|
|
<<-HTML
|
|
<html>
|
|
<script type="text/javascript">
|
|
window.location = "/";
|
|
</script>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
get '/' do
|
|
"<html><p>finished</p></html>"
|
|
end
|
|
end
|
|
end
|
|
|
|
it "loads a page without error" do
|
|
10.times do
|
|
visit("/redirect")
|
|
driver.find_xpath("//p").first.visible_text.should eq "finished"
|
|
end
|
|
end
|
|
end
|
|
|
|
context "localStorage works" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<body>
|
|
<span id='output'></span>
|
|
<script type="text/javascript">
|
|
if (typeof localStorage !== "undefined") {
|
|
if (!localStorage.refreshCounter) {
|
|
localStorage.refreshCounter = 0;
|
|
}
|
|
if (localStorage.refreshCounter++ > 0) {
|
|
document.getElementById("output").innerHTML = "localStorage is enabled";
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "displays the message on subsequent page loads" do
|
|
driver.find_xpath("//span[contains(.,'localStorage is enabled')]").should be_empty
|
|
visit "/"
|
|
driver.find_xpath("//span[contains(.,'localStorage is enabled')]").should_not be_empty
|
|
end
|
|
|
|
it "clears the message after a driver reset!" do
|
|
visit "/"
|
|
driver.find_xpath("//span[contains(.,'localStorage is enabled')]").should_not be_empty
|
|
driver.reset!
|
|
visit "/"
|
|
driver.find_xpath("//span[contains(.,'localStorage is enabled')]").should be_empty
|
|
end
|
|
end
|
|
|
|
context "form app with server-side handler" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
post "/" do
|
|
"<html><body><p>Congrats!</p></body></html>"
|
|
end
|
|
|
|
get "/" do
|
|
<<-HTML
|
|
<html>
|
|
<head><title>Form</title>
|
|
<body>
|
|
<form action="/" method="POST">
|
|
<input type="hidden" name="abc" value="123" />
|
|
<input type="submit" value="Submit" />
|
|
</form>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "submits a form without clicking" do
|
|
driver.find_xpath("//form")[0].submit
|
|
driver.html.should include "Congrats"
|
|
end
|
|
end
|
|
|
|
def driver_for_key_body(event)
|
|
driver_for_app do
|
|
get "/" do
|
|
<<-HTML
|
|
<html>
|
|
<head><title>Form</title></head>
|
|
<body>
|
|
<div id="charcode_value"></div>
|
|
<div id="keycode_value"></div>
|
|
<div id="which_value"></div>
|
|
<input type="text" id="charcode" name="charcode" on#{event}="setcharcode" />
|
|
<script type="text/javascript">
|
|
var element = document.getElementById("charcode")
|
|
element.addEventListener("#{event}", setcharcode);
|
|
function setcharcode(event) {
|
|
var element = document.getElementById("charcode_value");
|
|
element.innerHTML = event.charCode;
|
|
element = document.getElementById("keycode_value");
|
|
element.innerHTML = event.keyCode;
|
|
element = document.getElementById("which_value");
|
|
element.innerHTML = event.which;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
def charCode_for(character)
|
|
driver.find_xpath("//input")[0].set(character)
|
|
driver.find_xpath("//div[@id='charcode_value']")[0].visible_text
|
|
end
|
|
|
|
def keyCode_for(character)
|
|
driver.find_xpath("//input")[0].set(character)
|
|
driver.find_xpath("//div[@id='keycode_value']")[0].visible_text
|
|
end
|
|
|
|
def which_for(character)
|
|
driver.find_xpath("//input")[0].set(character)
|
|
driver.find_xpath("//div[@id='which_value']")[0].visible_text
|
|
end
|
|
|
|
context "keypress app" do
|
|
let(:driver) { driver_for_key_body "keypress" }
|
|
|
|
before { visit("/") }
|
|
|
|
it "returns the charCode for the keypressed" do
|
|
charCode_for("a").should eq "97"
|
|
charCode_for("A").should eq "65"
|
|
charCode_for("\r").should eq "13"
|
|
charCode_for(",").should eq "44"
|
|
charCode_for("<").should eq "60"
|
|
charCode_for("0").should eq "48"
|
|
end
|
|
|
|
it "returns the keyCode for the keypressed" do
|
|
keyCode_for("a").should eq "97"
|
|
keyCode_for("A").should eq "65"
|
|
keyCode_for("\r").should eq "13"
|
|
keyCode_for(",").should eq "44"
|
|
keyCode_for("<").should eq "60"
|
|
keyCode_for("0").should eq "48"
|
|
end
|
|
|
|
it "returns the which for the keypressed" do
|
|
which_for("a").should eq "97"
|
|
which_for("A").should eq "65"
|
|
which_for("\r").should eq "13"
|
|
which_for(",").should eq "44"
|
|
which_for("<").should eq "60"
|
|
which_for("0").should eq "48"
|
|
end
|
|
end
|
|
|
|
shared_examples "a keyupdown app" do
|
|
it "returns a 0 charCode for the event" do
|
|
charCode_for("a").should eq "0"
|
|
charCode_for("A").should eq "0"
|
|
charCode_for("\b").should eq "0"
|
|
charCode_for(",").should eq "0"
|
|
charCode_for("<").should eq "0"
|
|
charCode_for("0").should eq "0"
|
|
end
|
|
|
|
it "returns the keyCode for the event" do
|
|
keyCode_for("a").should eq "65"
|
|
keyCode_for("A").should eq "65"
|
|
keyCode_for("\b").should eq "8"
|
|
keyCode_for(",").should eq "188"
|
|
keyCode_for("<").should eq "188"
|
|
keyCode_for("0").should eq "48"
|
|
end
|
|
|
|
it "returns the which for the event" do
|
|
which_for("a").should eq "65"
|
|
which_for("A").should eq "65"
|
|
which_for("\b").should eq "8"
|
|
which_for(",").should eq "188"
|
|
which_for("<").should eq "188"
|
|
which_for("0").should eq "48"
|
|
end
|
|
end
|
|
|
|
context "keydown app" do
|
|
let(:driver) { driver_for_key_body "keydown" }
|
|
before { visit("/") }
|
|
it_behaves_like "a keyupdown app"
|
|
end
|
|
|
|
context "keyup app" do
|
|
let(:driver) { driver_for_key_body "keyup" }
|
|
before { visit("/") }
|
|
it_behaves_like "a keyupdown app"
|
|
end
|
|
|
|
context "javascript new window app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get '/new_window' do
|
|
<<-HTML
|
|
<html>
|
|
<script type="text/javascript">
|
|
window.open('http://#{request.host_with_port}/?#{request.query_string}', 'myWindow');
|
|
</script>
|
|
<p>bananas</p>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
get "/" do
|
|
sleep params['sleep'].to_i if params['sleep']
|
|
"<html><head><title>My New Window</title></head><body><p>finished</p></body></html>"
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "has the expected text in the new window" do
|
|
visit("/new_window")
|
|
driver.within_window(driver.window_handles.last) do
|
|
driver.find_xpath("//p").first.visible_text.should eq "finished"
|
|
end
|
|
end
|
|
|
|
it "can switch to another window" do
|
|
visit("/new_window")
|
|
driver.switch_to_window(driver.window_handles.last)
|
|
driver.find_xpath("//p").first.visible_text.should eq "finished"
|
|
end
|
|
|
|
it "knows the current window handle" do
|
|
visit("/new_window")
|
|
driver.within_window(driver.window_handles.last) do
|
|
driver.current_window_handle.should eq driver.window_handles.last
|
|
end
|
|
end
|
|
|
|
it "can close the current window" do
|
|
visit("/new_window")
|
|
original_handle = driver.current_window_handle
|
|
driver.switch_to_window(driver.window_handles.last)
|
|
driver.close_window(driver.current_window_handle)
|
|
|
|
driver.current_window_handle.should eq(original_handle)
|
|
end
|
|
|
|
it "can close an unfocused window" do
|
|
visit("/new_window")
|
|
driver.close_window(driver.window_handles.last)
|
|
driver.window_handles.size.should eq(1)
|
|
end
|
|
|
|
it "can close the last window" do
|
|
visit("/new_window")
|
|
handles = driver.window_handles
|
|
handles.each { |handle| driver.close_window(handle) }
|
|
driver.html.should be_empty
|
|
handles.should_not include(driver.current_window_handle)
|
|
end
|
|
|
|
it "waits for the new window to load" do
|
|
visit("/new_window?sleep=1")
|
|
driver.within_window(driver.window_handles.last) do
|
|
driver.find_xpath("//p").first.visible_text.should eq "finished"
|
|
end
|
|
end
|
|
|
|
it "waits for the new window to load when the window location has changed" do
|
|
visit("/new_window?sleep=2")
|
|
driver.execute_script("setTimeout(function() { window.location = 'about:blank' }, 1000)")
|
|
driver.within_window(driver.window_handles.last) do
|
|
driver.find_xpath("//p").first.visible_text.should eq "finished"
|
|
end
|
|
end
|
|
|
|
it "switches back to the original window" do
|
|
visit("/new_window")
|
|
driver.within_window(driver.window_handles.last) { }
|
|
driver.find_xpath("//p").first.visible_text.should eq "bananas"
|
|
end
|
|
|
|
it "supports finding a window by name" do
|
|
visit("/new_window")
|
|
driver.within_window('myWindow') do
|
|
driver.find_xpath("//p").first.visible_text.should eq "finished"
|
|
end
|
|
end
|
|
|
|
it "supports finding a window by title" do
|
|
visit("/new_window?sleep=5")
|
|
driver.within_window('My New Window') do
|
|
driver.find_xpath("//p").first.visible_text.should eq "finished"
|
|
end
|
|
end
|
|
|
|
it "supports finding a window by url" do
|
|
visit("/new_window?test")
|
|
driver.within_window(driver_url(driver, "/?test")) do
|
|
driver.find_xpath("//p").first.visible_text.should eq "finished"
|
|
end
|
|
end
|
|
|
|
it "raises an error if the window is not found" do
|
|
expect { driver.within_window('myWindowDoesNotExist') }.
|
|
to raise_error(Capybara::Webkit::NoSuchWindowError)
|
|
end
|
|
|
|
it "has a number of window handles equal to the number of open windows" do
|
|
driver.window_handles.size.should eq 1
|
|
visit("/new_window")
|
|
driver.window_handles.size.should eq 2
|
|
end
|
|
|
|
it "removes windows when closed via JavaScript" do
|
|
visit("/new_window")
|
|
driver.execute_script('console.log(window.document.title); window.close()')
|
|
sleep 2
|
|
driver.window_handles.size.should eq 1
|
|
end
|
|
|
|
it "closes new windows on reset" do
|
|
visit("/new_window")
|
|
last_handle = driver.window_handles.last
|
|
driver.reset!
|
|
driver.window_handles.should_not include(last_handle)
|
|
end
|
|
|
|
it "leaves the old window focused when opening a new window" do
|
|
visit("/new_window")
|
|
current_window = driver.current_window_handle
|
|
driver.open_new_window
|
|
|
|
driver.current_window_handle.should eq current_window
|
|
driver.window_handles.size.should eq 3
|
|
end
|
|
|
|
it "opens blank windows" do
|
|
visit("/new_window")
|
|
driver.open_new_window
|
|
driver.switch_to_window(driver.window_handles.last)
|
|
driver.html.should be_empty
|
|
end
|
|
end
|
|
|
|
it "preserves cookies across windows" do
|
|
session_id = '12345'
|
|
driver = driver_for_app do
|
|
get '/new_window' do
|
|
<<-HTML
|
|
<html>
|
|
<script type="text/javascript">
|
|
window.open('http://#{request.host_with_port}/set_cookie');
|
|
</script>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
get '/set_cookie' do
|
|
response.set_cookie 'session_id', session_id
|
|
end
|
|
end
|
|
|
|
visit("/new_window", driver)
|
|
driver.cookies['session_id'].should eq session_id
|
|
end
|
|
|
|
context "timers app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/success" do
|
|
'<html><body></body></html>'
|
|
end
|
|
|
|
get "/not-found" do
|
|
404
|
|
end
|
|
|
|
get "/outer" do
|
|
<<-HTML
|
|
<html>
|
|
<head>
|
|
<script>
|
|
function emit_true_load_finished(){var divTag = document.createElement("div");divTag.innerHTML = "<iframe src='/success'></iframe>";document.body.appendChild(divTag);};
|
|
function emit_false_load_finished(){var divTag = document.createElement("div");divTag.innerHTML = "<iframe src='/not-found'></iframe>";document.body.appendChild(divTag);};
|
|
function emit_false_true_load_finished() { emit_false_load_finished(); setTimeout('emit_true_load_finished()',100); };
|
|
</script>
|
|
</head>
|
|
<body onload="setTimeout('emit_false_true_load_finished()',100)">
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
get '/' do
|
|
"<html><body></body></html>"
|
|
end
|
|
end
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "raises error for any loadFinished failure" do
|
|
expect do
|
|
visit("/outer")
|
|
sleep 1
|
|
driver.find_xpath("//body")
|
|
end.to raise_error(Capybara::Webkit::InvalidResponseError)
|
|
end
|
|
end
|
|
|
|
describe "basic auth" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/" do
|
|
if env["HTTP_AUTHORIZATION"] == "Basic #{Base64.encode64("user:password").strip}"
|
|
env["HTTP_AUTHORIZATION"]
|
|
else
|
|
headers "WWW-Authenticate" => 'Basic realm="Secure Area"'
|
|
status 401
|
|
"401 Unauthorized."
|
|
end
|
|
end
|
|
|
|
get "/reset" do
|
|
headers "WWW-Authenticate" => 'Basic realm="Secure Area"'
|
|
status 401
|
|
"401 Unauthorized."
|
|
end
|
|
end
|
|
end
|
|
|
|
before do
|
|
visit('/reset')
|
|
end
|
|
|
|
it "can authenticate a request" do
|
|
driver.browser.authenticate('user', 'password')
|
|
visit("/")
|
|
driver.html.should include("Basic "+Base64.encode64("user:password").strip)
|
|
end
|
|
|
|
it "returns 401 for incorrectly authenticated request" do
|
|
driver.browser.authenticate('user1', 'password1')
|
|
driver.browser.timeout = 2
|
|
lambda { visit("/") }.should_not raise_error
|
|
driver.status_code.should eq 401
|
|
end
|
|
|
|
it "returns 401 for unauthenticated request" do
|
|
driver.browser.timeout = 2
|
|
lambda { visit("/") }.should_not raise_error
|
|
driver.status_code.should eq 401
|
|
end
|
|
end
|
|
|
|
describe "url blacklisting" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/" do
|
|
<<-HTML
|
|
<html>
|
|
<body>
|
|
<script src="/script"></script>
|
|
<iframe src="http://example.com/path" id="frame1"></iframe>
|
|
<iframe src="http://example.org/path/to/file" id="frame2"></iframe>
|
|
<iframe src="/frame" id="frame3"></iframe>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
get "/frame" do
|
|
<<-HTML
|
|
<html>
|
|
<body>
|
|
<p>Inner</p>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
get "/script" do
|
|
<<-JS
|
|
document.write('<p>Script Run</p>')
|
|
JS
|
|
end
|
|
end
|
|
end
|
|
|
|
before do
|
|
driver.browser.url_blacklist = ["http://example.org/path/to/file",
|
|
"http://example.com",
|
|
"#{AppRunner.app_host}/script"]
|
|
end
|
|
|
|
it "should not fetch urls blocked by host" do
|
|
visit("/")
|
|
driver.within_frame('frame1') do
|
|
driver.find_xpath("//body").first.visible_text.should be_empty
|
|
end
|
|
end
|
|
|
|
it "should not fetch urls blocked by path" do
|
|
visit('/')
|
|
driver.within_frame('frame2') do
|
|
driver.find_xpath("//body").first.visible_text.should be_empty
|
|
end
|
|
end
|
|
|
|
it "should not fetch blocked scripts" do
|
|
visit("/")
|
|
driver.html.should_not include("Script Run")
|
|
end
|
|
|
|
it "should fetch unblocked urls" do
|
|
visit('/')
|
|
driver.within_frame('frame3') do
|
|
driver.find_xpath("//p").first.visible_text.should eq "Inner"
|
|
end
|
|
end
|
|
|
|
it "returns a status code for blocked urls" do
|
|
visit("/")
|
|
driver.within_frame('frame1') do
|
|
driver.status_code.should eq 200
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "timeout for long requests" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
html = <<-HTML
|
|
<html>
|
|
<body>
|
|
<form action="/form" method="post">
|
|
<input type="submit" value="Submit"/>
|
|
</form>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
|
|
get "/" do
|
|
sleep(2)
|
|
html
|
|
end
|
|
|
|
post "/form" do
|
|
sleep(4)
|
|
html
|
|
end
|
|
end
|
|
end
|
|
|
|
it "should not raise a timeout error when zero" do
|
|
driver.browser.timeout = 0
|
|
lambda { visit("/") }.should_not raise_error
|
|
end
|
|
|
|
it "should raise a timeout error" do
|
|
driver.browser.timeout = 1
|
|
lambda { visit("/") }.should raise_error(Timeout::Error, "Request timed out after 1 second(s)")
|
|
end
|
|
|
|
it "should not raise an error when the timeout is high enough" do
|
|
driver.browser.timeout = 10
|
|
lambda { visit("/") }.should_not raise_error
|
|
end
|
|
|
|
it "should set the timeout for each request" do
|
|
driver.browser.timeout = 10
|
|
lambda { visit("/") }.should_not raise_error
|
|
driver.browser.timeout = 1
|
|
lambda { visit("/") }.should raise_error(Timeout::Error)
|
|
end
|
|
|
|
it "should set the timeout for each request" do
|
|
driver.browser.timeout = 1
|
|
lambda { visit("/") }.should raise_error(Timeout::Error)
|
|
driver.reset!
|
|
driver.browser.timeout = 10
|
|
lambda { visit("/") }.should_not raise_error
|
|
end
|
|
|
|
it "should raise a timeout on a slow form" do
|
|
driver.browser.timeout = 3
|
|
visit("/")
|
|
driver.status_code.should eq 200
|
|
driver.browser.timeout = 1
|
|
driver.find_xpath("//input").first.click
|
|
lambda { driver.status_code }.should raise_error(Timeout::Error)
|
|
end
|
|
|
|
it "get timeout" do
|
|
driver.browser.timeout = 10
|
|
driver.browser.timeout.should eq 10
|
|
driver.browser.timeout = 3
|
|
driver.browser.timeout.should eq 3
|
|
end
|
|
end
|
|
|
|
describe "logger app" do
|
|
it "logs nothing before turning on the logger" do
|
|
visit("/")
|
|
@write.close
|
|
log.should_not include logging_message
|
|
end
|
|
|
|
it "logs its commands after turning on the logger" do
|
|
driver.enable_logging
|
|
visit("/")
|
|
@write.close
|
|
log.should include logging_message
|
|
end
|
|
|
|
before do
|
|
@read, @write = IO.pipe
|
|
end
|
|
|
|
let(:logging_message) { 'Wrote response true' }
|
|
|
|
let(:driver) do
|
|
connection = Capybara::Webkit::Connection.new(:stderr => @write)
|
|
browser = Capybara::Webkit::Browser.new(connection)
|
|
Capybara::Webkit::Driver.new(AppRunner.app, :browser => browser)
|
|
end
|
|
|
|
def log
|
|
@read.read
|
|
end
|
|
end
|
|
|
|
context "synchronous ajax app" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get '/' do
|
|
<<-HTML
|
|
<html>
|
|
<body>
|
|
<form id="theForm">
|
|
<input type="submit" value="Submit" />
|
|
</form>
|
|
<script>
|
|
document.getElementById('theForm').onsubmit = function() {
|
|
xhr = new XMLHttpRequest();
|
|
xhr.open('POST', '/', false);
|
|
xhr.setRequestHeader('Content-Type', 'text/plain');
|
|
xhr.send('hello');
|
|
console.log(xhr.response);
|
|
return false;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
post '/' do
|
|
request.body.read
|
|
end
|
|
end
|
|
end
|
|
|
|
it 'should not hang the server' do
|
|
visit('/')
|
|
driver.find_xpath('//input').first.click
|
|
driver.console_messages.first[:message].should eq "hello"
|
|
end
|
|
end
|
|
|
|
context 'path' do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html>
|
|
<body>
|
|
<div></div>
|
|
<div><div><span>hello</span></div></div>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
|
|
it 'returns an xpath for the current node' do
|
|
visit('/')
|
|
path = driver.find_xpath('//span').first.path
|
|
driver.find_xpath(path).first.text.should eq 'hello'
|
|
end
|
|
end
|
|
|
|
context 'unattached node app' do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html><body>
|
|
<p id="remove-me">Remove me</p>
|
|
<a href="#" id="remove-button">remove</a>
|
|
<script type="text/javascript">
|
|
document.getElementById('remove-button').addEventListener('click', function() {
|
|
var p = document.getElementById('remove-me');
|
|
p.parentNode.removeChild(p);
|
|
});
|
|
</script>
|
|
</body></html>
|
|
HTML
|
|
end
|
|
|
|
it 'raises NodeNotAttachedError' do
|
|
visit '/'
|
|
remove_me = driver.find_css('#remove-me').first
|
|
expect(remove_me).not_to be_nil
|
|
driver.find_css('#remove-button').first.click
|
|
expect { remove_me.text }.to raise_error(Capybara::Webkit::NodeNotAttachedError)
|
|
end
|
|
|
|
it 'raises NodeNotAttachedError if the argument node is unattached' do
|
|
visit '/'
|
|
remove_me = driver.find_css('#remove-me').first
|
|
expect(remove_me).not_to be_nil
|
|
remove_button = driver.find_css('#remove-button').first
|
|
expect(remove_button).not_to be_nil
|
|
remove_button.click
|
|
expect { remove_button == remove_me }.to raise_error(Capybara::Webkit::NodeNotAttachedError)
|
|
expect { remove_me == remove_button }.to raise_error(Capybara::Webkit::NodeNotAttachedError)
|
|
end
|
|
end
|
|
|
|
context "version" do
|
|
let(:driver) do
|
|
driver_for_html(<<-HTML)
|
|
<html><body></body></html>
|
|
HTML
|
|
end
|
|
|
|
before { visit("/") }
|
|
|
|
it "includes Capybara, capybara-webkit, Qt, and WebKit versions" do
|
|
result = driver.version
|
|
result.should include("Capybara: #{Capybara::VERSION}")
|
|
result.should include("capybara-webkit: #{Capybara::Driver::Webkit::VERSION}")
|
|
result.should =~ /Qt: \d+\.\d+\.\d+/
|
|
result.should =~ /WebKit: \d+\.\d+/
|
|
result.should =~ /QtWebKit: \d+\.\d+/
|
|
end
|
|
end
|
|
|
|
context "history" do
|
|
let(:driver) do
|
|
driver_for_app do
|
|
get "/:param" do |param|
|
|
<<-HTML
|
|
<html>
|
|
<body>
|
|
<p>#{param}</p>
|
|
<a href="/navigated">Navigate</a>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
end
|
|
end
|
|
end
|
|
|
|
it "can navigate in history" do
|
|
visit("/first")
|
|
driver.find_xpath("//p").first.text.should eq('first')
|
|
driver.find_xpath("//a").first.click
|
|
driver.find_xpath("//p").first.text.should eq('navigated')
|
|
driver.go_back
|
|
driver.find_xpath("//p").first.text.should eq('first')
|
|
driver.go_forward
|
|
driver.find_xpath("//p").first.text.should eq('navigated')
|
|
end
|
|
end
|
|
|
|
def driver_url(driver, path)
|
|
URI.parse(driver.current_url).merge(path).to_s
|
|
end
|
|
end
|