# -*- encoding: UTF-8 -*-

require 'spec_helper'
require 'capybara/webkit/driver'
require 'base64'

describe Capybara::Webkit::Driver do
  include AppRunner

  def visit(path, driver=self.driver)
    driver.visit(url(path))
  end

  def url(path)
    "#{AppRunner.app_host}#{path}"
  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&nbsp;not&nbsp;normalized&nbsp;</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 "hidden text app" do
    let(:driver) do
      driver_for_html(<<-HTML)
        <html>
          <body>
            <h1 style="display: none">Hello</h1>
          </body>
        </html>
      HTML
    end

    before { visit("/") }

    it "has no visible text" do
      driver.find_xpath("/html").first.text.should be_empty
    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
    before do
      stub_const('Capybara::ModalNotFound', Class.new(StandardError))
    end

    context "on an alert app" do
      let(:driver) do
        driver_for_app do
          get '/' do
            <<-HTML
              <html>
                <head>
                </head>
                <body>
                  <script type="text/javascript">
                    alert("Alert Text\\nGoes Here");
                  </script>
                </body>
              </html>
            HTML
          end

          get '/async' do
            <<-HTML
              <html>
                <head>
                </head>
                <body>
                  <script type="text/javascript">
                    function testAlert() {
                      setTimeout(function() { alert("Alert Text\\nGoes Here"); },
                        #{params[:sleep] || 100});
                    }
                  </script>
                  <input type="button" onclick="testAlert()" name="test"/>
                </body>
              </html>
            HTML
          end

          get '/ajax' do
            <<-HTML
              <html>
                <head>
                </head>
                <body>
                  <script type="text/javascript">
                    function testAlert() {
                      var xhr = new XMLHttpRequest();
                      xhr.open('GET', '/slow', true);
                      xhr.setRequestHeader('Content-Type', 'text/plain');
                      xhr.onreadystatechange = function () {
                        if (xhr.readyState == 4) {
                          alert('From ajax');
                        }
                      };
                      xhr.send();
                    }
                  </script>
                  <input type="button" onclick="testAlert()" name="test"/>
                </body>
              </html>
            HTML
          end

          get '/double' do
            <<-HTML
              <html>
                <head>
                </head>
                <body>
                  <script type="text/javascript">
                    alert('First alert'); 
                  </script>
                  <input type="button" onclick="alert('Second alert')" name="test"/>
                </body>
              </html>
            HTML
          end

          get '/slow' do
            sleep 0.5
            ""
          end
        end
      end

      it 'accepts any alert modal if no match is provided' do
        alert_message = driver.accept_modal(:alert) do
          visit("/")
        end
        alert_message.should eq "Alert Text\nGoes Here"
      end

      it 'accepts an alert modal if it matches' do
        alert_message = driver.accept_modal(:alert, text: "Alert Text\nGoes Here") do
          visit("/")
        end
        alert_message.should eq "Alert Text\nGoes Here"
      end

      it 'raises an error when accepting an alert modal that does not match' do
        expect {
          driver.accept_modal(:alert, text: 'No?') do
            visit('/')
          end
        }.to raise_error Capybara::ModalNotFound, "Unable to find modal dialog with No?"
      end

      it 'finds two alert windows in a row' do
        driver.accept_modal(:alert, text: 'First alert') do 
          visit('/double')
        end

        expect {
          driver.accept_modal(:alert, text: 'Boom') do 
            driver.find_xpath("//input").first.click
          end
        }.to raise_error Capybara::ModalNotFound, "Unable to find modal dialog with Boom"
      end

      it 'waits to accept an async alert modal' do
        visit("/async")
        alert_message = driver.accept_modal(:alert) do
          driver.find_xpath("//input").first.click
        end
        alert_message.should eq "Alert Text\nGoes Here"
      end

      it 'times out waiting for an async alert modal' do
        visit("/async?sleep=1000")
        expect {
          driver.accept_modal(:alert, wait: 0.1) do
            driver.find_xpath("//input").first.click
          end
        }.to raise_error Capybara::ModalNotFound, "Timed out waiting for modal dialog"
      end

      it 'raises an error when an unexpected modal is displayed' do
        expect {
          driver.accept_modal(:confirm) do
            visit("/")
          end
        }.to raise_error Capybara::ModalNotFound, "Unable to find modal dialog"
      end

      it "should let me read my alert messages" do
        visit("/")
        driver.alert_messages.first.should eq "Alert Text\nGoes Here"
      end

      it "empties the array when reset" do
        visit("/")
        driver.reset!
        driver.alert_messages.should be_empty
      end

      it "clears alerts from ajax requests in between sessions" do
        visit("/ajax")
        driver.find("//input").first.click
        driver.reset!
        sleep 0.5
        driver.alert_messages.should eq([])
        expect { visit("/") }.not_to raise_error
      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");
                }
                function test_complex_dialog() {
                  if(confirm("Yes?"))
                    if(confirm("Really?"))
                      console.log("hello");
                  else
                    console.log("goodbye");
                }
                function test_async_dialog() {
                  setTimeout(function() {
                    if(confirm("Yes?"))
                      console.log("hello");
                    else
                      console.log("goodbye");
                  }, 100);
                }
              </script>
              <input type="button" onclick="test_dialog()" name="test"/>
              <input type="button" onclick="test_complex_dialog()" name="test_complex"/>
              <input type="button" onclick="test_async_dialog()" name="test_async"/>
            </body>
          </html>
        HTML
      end

      before { visit("/") }

      it 'accepts any confirm modal if no match is provided' do
        driver.accept_modal(:confirm) do
          driver.find_xpath("//input").first.click
        end
        driver.console_messages.first[:message].should eq "hello"
      end

      it 'dismisses a confirm modal that does not match' do
        begin
          driver.accept_modal(:confirm, text: 'No?') do
            driver.find_xpath("//input").first.click
            driver.console_messages.first[:message].should eq "goodbye"
          end
        rescue Capybara::ModalNotFound
        end
      end

      it 'raises an error when accepting a confirm modal that does not match' do
        expect {
          driver.accept_modal(:confirm, text: 'No?') do
            driver.find_xpath("//input").first.click
          end
        }.to raise_error Capybara::ModalNotFound, "Unable to find modal dialog with No?"
      end

      it 'dismisses any confirm modal if no match is provided' do
        driver.dismiss_modal(:confirm) do
          driver.find_xpath("//input").first.click
        end
        driver.console_messages.first[:message].should eq "goodbye"
      end

      it 'raises an error when dismissing a confirm modal that does not match' do
        expect {
          driver.dismiss_modal(:confirm, text: 'No?') do
            driver.find_xpath("//input").first.click
          end
        }.to raise_error Capybara::ModalNotFound, "Unable to find modal dialog with No?"
      end

      it 'waits to accept an async confirm modal' do
        visit("/async")
        confirm_message = driver.accept_modal(:confirm) do
          driver.find_css("input[name=test_async]").first.click
        end
        confirm_message.should eq "Yes?"
      end

      it 'allows the nesting of dismiss and accept' do
        driver.dismiss_modal(:confirm) do
          driver.accept_modal(:confirm) do
            driver.find_css("input[name=test_complex]").first.click
          end
        end
        driver.console_messages.first[:message].should eq "goodbye"
      end

      it 'raises an error when an unexpected modal is displayed' do
        expect {
          driver.accept_modal(:prompt) do
            driver.find_xpath("//input").first.click
          end
        }.to raise_error Capybara::ModalNotFound, "Unable to find modal dialog"
      end

      it 'dismisses a confirm modal when prompt is expected' do
        begin
          driver.accept_modal(:prompt) do
            driver.find_xpath("//input").first.click
            driver.console_messages.first[:message].should eq "goodbye"
          end
        rescue Capybara::ModalNotFound
        end
      end

      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");
                }
                function test_complex_dialog() {
                  var response = prompt("Your name?", "John Smith");
                  if(response != null)
                    if(prompt("Your age?"))
                      console.log("hello " + response);
                  else
                    console.log("goodbye");
                }
                function test_async_dialog() {
                  setTimeout(function() {
                    var response = prompt("Your name?", "John Smith");
                  }, 100);
                }
              </script>
              <input type="button" onclick="test_dialog()" name="test"/>
              <input type="button" onclick="test_complex_dialog()" name="test_complex"/>
              <input type="button" onclick="test_async_dialog()" name="test_async"/>
            </body>
          </html>
        HTML
      end

      before { visit("/") }

      it 'accepts any prompt modal if no match is provided' do
        driver.accept_modal(:prompt) do
          driver.find_xpath("//input").first.click
        end
        driver.console_messages.first[:message].should eq "hello John Smith"
      end

      it 'accepts any prompt modal with the provided response' do
        driver.accept_modal(:prompt, with: 'Capy') do
          driver.find_xpath("//input").first.click
        end
        driver.console_messages.first[:message].should eq "hello Capy"
      end

      it 'raises an error when accepting a prompt modal that does not match' do
        expect {
          driver.accept_modal(:prompt, text: 'Your age?') do
            driver.find_xpath("//input").first.click
          end
        }.to raise_error Capybara::ModalNotFound, "Unable to find modal dialog with Your age?"
      end

      it 'dismisses any prompt modal if no match is provided' do
        driver.dismiss_modal(:prompt) do
          driver.find_xpath("//input").first.click
        end
        driver.console_messages.first[:message].should eq "goodbye"
      end

      it 'dismisses a prompt modal that does not match' do
        begin
          driver.accept_modal(:prompt, text: 'Your age?') do
            driver.find_xpath("//input").first.click
            driver.console_messages.first[:message].should eq "goodbye"
          end
        rescue Capybara::ModalNotFound
        end
      end

      it 'raises an error when dismissing a prompt modal that does not match' do
        expect {
          driver.dismiss_modal(:prompt, text: 'Your age?') do
            driver.find_xpath("//input").first.click
          end
        }.to raise_error Capybara::ModalNotFound, "Unable to find modal dialog with Your age?"
      end

      it 'waits to accept an async prompt modal' do
        visit("/async")
        prompt_message = driver.accept_modal(:prompt) do
          driver.find_css("input[name=test_async]").first.click
        end
        prompt_message.should eq "Your name?"
      end

      it 'allows the nesting of dismiss and accept' do
        driver.dismiss_modal(:prompt) do
          driver.accept_modal(:prompt) do
            driver.find_css("input[name=test_complex]").first.click
          end
        end
        driver.console_messages.first[:message].should eq "goodbye"
      end

      it 'raises an error when an unexpected modal is displayed' do
        expect {
          driver.accept_modal(:confirm) do
            driver.find_xpath("//input").first.click
          end
        }.to raise_error Capybara::ModalNotFound, "Unable to find modal dialog"
      end

      it 'dismisses a prompt modal when confirm is expected' do
        begin
          driver.accept_modal(:confirm) do
            driver.find_xpath("//input").first.click
            driver.console_messages.first[:message].should eq "goodbye"
          end
        rescue Capybara::ModalNotFound
        end
      end

      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, browser)
        <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
      connection.unstub(:gets)
      connection.unstub(:puts)
      connection.unstub(:print)
    end

    def make_the_server_go_away
      connection.stub(:gets).and_return(nil)
      connection.stub(:puts)
      connection.stub(:print)
    end

    let(:browser) { Capybara::Webkit::Browser.new(connection) }
    let(:connection) { Capybara::Webkit::Connection.new }
  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.set_cookie 'cookie=abc; domain=127.0.0.1; path=/'
      visit "/"
      echoed_cookie.should eq "abc"
    end

    it "clears cookies" do
      driver.clear_cookies
      visit "/"
      echoed_cookie.should eq ""
    end

    it "allows reading cookies" do
      driver.cookies["cookie"].should eq "abc"
      driver.cookies.find("cookie").path.should eq "/"
      driver.cookies.find("cookie").domain.should include "127.0.0.1"
    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 "caching app" do
    let(:driver) do
      etag_value = SecureRandom.hex

      driver_for_app do
        get '/' do
          etag etag_value
          <<-HTML
            <html>
              <body>
                Expected body
              </body>
            </html>
          HTML
        end
      end
    end

    it "returns a body for cached responses" do
      visit '/'
      first = driver.html
      visit '/'
      second = driver.html
      expect(second).to eq(first)
    end
  end

  context "offline application cache" do
    let(:driver) do
      @visited = []
      visited = @visited

      driver_for_app do
        get '/8d853f09-4275-409d-954d-ebbf6e2ce732' do
          content_type 'text/cache-manifest'
          visited << 'manifest'
          <<-TEXT
