Permalink
Browse files

Merge remote-tracking branch 'upstream/master'

Conflicts:
	Gemfile.lock
	lib/capybara_webkit_builder.rb
  • Loading branch information...
2 parents e14f6ee + fc5cba3 commit f8cd4997f3080caa680f8b879b71c660baad9c8c @morenocarullo morenocarullo committed Mar 19, 2012
View
9 CONTRIBUTING.md
@@ -1,5 +1,14 @@
We love pull requests. Here's a quick guide:
+Dependencies
+
+Some of the tests depend on the `identify` command that comes with Imagemagick.
+Imagemagick can be installed via [homebrew](http://mxcl.github.com/homebrew/).
+
+ brew install imagemagick
+
+Contributing
+
1. Fork the repo.
2. Run the tests. We only take pull requests with passing tests, and it's great
View
70 ChangeLog
@@ -0,0 +1,70 @@
+2012-03-09 Joe Ferris <jferris@thoughtbot.com>
+ * node.rb, driver_spec.rb: Allow interaction with invisible elements
+
+2012-03-02 Joe Ferris <jferris@thoughtbot.com>
+ * browser.rb: Use Timeout from stdlib since Capybara.timeout is being removed
+
+2012-03-02 Matthew Mongeau <halogenandtoast@gmail.com>
+ * capybara_webkit_builder.rb:
+ set LANG to en_US.UTF-8 to prevent string encoding issues during install
+
+2012-03-02 Marc Schwieterman
+ * Rakefile: pro, find_command, and CommandFactory are more structured
+
+2012-02-27 Joe Ferris <jferris@thoughtbot.com>
+ * README.md: Fixed broken wiki link
+
+2012-02-24 Joe Ferris <jferris@thoughtbot.com>
+ * README.md: Update instructions for platform specific installation issues
+
+2012-02-17 Matthew Mongeau <halogenandtoast@gmail.com>
+ * driver_spec.rb, capybara.js: Send proper keycode during keypress event
+
+2012-02-17 Matthew Mongeau <halogenandtoast@gmail.com>
+ * ChangeLog, version.rb:
+ Bump to 0.10.0
+
+2012-02-17 Marc Schwieterman
+ * driver_spec.rb, Reset.cpp, Reset.h: Reset history when resetting session.
+ * driver_spec.rb, webkit.rb, browser.rb, CommandFactory.cpp, CurrentUrl.cpp,
+ CurrentUrl.h, find_command.h, webkit_server.pro:
+ current_url now conforms more closely to the behavior of Selenium.
+
+2012-02-17 Igor Zubkov <igor.zubkov@gmail.com>
+ * spec_helper.rb: Fix typo.
+
+2012-02-17 Matthew Mongeau <halogenandtoast@gmail.com>
+ * capybara_webkit_builder.rb, capybara_webkit_builder_spec.rb,
+ spec_helper.rb:
+ Allow for overriding MAKE, QMAKE, and SPEC options via the environment.
+
+2012-02-12 Jason Petersen <jasonmp85@gmail.com>
+ * driver_spec.rb, node.rb, capybara.js:
+ Selected attribute is no longer removed when selecting/deselecting. Only the
+ property is changed.
+
+2012-02-09 Gabe Berke-Williams <gabe@thoughtbot.com>
+ * capybara-webkit.gemspec: Note capybara-webkit's usage of Sinatra.
+
+2012-02-03 Matthew Mongeau <halogenandtoast@gmail.com>
+ * version.rb:
+ Bump to 0.9.0
+
+2012-02-01 Joe Ferris <jferris@thoughtbot.com>
+ * driver_spec.rb, Connection.cpp, Connection.h:
+ Try to detect when a command starts a page load and wait for it to finish.
+
+2012-01-27 Matthew Mongeau <halogenandtoast@gmail.com>
+ * driver_spec.rb, capybara.js: Trigger mousedown and mouseup events.
+ * driver_spec.rb, capybara.js: Simulate browser events more closely.
+
+2012-01-19 Marco Antonio <marcofognog@gmail.com>
+ * node.rb, driver_spec.rb:
+ Raise ElementNotDisplayedError also for #drag_to and #select_option when they are invisible.
+
+2012-01-18 Marco Antonio <marcofognog@gmail.com>
+ * node.rb, driver_spec.rb:
+ Raise error when an invisible element receives #click so it resembles a browser more closely.
+
+2012-01-15 Marc Schwieterman
+ * CONTRIBUTING.md: add imagemagick dependency to contributing guide.
View
25 Gemfile.lock
@@ -1,31 +1,32 @@
PATH
remote: .
specs:
- capybara-webkit (0.7.2)
+ capybara-webkit (0.11.0)
capybara (>= 1.0.0, < 1.2)
+ json
GEM
remote: http://rubygems.org/
specs:
appraisal (0.4.0)
bundler
rake
- capybara (1.1.1)
+ capybara (1.1.2)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
- childprocess (0.2.2)
+ childprocess (0.3.1)
ffi (~> 1.0.6)
diff-lcs (1.1.2)
- ffi (1.0.9)
- ffi (1.0.9-x86-mingw32)
- json_pure (1.6.1)
- mime-types (1.16)
+ ffi (1.0.11)
+ json (1.6.5)
+ mime-types (1.17.2)
mini_magick (3.2.1)
subexec (~> 0.0.4)
+ multi_json (1.0.4)
nokogiri (1.5.0)
nokogiri (1.5.0-x86-mingw32)
rack (1.3.2)
@@ -40,11 +41,11 @@ GEM
rspec-expectations (2.6.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.6.0)
- rubyzip (0.9.4)
- selenium-webdriver (2.7.0)
- childprocess (>= 0.2.1)
- ffi (>= 1.0.7)
- json_pure
+ rubyzip (0.9.6.1)
+ selenium-webdriver (2.19.0)
+ childprocess (>= 0.2.5)
+ ffi (~> 1.0.9)
+ multi_json (~> 1.0.4)
rubyzip
sinatra (1.1.2)
rack (~> 1.1)
View
25 NEWS.md
@@ -0,0 +1,25 @@
+New for 0.11.0:
+
+* Allow interaction with invisible elements
+* Use Timeout from stdlib since Capybara.timeout is being removed
+
+New for 0.10.1:
+
+* LANG environment variable is set to en_US.UTF-8 in order to avoid string encoding issues from qmake.
+* pro, find_command, and CommandFactory are more structured.
+* Changed wiki link and directing platform specific issues to the google group.
+* Pass proper keycode value for keypress events.
+
+New for 0.10.0:
+
+* current_url now more closely matches the behavior of Selenium
+* custom MAKE, QMAKE, and SPEC options can be set from the environment
+* BUG: Selected attribute is no longer removed when selecting/deselecting. Only the property is changed.
+
+New for 0.9.0:
+
+* Raise an error when an invisible element receives #click.
+* Raise ElementNotDisplayedError for #drag_to and #select_option when element is invisible.
+* Trigger mousedown and mouseup events.
+* Model mouse events more closely to the browser.
+* Try to detech when a command starts a page load and wait for it to finish
View
20 README.md
@@ -3,13 +3,18 @@ capybara-webkit
A [capybara](https://github.com/jnicklas/capybara) driver that uses [WebKit](http://webkit.org) via [QtWebKit](http://doc.qt.nokia.com/4.7/qtwebkit.html).
-Qt Dependency
+Qt Dependency and Installation Issues
-------------
capybara-webkit depends on a WebKit implementation from Qt, a cross-platform
development toolkit. You'll need to download the Qt libraries to build and
install the gem. You can find instructions for downloading and installing QT on
-the [capybara-webkit wiki](https://github.com/thoughtbot/capybara-webkit/wiki/Installing-QT)
+the [capybara-webkit wiki](https://github.com/thoughtbot/capybara-webkit/wiki/Installing-Qt-and-compiling-capybara-webkit)
+
+Windows Support
+---------------
+
+Currently 32bit Windows will compile Capybara-webkit. Support for Windows is provided by the open source community and Windows related issues should be posted to the [mailing list](http://groups.google.com/group/capybara-webkit)
Reporting Issues
----------------
@@ -20,7 +25,11 @@ log of what happened during each test. Before filing a crash bug, please see
[Reporting Crashes](https://github.com/thoughtbot/capybara-webkit/wiki/Reporting-Crashes).
You're much more likely to get a fix if you follow those instructions.
-If you are having compiling issues please post to the [mailing list](http://groups.google.com/group/capybara-webkit).
+If you are having compiling issues please check out the
+[capybara-webkit wiki](https://github.com/thoughtbot/capybara-webkit/wiki/Installing-Qt-and-compiling-capybara-webkit).
+If you don't have any luck there, please post to the
+[mailing list](http://groups.google.com/group/capybara-webkit). Please don't
+open a Github issue for a system-specific compiler issue.
CI
--
@@ -62,11 +71,6 @@ Code for rendering the current webpage to a PNG is borrowed from Phantom.js' imp
The names and logos for thoughtbot are trademarks of thoughtbot, inc.
-Notes
------
-
-capybara-webkit will listen on port 8200. This may conflict with other services.
-
License
-------
View
11 Rakefile
@@ -49,12 +49,17 @@ task :generate_command do
Dir.glob("src/*.pro").each do |project_file_name|
project = IO.read(project_file_name)
- project.gsub!(/^(HEADERS = .*)/, "\\1 #{name}.h")
- project.gsub!(/^(SOURCES = .*)/, "\\1 #{name}.cpp")
+ project.gsub!(/^(HEADERS = .*)/, "\\1\n #{name}.h \\")
+ project.gsub!(/^(SOURCES = .*)/, "\\1\n #{name}.cpp \\")
File.open(project_file_name, "w") { |file| file.write(project) }
end
File.open("src/find_command.h", "a") do |file|
- file.write("CHECK_COMMAND(#{name})")
+ file.write("CHECK_COMMAND(#{name})\n")
end
+
+ command_factory_file_name = "src/CommandFactory.cpp"
+ command_factory = IO.read(command_factory_file_name)
+ command_factory.sub!(/^$/, "#include \"#{name}.h\"\n")
+ File.open(command_factory_file_name, "w") { |file| file.write(command_factory) }
end
View
2 capybara-webkit.gemspec
@@ -16,8 +16,10 @@ Gem::Specification.new do |s|
s.extensions = "extconf.rb"
s.add_runtime_dependency("capybara", [">= 1.0.0", "< 1.2"])
+ s.add_runtime_dependency("json")
s.add_development_dependency("rspec", "~> 2.6.0")
+ # Sinatra is used by Capybara's TestApp
s.add_development_dependency("sinatra")
s.add_development_dependency("mini_magick")
s.add_development_dependency("rake")
View
12 gemfiles/1.0.gemfile.lock
@@ -1,8 +1,9 @@
PATH
- remote: /Users/gabe/thoughtbot/capybara-webkit
+ remote: /Users/jferris/Source/capybara-webkit
specs:
- capybara-webkit (0.7.2)
+ capybara-webkit (0.8.0)
capybara (>= 1.0.0, < 1.2)
+ json
GEM
remote: http://rubygems.org/
@@ -21,6 +22,7 @@ GEM
ffi (~> 1.0.6)
diff-lcs (1.1.3)
ffi (1.0.10)
+ json (1.6.3)
json_pure (1.6.1)
mime-types (1.17.2)
mini_magick (3.3)
@@ -47,9 +49,9 @@ GEM
json_pure
rubyzip
sinatra (1.3.1)
- rack (>= 1.3.4, ~> 1.3)
- rack-protection (>= 1.1.2, ~> 1.1)
- tilt (>= 1.3.3, ~> 1.3)
+ rack (~> 1.3, >= 1.3.4)
+ rack-protection (~> 1.1, >= 1.1.2)
+ tilt (~> 1.3, >= 1.3.3)
subexec (0.1.0)
tilt (1.3.3)
xpath (0.1.4)
View
12 gemfiles/1.1.gemfile.lock
@@ -1,8 +1,9 @@
PATH
- remote: /Users/gabe/thoughtbot/capybara-webkit
+ remote: /Users/jferris/Source/capybara-webkit
specs:
- capybara-webkit (0.7.2)
+ capybara-webkit (0.8.0)
capybara (>= 1.0.0, < 1.2)
+ json
GEM
remote: http://rubygems.org/
@@ -21,6 +22,7 @@ GEM
ffi (~> 1.0.6)
diff-lcs (1.1.3)
ffi (1.0.10)
+ json (1.6.3)
json_pure (1.6.1)
mime-types (1.17.2)
mini_magick (3.3)
@@ -47,9 +49,9 @@ GEM
json_pure
rubyzip
sinatra (1.3.1)
- rack (>= 1.3.4, ~> 1.3)
- rack-protection (>= 1.1.2, ~> 1.1)
- tilt (>= 1.3.3, ~> 1.3)
+ rack (~> 1.3, >= 1.3.4)
+ rack-protection (~> 1.1, >= 1.1.2)
+ tilt (~> 1.3, >= 1.3.3)
subexec (0.1.0)
tilt (1.3.3)
xpath (0.1.4)
View
2 lib/capybara/driver/webkit.rb
@@ -27,7 +27,7 @@ def initialize(app, options={})
end
def current_url
- browser.url
+ browser.current_url
end
def requested_url
View
21 lib/capybara/driver/webkit/browser.rb
@@ -1,6 +1,6 @@
require 'socket'
require 'thread'
-require 'capybara/util/timeout'
+require 'timeout'
require 'json'
require 'rbconfig'
@@ -71,6 +71,10 @@ def requested_url
command("RequestedUrl")
end
+ def current_url
+ command("CurrentUrl")
+ end
+
def frame_focus(frame_id_or_index=nil)
if frame_id_or_index.is_a? Fixnum
command("FrameFocus", "", frame_id_or_index.to_s)
@@ -144,6 +148,14 @@ def fork_server
pipe
end
+ def kill_process(pid)
+ if RUBY_PLATFORM =~ /mingw32/
+ Process.kill(9, pid)
+ else
+ Process.kill("INT", pid)
+ end
+ end
+
def register_shutdown_hook
@owner_pid = Process.pid
at_exit do
@@ -199,9 +211,10 @@ def pipe_readable?(pipe)
end
def connect
- Capybara.timeout(5) do
- attempt_connect
- !@socket.nil?
+ Timeout.timeout(5) do
+ while @socket.nil?
+ attempt_connect
+ end
end
end
View
6 lib/capybara/driver/webkit/node.rb
@@ -18,11 +18,7 @@ def [](name)
def value
if multiple_select?
- self.find(".//option").select do |option|
- option["selected"] == "selected"
- end.map do |option|
- option.value
- end
+ self.find(".//option").select(&:selected?).map(&:value)
else
invoke "value"
end
View
2 lib/capybara/driver/webkit/version.rb
@@ -1,7 +1,7 @@
module Capybara
module Driver
class Webkit
- VERSION = '0.7.2'.freeze
+ VERSION = '0.11.0'.freeze
end
end
end
View
60 lib/capybara_webkit_builder.rb
@@ -4,61 +4,67 @@
module CapybaraWebkitBuilder
extend self
- def has_binary?(binary)
- case RbConfig::CONFIG['host_os']
- when /mingw32/
- system("#{binary} --version")
- else
- system("which #{make}")
- end
- end
-
def make_bin
- make_binaries = ['gmake', 'make']
- make_binaries.detect { |make| has_binary?(make) }
+ ENV['MAKE'] || 'make'
end
def qmake_bin
- qmake_binaries = ['qmake', 'qmake-qt4']
- qmake_binaries.detect { |qmake| has_binary?(qmake) }
+ ENV['QMAKE'] || 'qmake'
end
-
- def makefile
+
+ def spec
+ ENV['SPEC'] || os_spec
+ end
+
+ def os_spec
case RbConfig::CONFIG['host_os']
when /linux/
- system("#{qmake_bin} -spec linux-g++")
+ "linux-g++"
when /freebsd/
- system("#{qmake_bin} -spec freebsd-g++")
+ "freebsd-g++"
when /mingw32/
- system("#{qmake_bin} -spec win32-g++")
+ "win32-g++"
else
- system("#{qmake_bin} -spec macx-g++")
+ "macx-g++"
end
end
+ def makefile
+ system("#{make_env_variables} #{qmake_bin} -spec #{spec}")
+ end
+
def qmake
- system("#{make_bin} qmake")
+ system("#{make_env_variables} #{make_bin} qmake")
+ end
+
+ def make_env_variables
+ case RbConfig::CONFIG['host_os']
+ when /mingw32/
+ ''
+ else
+ "LANG='en_US.UTF-8'"
+ end
end
def path_to_binary
case RbConfig::CONFIG['host_os']
when /mingw32/
- 'src/debug/webkit_server.exe'
+ "src/debug/webkit_server.exe"
else
- 'src/webkit_server'
+ "src/webkit_server"
end
end
-
+
def build
system(make_bin) or return false
- FileUtils.mkdir("bin") unless File.directory?("bin")
- FileUtils.cp(path_to_binary, "bin", :preserve => true)
+ FileUtils.mkdir("bin") unless File.directory?("bin")
+ FileUtils.cp(path_to_binary, "bin", :preserve => true)
end
def build_all
makefile &&
- qmake &&
- build
+ qmake &&
+ build
end
end
View
37 spec/capybara_webkit_builder_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+require 'capybara_webkit_builder'
+
+describe CapybaraWebkitBuilder do
+ let(:builder) { CapybaraWebkitBuilder }
+
+ it "will use the env variable for #make_bin" do
+ with_env_vars("MAKE" => "fake_make") do
+ builder.make_bin.should == "fake_make"
+ end
+ end
+
+ it "will use the env variable for #qmake_bin" do
+ with_env_vars("QMAKE" => "fake_qmake") do
+ builder.qmake_bin.should == "fake_qmake"
+ end
+ end
+
+ it "will use the env variable for #os_spec" do
+ with_env_vars("SPEC" => "fake_os_spec") do
+ builder.spec.should == "fake_os_spec"
+ end
+ end
+
+ it "defaults the #make_bin" do
+ builder.make_bin.should == 'make'
+ end
+
+ it "defaults the #qmake_bin" do
+ builder.qmake_bin.should == 'qmake'
+ end
+
+ it "defaults #spec to the #os_specs" do
+ builder.spec.should == builder.os_spec
+ end
+end
+
View
169 spec/driver_spec.rb
@@ -132,6 +132,20 @@
subject.find("//input").first.click
subject.find("//p").first.text.should == ""
end
+
+ it "returns the current URL when changed by pushState after a redirect" do
+ subject.visit("/redirect-me")
+ port = subject.instance_variable_get("@rack_server").port
+ subject.execute_script("window.history.pushState({}, '', '/pushed-after-redirect')")
+ subject.current_url.should == "http://127.0.0.1:#{port}/pushed-after-redirect"
+ end
+
+ it "returns the current URL when changed by replaceState after a redirect" do
+ subject.visit("/redirect-me")
+ port = subject.instance_variable_get("@rack_server").port
+ subject.execute_script("window.history.replaceState({}, '', '/replaced-after-redirect')")
+ subject.current_url.should == "http://127.0.0.1:#{port}/replaced-after-redirect"
+ end
end
context "css app" do
@@ -197,6 +211,11 @@
subject.find("//*[contains(., 'hello')]").should be_empty
end
+ it "has a location of 'about:blank' after reseting" do
+ subject.reset!
+ subject.current_url.should == "about:blank"
+ end
+
it "raises an error for an invalid xpath query" do
expect { subject.find("totally invalid salad") }.
to raise_error(Capybara::Driver::Webkit::WebkitInvalidResponseError, /xpath/i)
@@ -223,6 +242,18 @@
subject.current_url.should == "http://127.0.0.1:#{port}/hello/world?success=true"
end
+ it "returns the current URL when changed by pushState" do
+ port = subject.instance_variable_get("@rack_server").port
+ subject.execute_script("window.history.pushState({}, '', '/pushed')")
+ subject.current_url.should == "http://127.0.0.1:#{port}/pushed"
+ end
+
+ it "returns the current URL when changed by replaceState" do
+ port = subject.instance_variable_get("@rack_server").port
+ subject.execute_script("window.history.replaceState({}, '', '/replaced')")
+ subject.current_url.should == "http://127.0.0.1:#{port}/replaced"
+ end
+
it "does not double-encode URLs" do
subject.visit("/hello/world?success=%25true")
subject.current_url.should =~ /success=\%25true/
@@ -380,6 +411,7 @@
</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
@@ -450,18 +482,45 @@
let(:banana_option) { subject.find("//option[@id='topping-banana']").first }
let(:cherry_option) { subject.find("//option[@id='topping-cherry']").first }
let(:toppings_select) { subject.find("//select[@name='toppings']").first }
+ let(:reset_button) { subject.find("//button[@type='reset']").first }
+
+ context "a select element's selection has been changed" do
+ before do
+ animal_select.value.should == "Capybara"
+ monkey_option.select_option
+ end
+
+ it "returns the new selection" do
+ animal_select.value.should == "Monkey"
+ end
+
+ it "does not modify the selected attribute of a new selection" do
+ monkey_option['selected'].should be_empty
+ end
+
+ it "returns the old value when a reset button is clicked" do
+ reset_button.click
- it "selects an option" do
- animal_select.value.should == "Capybara"
- monkey_option.select_option
- animal_select.value.should == "Monkey"
+ animal_select.value.should == "Capybara"
+ end
end
- it "unselects an option in a multi-select" do
- toppings_select.value.should include("Apple", "Banana", "Cherry")
+ 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
- toppings_select.value.should_not include("Apple")
+ 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
it "reselects an option in a multi-select" do
@@ -529,6 +588,44 @@
end
end
+ context "dom events" do
+ before(:all) do
+ @app = lambda do |env|
+ body = <<-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);
+ }
+ </script>
+ </body></html>
+ HTML
+ [200,
+ { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
+ [body]]
+ end
+ end
+
+ it "triggers mouse events" do
+ subject.find("//a").first.click
+ subject.find("//li").map(&:text).should == %w(mousedown mouseup click)
+ end
+ end
+
context "form events app" do
before(:all) do
@app = lambda do |env|
@@ -562,8 +659,11 @@
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>
@@ -579,7 +679,7 @@
let(:keyevents) do
(%w{focus} +
- newtext.length.times.collect { %w{keydown keypress keyup} } +
+ newtext.length.times.collect { %w{keydown keypress keyup input} } +
%w{change blur}).flatten
end
@@ -597,12 +697,12 @@
it "triggers radio input events" do
subject.find("//input[@type='radio']").first.set(true)
- subject.find("//li").map(&:text).should == %w(click change)
+ subject.find("//li").map(&:text).should == %w(mousedown mouseup change click)
end
it "triggers checkbox events" do
subject.find("//input[@type='checkbox']").first.set(true)
- subject.find("//li").map(&:text).should == %w(click change)
+ subject.find("//li").map(&:text).should == %w(mousedown mouseup change click)
end
end
@@ -706,23 +806,22 @@
context "slow app" do
before(:all) do
+ @result = ""
@app = lambda do |env|
- body = <<-HTML
- <html><body>
- <form action="/next"><input type="submit"/></form>
- <p>#{env['PATH_INFO']}</p>
- </body></html>
- HTML
- sleep(0.5)
+ if env["PATH_INFO"] == "/result"
+ sleep(0.5)
+ @result << "finished"
+ end
+ body = %{<html><body><a href="/result">Go</a></body></html>}
[200,
{ 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s },
[body]]
end
end
it "waits for a request to load" do
- subject.find("//input").first.click
- subject.find("//p").first.text.should == "/next"
+ subject.find("//a").first.click
+ @result.should == "finished"
end
end
@@ -1273,4 +1372,34 @@ def set_automatic_reload(value)
subject.body.should include "Congrats"
end
end
+
+ context "keypress app" do
+ before(:all) do
+ @app = lambda do |env|
+ body = <<-HTML
+ <html>
+ <head><title>Form</title></head>
+ <body>
+ <div id="charcode_value"></div>
+ <input type="text" id="charcode" name="charcode" onkeypress="setcharcode" />
+ <script type="text/javascript">
+ var element = document.getElementById("charcode")
+ element.addEventListener("keypress", setcharcode);
+ function setcharcode(event) {
+ var element = document.getElementById("charcode_value");
+ element.innerHTML = event.charCode;
+ }
+ </script>
+ </body>
+ </html>
+ HTML
+ [200, { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s }, [body]]
+ end
+ end
+
+ it "returns the charCode for the keypressed" do
+ subject.find("//input")[0].set("a")
+ subject.find("//div")[0].text.should == "97"
+ end
+ end
end
View
14 spec/spec_helper.rb
@@ -28,3 +28,17 @@
Capybara.register_driver :reusable_webkit do |app|
Capybara::Driver::Webkit.new(app, :browser => $webkit_browser)
end
+
+def with_env_vars(vars)
+ old_env_variables = {}
+ vars.each do |key, value|
+ old_env_variables[key] = ENV[key]
+ ENV[key] = value
+ end
+
+ yield
+
+ old_env_variables.each do |key, value|
+ ENV[key] = value
+ end
+end
View
1 src/CommandFactory.cpp
@@ -20,6 +20,7 @@
#include "SetProxy.h"
#include "ConsoleMessages.h"
#include "RequestedUrl.h"
+#include "CurrentUrl.h"
CommandFactory::CommandFactory(WebPage *page, QObject *parent) : QObject(parent) {
m_page = page;
View
20 src/Connection.cpp
@@ -17,6 +17,8 @@ Connection::Connection(QTcpSocket *socket, WebPage *page, QObject *parent) :
m_command = NULL;
m_pageSuccess = true;
m_commandWaiting = false;
+ m_pageLoadingFromCommand = false;
+ m_pendingResponse = NULL;
connect(m_socket, SIGNAL(readyRead()), m_commandParser, SLOT(checkNext()));
connect(m_commandParser, SIGNAL(commandReady(QString, QStringList)), this, SLOT(commandReady(QString, QStringList)));
connect(m_page, SIGNAL(pageFinished(bool)), this, SLOT(pendingLoadFinished(bool)));
@@ -38,6 +40,7 @@ void Connection::startCommand() {
if (m_pageSuccess) {
m_command = m_commandFactory->createCommand(m_commandName.toAscii().constData());
if (m_command) {
+ connect(m_page, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand()));
connect(m_command,
SIGNAL(finished(Response *)),
this,
@@ -55,16 +58,31 @@ void Connection::startCommand() {
}
}
+void Connection::pageLoadingFromCommand() {
+ m_pageLoadingFromCommand = true;
+}
+
void Connection::pendingLoadFinished(bool success) {
m_pageSuccess = success;
if (m_commandWaiting)
startCommand();
+ if (m_pageLoadingFromCommand) {
+ m_pageLoadingFromCommand = false;
+ if (m_pendingResponse) {
+ writeResponse(m_pendingResponse);
+ m_pendingResponse = NULL;
+ }
+ }
}
void Connection::finishCommand(Response *response) {
+ disconnect(m_page, SIGNAL(loadStarted()), this, SLOT(pageLoadingFromCommand()));
m_command->deleteLater();
m_command = NULL;
- writeResponse(response);
+ if (m_pageLoadingFromCommand)
+ m_pendingResponse = response;
+ else
+ writeResponse(response);
}
void Connection::writeResponse(Response *response) {
View
3 src/Connection.h
@@ -18,6 +18,7 @@ class Connection : public QObject {
void commandReady(QString commandName, QStringList arguments);
void finishCommand(Response *response);
void pendingLoadFinished(bool success);
+ void pageLoadingFromCommand();
private:
void startCommand();
@@ -32,5 +33,7 @@ class Connection : public QObject {
CommandFactory *m_commandFactory;
bool m_pageSuccess;
bool m_commandWaiting;
+ bool m_pageLoadingFromCommand;
+ Response *m_pendingResponse;
};
View
71 src/CurrentUrl.cpp
@@ -0,0 +1,71 @@
+#include "CurrentUrl.h"
+#include "WebPage.h"
+
+CurrentUrl::CurrentUrl(WebPage *page, QObject *parent) : Command(page, parent) {
+}
+
+/*
+ * This CurrentUrl command attempts to produce a current_url value consistent
+ * with that returned by the Selenium WebDriver Capybara driver.
+ *
+ * It does not currently return the correct value in the case of an iframe whose
+ * source URL results in a redirect because the loading of the iframe does not
+ * generate a history item. This is most likely a rare case and is consistent
+ * with the current behavior of the capybara-webkit driver.
+ *
+ * The following two values are *not* affected by Javascript pushState.
+ *
+ * QWebFrame->url()
+ * QWebHistoryItem.originalUrl()
+ *
+ * The following two values *are* affected by Javascript pushState.
+ *
+ * QWebFrame->requestedUrl()
+ * QWebHistoryItem.url()
+ *
+ * In the cases that we have access to both the QWebFrame values and the
+ * correct history item for that frame, we can compare the values and determine
+ * if a redirect occurred and if pushState was used. The table below describes
+ * the various combinations of URL values that are possible.
+ *
+ * O -> originally requested URL
+ * R -> URL after redirection
+ * P -> URL set by pushState
+ * * -> denotes the desired URL value from the frame
+ *
+ * frame history
+ * case url requestedUrl url originalUrl
+ * -----------------------------------------------------------------
+ * regular load O O* O O
+ *
+ * redirect w/o R* O R O
+ * pushState
+ *
+ * pushState O P* P O
+ * only
+ *
+ * redirect w/ R P* P O
+ * pushState
+ *
+ * Based on the above information, we only need to check for the case of a
+ * redirect w/o pushState, in which case QWebFrame->url() will have the correct
+ * current_url value. In all other cases QWebFrame->requestedUrl() is correct.
+ */
+void CurrentUrl::start(QStringList &arguments) {
+ Q_UNUSED(arguments);
+
+ QUrl humanUrl = wasRedirectedAndNotModifiedByJavascript() ?
+ page()->currentFrame()->url() : page()->currentFrame()->requestedUrl();
+ QByteArray encodedBytes = humanUrl.toEncoded();
+ QString urlString = QString(encodedBytes);
+ emit finished(new Response(true, urlString));
+}
+
+bool CurrentUrl::wasRegularLoad() {
+ return page()->currentFrame()->url() == page()->currentFrame()->requestedUrl();
+}
+
+bool CurrentUrl::wasRedirectedAndNotModifiedByJavascript() {
+ return !wasRegularLoad() && page()->currentFrame()->url() == page()->history()->currentItem().url();
+}
+
View
16 src/CurrentUrl.h
@@ -0,0 +1,16 @@
+#include "Command.h"
+
+class WebPage;
+
+class CurrentUrl : public Command {
+ Q_OBJECT
+
+ public:
+ CurrentUrl(WebPage *page, QObject *parent = 0);
+ virtual void start(QStringList &arguments);
+
+ private:
+ bool wasRegularLoad();
+ bool wasRedirectedAndNotModifiedByJavascript();
+};
+
View
7 src/Reset.cpp
@@ -16,6 +16,13 @@ void Reset::start(QStringList &arguments) {
page()->setUserAgent(NULL);
page()->resetResponseHeaders();
page()->resetConsoleMessages();
+ resetHistory();
emit finished(new Response(true));
}
+void Reset::resetHistory() {
+ // Clearing the history preserves the current history item, so set it to blank first.
+ page()->currentFrame()->setUrl(QUrl("about:blank"));
+ page()->history()->clear();
+}
+
View
3 src/Reset.h
@@ -8,5 +8,8 @@ class Reset : public Command {
public:
Reset(WebPage *page, QObject *parent = 0);
virtual void start(QStringList &arguments);
+
+ private:
+ void resetHistory();
};
View
2 src/Server.cpp
@@ -12,7 +12,7 @@ Server::Server(QObject *parent, bool ignoreSslErrors) : QObject(parent) {
bool Server::start() {
connect(m_tcp_server, SIGNAL(newConnection()), this, SLOT(handleConnection()));
- return m_tcp_server->listen(QHostAddress::Any, 0);
+ return m_tcp_server->listen(QHostAddress::LocalHost, 0);
}
quint16 Server::server_port() const {
View
27 src/capybara.js
@@ -96,7 +96,21 @@ Capybara = {
return this.nodes[index].submit();
},
+ mousedown: function(index) {
+ var mousedownEvent = document.createEvent('MouseEvents');
+ mousedownEvent.initMouseEvent('mousedown', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ this.nodes[index].dispatchEvent(mousedownEvent);
+ },
+
+ mouseup: function(index) {
+ var mouseupEvent = document.createEvent('MouseEvents');
+ mouseupEvent.initMouseEvent('mouseup', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ this.nodes[index].dispatchEvent(mouseupEvent);
+ },
+
click: function (index) {
+ this.mousedown(index);
+ this.mouseup(index);
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
this.nodes[index].dispatchEvent(clickEvent);
@@ -160,20 +174,21 @@ Capybara = {
for (strindex = 0; strindex < length; strindex++) {
node.value += value[strindex];
this.trigger(index, "keydown");
- this.keypress(index, false, false, false, false, 0, value[strindex]);
+ this.keypress(index, false, false, false, false, 0, value.charCodeAt(strindex));
this.trigger(index, "keyup");
+ this.trigger(index, "input");
}
this.trigger(index, "change");
this.trigger(index, "blur");
} else if (type === "checkbox" || type === "radio") {
- node.checked = (value === "true");
- this.trigger(index, "click");
- this.trigger(index, "change");
+ if (node.checked != (value === "true")) {
+ this.click(index)
+ }
} else if (type === "file") {
this.lastAttachedFile = value;
- this.trigger(index, "click");
+ this.click(index)
} else {
node.value = value;
@@ -182,13 +197,11 @@ Capybara = {
selectOption: function(index) {
this.nodes[index].selected = true;
- this.nodes[index].setAttribute("selected", "selected");
this.trigger(index, "change");
},
unselectOption: function(index) {
this.nodes[index].selected = false;
- this.nodes[index].removeAttribute("selected");
this.trigger(index, "change");
},
View
1 src/find_command.h
@@ -24,3 +24,4 @@ CHECK_COMMAND(Headers)
CHECK_COMMAND(SetProxy)
CHECK_COMMAND(ConsoleMessages)
CHECK_COMMAND(RequestedUrl)
+CHECK_COMMAND(CurrentUrl)
View
2 src/webkit_server.pro
@@ -2,6 +2,7 @@ TEMPLATE = app
TARGET = webkit_server
DESTDIR = .
HEADERS = \
+ CurrentUrl.h \
RequestedUrl.h \
ConsoleMessages.h \
WebPage.h \
@@ -35,6 +36,7 @@ HEADERS = \
SetProxy.h \
SOURCES = \
+ CurrentUrl.cpp \
RequestedUrl.cpp \
ConsoleMessages.cpp \
main.cpp \

0 comments on commit f8cd499

Please sign in to comment.