CACHE MANIFEST
/4aaffa31-f42d-403e-a19e-6b248d608087
          TEXT
        end

        # UUID urls so that this gets isolated from other tests
        get '/f8742c39-8bef-4196-b1c3-80f8a3d65f3e' do
          visited << 'complex'
          <<-HTML
            <html manifest="/8d853f09-4275-409d-954d-ebbf6e2ce732">
              <body>
                <span id='state'></span>
                <span id='finished'></span>
                <script type="text/javascript">
                  document.getElementById("state").innerHTML = applicationCache.status;
                  applicationCache.addEventListener('cached', function() {
                    document.getElementById("finished").innerHTML = 'cached';
                  });
                  applicationCache.addEventListener('error', function() {
                    document.getElementById("finished").innerHTML = 'error';
                  });
                </script>
              </body>
            </html>
          HTML
        end

        get '/4aaffa31-f42d-403e-a19e-6b248d608087' do
          visited << 'simple'
          <<-HTML
            <html manifest="/8d853f09-4275-409d-954d-ebbf6e2ce732">
              <body>
              </body>
            </html>
          HTML
        end
      end
    end

    before { visit("/f8742c39-8bef-4196-b1c3-80f8a3d65f3e") }

    it "has proper state available" do
      driver.find_xpath("//*[@id='state']").first.visible_text.should == '0'
      sleep 1
      @visited.should eq(['complex', 'manifest', 'simple']), 'files were not downloaded in expected order'
      driver.find_xpath("//*[@id='finished']").first.visible_text.should == 'cached'
    end

    it "is cleared on driver reset!" do
      sleep 1
      @visited.should eq(['complex', 'manifest', 'simple']), 'files were not downloaded in expected order'
      driver.reset!
      @visited.clear
      visit '/4aaffa31-f42d-403e-a19e-6b248d608087'
      sleep 1
      @visited.should eq(['simple', 'manifest', 'simple']), 'simple action was used from cache instead of server'
    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.authenticate('user', 'password')
      visit("/")
      driver.html.should include("Basic "+Base64.encode64("user:password").strip)
    end

    it "returns 401 for incorrectly authenticated request" do
      driver.authenticate('user1', 'password1')
      lambda { visit("/") }.should_not raise_error
      driver.status_code.should eq 401
    end

    it "returns 401 for unauthenticated request" do
      lambda { visit("/") }.should_not raise_error
      driver.status_code.should eq 401
    end

    it "can be reset with subsequent authenticate call", skip_on_qt4: true do
      driver.authenticate('user', 'password')
      visit("/")
      driver.html.should include("Basic "+Base64.encode64("user:password").strip)
      driver.authenticate('user1', 'password1')
      lambda { visit("/") }.should_not raise_error
      driver.status_code.should eq 401
    end
  end

  describe "url blacklisting", skip_if_offline: true 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>
              <iframe src="http://example.org/foo/bar" id="frame4"></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.block_url "http://example.org/path/to/file"
      driver.block_url "http://example.*/foo/*"
      driver.block_url "http://example.com"
      driver.block_url "#{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 "should not fetch urls blocked by wildcard match" do
      visit('/')
      driver.within_frame('frame4') do
        driver.find("//body").first.text.should be_empty
      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 "url whitelisting", skip_if_offline: true do
    it_behaves_like "output writer" do
      let(:driver) do
        driver_for_html(<<-HTML, browser)
          <<-HTML
            <html>
              <body>
                <iframe src="http://example.com/path" id="frame"></iframe>
                <iframe src="http://www.example.com" id="frame2"></iframe>
                <iframe src="data:text/plain,Hello"></iframe>
              </body>
            </html>
        HTML
      end

      it "prints a warning for remote requests by default" do
        visit("/")

        expect(stderr).to include("http://example.com/path")
        expect(stderr).not_to include(driver.current_url)
      end

      it "can allow specific hosts" do
        driver.allow_url("example.com")
        driver.allow_url("www.example.com")
        visit("/")

        expect(stderr).not_to include("http://example.com/path")
        expect(stderr).not_to include(driver.current_url)
        driver.within_frame("frame") do
          expect(driver.find("//body").first.text).not_to be_empty
        end
      end

      it "can allow all hosts" do
        driver.allow_unknown_urls
        visit("/")

        expect(stderr).not_to include("http://example.com/path")
        expect(stderr).not_to include(driver.current_url)
        driver.within_frame("frame") do
          expect(driver.find("//body").first.text).not_to be_empty
        end
      end

      it "resets allowed hosts on reset" do
        driver.allow_unknown_urls
        driver.reset!
        visit("/")

        expect(stderr).to include("http://example.com/path")
        expect(stderr).not_to include(driver.current_url)        
      end

      it "can block unknown hosts" do
        driver.block_unknown_urls
        visit("/")

        expect(stderr).not_to include("http://example.com/path")
        expect(stderr).not_to include(driver.current_url)
        driver.within_frame("frame") do
          expect(driver.find("//body").first.text).to be_empty
        end
      end

      it "can allow urls with wildcards" do
        driver.allow_url("*/path")
        visit("/")

        expect(stderr).to include("www.example.com")
        expect(stderr).not_to include("example.com/path")
        expect(stderr).not_to include(driver.current_url)
      end

      it "whitelists localhost on reset" do
        driver.reset!

        visit("/")

        expect(stderr).not_to include(driver.current_url)
      end

      it "does not print a warning for data URIs" do
        visit("/")

        expect(stderr).not_to include('Request to unknown URL: data:text/plain')
      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.timeout = 0
      lambda { visit("/") }.should_not raise_error
    end

    it "should raise a timeout error" do
      driver.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.timeout = 10
      lambda { visit("/") }.should_not raise_error
    end

    it "should set the timeout for each request" do
      driver.timeout = 10
      lambda { visit("/") }.should_not raise_error
      driver.timeout = 1
      lambda { visit("/") }.should raise_error(Timeout::Error)
    end

    it "should set the timeout for each request" do
      driver.timeout = 1
      lambda { visit("/") }.should raise_error(Timeout::Error)
      driver.reset!
      driver.timeout = 10
      lambda { visit("/") }.should_not raise_error
    end

    it "should raise a timeout on a slow form" do
      driver.timeout = 3
      visit("/")
      driver.status_code.should eq 200
      driver.timeout = 1
      driver.find_xpath("//input").first.click
      lambda { driver.status_code }.should raise_error(Timeout::Error)
    end

    it "get timeout" do
      driver.timeout = 10
      driver.timeout.should eq 10
      driver.timeout = 3
      driver.timeout.should eq 3
    end
  end

  describe "logger app" do
    it_behaves_like "output writer" do
      let(:driver) do
        driver_for_html("<html><body>Hello</body></html>", browser)
      end

      it "logs nothing before turning on the logger" do
        visit("/")
        stderr.should_not include logging_message
      end

      it "logs its commands after turning on the logger" do
        driver.enable_logging
        visit("/")
        stderr.should include logging_message
      end

      let(:logging_message) { 'Wrote response true' }
    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

  context "response header contains colon" do
    let(:driver) do
      driver_for_app do
        get "/" do
          headers "Content-Disposition" => 'filename="File: name.txt"'
        end
      end
    end

    it "sets the response header" do
      visit("/")

      expect(
        driver.response_headers["Content-Disposition"]
      ).to eq 'filename="File: name.txt"'
    end
  end

  context "with unfinished responses" do
    it_behaves_like "output writer" do
      let(:driver) do
        count = 0
        driver_for_app browser do
          get "/" do
            count += 1
            <<-HTML
              <html>
                <body>
                  <script type="text/javascript">
                    setTimeout(function () {
                      xhr = new XMLHttpRequest();
                      xhr.open('GET', '/async?#{count}', true);
                      xhr.setRequestHeader('Content-Type', 'text/plain');
                      xhr.send();
                    }, 50);
                  </script>
                </body>
              </html>
            HTML
          end

          get "/async" do
            sleep 2
            ""
          end
        end
      end

      it "aborts unfinished responses" do
        driver.enable_logging
        visit "/"
        sleep 0.5
        visit "/"
        sleep 0.5
        driver.reset!
        stderr.should abort_request_to("/async?2")
        stderr.should_not abort_request_to("/async?1")
      end

      def abort_request_to(path)
        include(%{Aborting request to "#{url(path)}"})
      end
    end
  end

  def driver_url(driver, path)
    URI.parse(driver.current_url).merge(path).to_s
  end
end
