Support Capybara 2.11

* Update versions being tested against
* Implement missing APIs from newer versions
This commit is contained in:
Thomas Walpole 2017-01-03 16:33:42 -08:00 committed by Joe Ferris
parent b22df5fe67
commit 43ebe80704
19 changed files with 270 additions and 90 deletions

View File

@ -25,18 +25,18 @@ addons:
matrix:
include:
- rvm: 1.9.3
gemfile: gemfiles/2.6.gemfile
gemfile: gemfiles/2.7.gemfile
env: QMAKE=/usr/lib/x86_64-linux-gnu/qt4/bin/qmake
- rvm: 1.9.3
gemfile: gemfiles/2.5.gemfile
gemfile: gemfiles/2.11.gemfile
env: QMAKE=/usr/lib/x86_64-linux-gnu/qt4/bin/qmake
- rvm: 2.3.1
gemfile: gemfiles/master.gemfile
allow_failures:
- gemfile: gemfiles/master.gemfile
gemfile:
- gemfiles/2.6.gemfile
- gemfiles/2.5.gemfile
- gemfiles/2.7.gemfile
- gemfiles/2.11.gemfile
before_install:
- gem install bundler
install: bundle

View File

@ -1,9 +1,13 @@
appraise "2.6" do
gem "capybara", "~> 2.6.0"
end
appraise "2.7" do
gem "capybara", "~> 2.7.0"
gem 'addressable', '< 2.5.0', :platforms=>[:ruby_19, :jruby_19] # 2.5 requires public_suffix which requires ruby 2.0
gem 'nokogiri', '< 1.7.0', :platforms=>[:ruby_19, :jruby_19] # 1.7.0 requires ruby 2.1+
end
appraise "2.11" do
gem "capybara", "~> 2.11.0"
gem 'addressable', '< 2.5.0', :platforms=>[:ruby_19, :jruby_19] # 2.5 requires public_suffix which requires ruby 2.0
gem 'nokogiri', '< 1.7.0', :platforms=>[:ruby_19, :jruby_19] # 1.7.0 requires ruby 2.1+
end
appraise "master" do

View File

@ -2,70 +2,63 @@ PATH
remote: .
specs:
capybara-webkit (1.11.1)
capybara (>= 2.3.0, < 2.8.0)
capybara (>= 2.3.0, < 2.13.0)
json
GEM
remote: https://rubygems.org/
specs:
addressable (2.3.6)
appraisal (0.4.0)
addressable (2.5.0)
public_suffix (~> 2.0, >= 2.0.2)
appraisal (0.4.1)
bundler
rake
capybara (2.5.0)
capybara (2.11.0)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
childprocess (0.5.5)
ffi (~> 1.0, >= 1.0.11)
diff-lcs (1.2.4)
ffi (1.9.8)
ffi (1.9.8-java)
ffi (1.9.8-x86-mingw32)
diff-lcs (1.2.5)
ffi (1.9.14-java)
json (1.8.3)
launchy (2.4.2)
json (1.8.3-java)
launchy (2.4.3)
addressable (~> 2.3)
launchy (2.4.2-java)
launchy (2.4.3-java)
addressable (~> 2.3)
spoon (~> 0.0.1)
mime-types (2.6.1)
mini_magick (3.2.1)
subexec (~> 0.0.4)
mini_portile2 (2.0.0)
multi_json (1.11.0)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
rack (1.6.4)
rack-protection (1.3.2)
mime-types (2.99.3)
mini_magick (4.6.0)
mini_portile2 (2.1.0)
nokogiri (1.7.0.1)
mini_portile2 (~> 2.1.0)
nokogiri (1.7.0.1-java)
nokogiri (1.7.0.1-x86-mingw32)
mini_portile2 (~> 2.1.0)
public_suffix (2.0.5)
rack (1.6.5)
rack-protection (1.5.3)
rack
rack-test (0.6.3)
rack (>= 1.0)
rake (0.9.2)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.4)
rspec-expectations (2.14.1)
rake (11.3.0)
rspec (2.99.0)
rspec-core (~> 2.99.0)
rspec-expectations (~> 2.99.0)
rspec-mocks (~> 2.99.0)
rspec-core (2.99.2)
rspec-expectations (2.99.2)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.3)
rubyzip (1.1.7)
selenium-webdriver (2.45.0)
childprocess (~> 0.5)
multi_json (~> 1.0)
rubyzip (~> 1.0)
websocket (~> 1.0)
sinatra (1.3.5)
rack (~> 1.4)
rack-protection (~> 1.3)
tilt (~> 1.3, >= 1.3.3)
spoon (0.0.4)
rspec-mocks (2.99.4)
sinatra (1.4.7)
rack (~> 1.5)
rack-protection (~> 1.4)
tilt (>= 1.3, < 3)
spoon (0.0.6)
ffi
subexec (0.0.4)
tilt (1.3.3)
websocket (1.2.1)
tilt (2.0.5)
xpath (2.0.0)
nokogiri (~> 1.3)
@ -77,13 +70,13 @@ PLATFORMS
DEPENDENCIES
appraisal (~> 0.4.0)
capybara-webkit!
json (< 2.0)
launchy
mime-types (< 3.0)
mini_magick
rake
rspec (~> 2.14.0)
selenium-webdriver
rake (< 12.0.0)
rspec (~> 2.14)
sinatra
BUNDLED WITH
1.11.2
1.13.6

View File

@ -21,16 +21,15 @@ Gem::Specification.new do |s|
s.requirements << "Qt >= 4.8"
s.add_runtime_dependency("capybara", ">= 2.3.0", "< 2.8.0")
s.add_runtime_dependency("capybara", ">= 2.3.0", "< 2.13.0")
s.add_runtime_dependency("json")
s.add_development_dependency("rspec", "~> 2.14.0")
s.add_development_dependency("rspec", "~> 2.14")
# Sinatra is used by Capybara's TestApp
s.add_development_dependency("sinatra")
s.add_development_dependency("mini_magick")
s.add_development_dependency("rake")
s.add_development_dependency("rake", "< 12.0.0")
s.add_development_dependency("appraisal", "~> 0.4.0")
s.add_development_dependency("selenium-webdriver")
s.add_development_dependency("launchy")
end

View File

@ -4,7 +4,8 @@ source "https://rubygems.org"
gem "mime-types", "< 3.0", :platforms=>[:ruby_19, :jruby_19]
gem "json", "< 2.0", :platforms=>[:ruby_19, :jruby_19]
gem "capybara", "~> 2.6.0"
gem "capybara", "~> 2.11.0"
gem "addressable", "< 2.5.0", :platforms=>[:ruby_19, :jruby_19]
gem "nokogiri", "< 1.7.0", :platforms=>[:ruby_19, :jruby_19]
gemspec :path=>"../"

View File

@ -4,7 +4,8 @@ source "https://rubygems.org"
gem "mime-types", "< 3.0", :platforms=>[:ruby_19, :jruby_19]
gem "json", "< 2.0", :platforms=>[:ruby_19, :jruby_19]
gem "capybara", "~> 2.7.0"
gem "addressable", "< 2.5.0", :platforms=>[:ruby_19, :jruby_19]
gem "nokogiri", "< 1.7.0", :platforms=>[:ruby_19, :jruby_19]
gemspec :path=>"../"

View File

@ -4,7 +4,6 @@ source "https://rubygems.org"
gem "mime-types", "< 3.0", :platforms=>[:ruby_19, :jruby_19]
gem "json", "< 2.0", :platforms=>[:ruby_19, :jruby_19]
gem "capybara", :github=>"jnicklas/capybara"
gemspec :path=>"../"

View File

@ -223,13 +223,13 @@ https://github.com/thoughtbot/capybara-webkit/wiki/Reporting-Crashes
MESSAGE
end
def evaluate_script(script)
json = command('Evaluate', script)
def evaluate_script(script, *args)
json = command('Evaluate', script, args.to_json)
JSON.parse("[#{json}]").first
end
def execute_script(script)
command('Execute', script)
def execute_script(script, *args)
command('Execute', script, args.to_json)
end
def render(path, width, height)

View File

@ -76,13 +76,18 @@ module Capybara::Webkit
@browser.title
end
def execute_script(script)
value = @browser.execute_script script
value.empty? ? nil : value
def execute_script(script, *args)
value = @browser.execute_script(script, *encode_args(args))
if value.empty?
nil
else
value
end
end
def evaluate_script(script)
@browser.evaluate_script script
def evaluate_script(script, *args)
@browser.evaluate_script(script, *encode_args(args))
end
def console_messages
@ -144,6 +149,21 @@ module Capybara::Webkit
end
end
def switch_to_frame(frame)
case frame
when :top
begin
loop { @browser.frame_focus }
rescue Capybara::Webkit::InvalidResponseError => e
raise unless e.message =~ /Already at parent frame/
end
when :parent
@browser.frame_focus
else
@browser.frame_focus(frame)
end
end
def within_window(selector)
current_window = current_window_handle
switch_to_window(selector)
@ -391,5 +411,15 @@ module Capybara::Webkit
"This option is global and can be configured once" \
" (not in a `before` or `setup` block)."
end
def encode_args(args)
args.map do |arg|
if arg.is_a?(Capybara::Webkit::Node)
{ ELEMENT: arg.native }.to_json
else
arg.to_json
end
end
end
end
end

View File

@ -114,11 +114,13 @@ module Capybara::Webkit
end
def disabled?
if %w(option optgroup).include? tag_name
self['disabled'] || find_xpath("parent::*")[0].disabled?
else
self['disabled']
end
xpath = "parent::optgroup[@disabled] | " \
"ancestor::select[@disabled] | " \
"parent::fieldset[@disabled] | " \
"ancestor::*[not(self::legend) or " \
"preceding-sibling::legend][parent::fieldset[@disabled]]"
self["disabled"] || !find_xpath(xpath).empty?
end
def path

View File

@ -62,12 +62,25 @@ describe Capybara::Webkit::Driver do
</head>
<body>
<script type="text/javascript">
document.write("<p id='farewell'>goodbye</p>");
document.write("<p id='farewell'>goodbye</p><iframe id='g' src='/iframe2'></iframe>");
</script>
</body>
</html>
HTML
end
get '/iframe2' do
<<-HTML
<html>
<head>
<title>Frame 2</title>
</head>
<body>
<div>In frame 2</div>
</body>
</html>
HTML
end
end
end
@ -95,6 +108,37 @@ describe Capybara::Webkit::Driver do
end
end
it "switches to frame by element" do
frame = driver.find_xpath('//iframe').first
element = double(Capybara::Node::Base, base: frame)
driver.switch_to_frame(element)
driver.find_xpath("//*[contains(., 'goodbye')]").should_not be_empty
driver.switch_to_frame(:parent)
end
it "can switch back to the parent frame" do
frame = driver.find_xpath('//iframe').first
element = double(Capybara::Node::Base, base: frame)
driver.switch_to_frame(element)
driver.switch_to_frame(:parent)
driver.find_xpath("//*[contains(., 'greeting')]").should_not be_empty
driver.find_xpath("//*[contains(., 'goodbye')]").should be_empty
end
it "can switch to the top frame" do
frame = driver.find_xpath('//iframe').first
element = double(Capybara::Node::Base, base: frame)
driver.switch_to_frame(element)
frame2 = driver.find_xpath('//iframe[@id="g"]').first
element2 = double(Capybara::Node::Base, base: frame2)
driver.switch_to_frame(element2)
driver.find_xpath("//div[contains(., 'In frame 2')]").should_not be_empty
driver.switch_to_frame(:top)
driver.find_xpath("//*[contains(., 'greeting')]").should_not be_empty
driver.find_xpath("//*[contains(., 'goodbye')]").should be_empty
driver.find_xpath("//div[contains(., 'In frame 2')]").should be_empty
end
it "raises error for missing frame by index" do
expect { driver.within_frame(1) { } }.
to raise_error(Capybara::Webkit::InvalidResponseError)
@ -528,6 +572,38 @@ describe Capybara::Webkit::Driver do
to raise_error(Capybara::Webkit::InvalidResponseError)
end
it "passes arguments to executed Javascript" do
driver.execute_script(%<document.getElementById('greeting').innerHTML = arguments[0]>, "My argument")
driver.find_xpath("//p[contains(., 'My argument')]").should_not be_empty
end
it "passes multiple arguments to executed Javascript" do
driver.execute_script(
%<document.getElementById('greeting').innerHTML = arguments[0] + arguments[1] + arguments[2].color>,
"random", 4, {color: 'red'})
driver.find_xpath("//p[contains(., 'random4red')]").should_not be_empty
end
it "passes page elements to executed Javascript" do
greeting = driver.find_xpath("//p[@id='greeting']").first
driver.execute_script(%<arguments[0].innerHTML = arguments[1]>, greeting, "new content")
driver.find_xpath("//p[@id='greeting'][contains(., 'new content')]").should_not be_empty
end
it "passes arguments to evaaluated Javascript" do
driver.evaluate_script(%<arguments[0]>, 3).should eq 3
end
it "passes multiple arguments to evaluated Javascript" do
driver.evaluate_script(%<arguments[0] + arguments[1] + arguments[2].num>, 3, 4, {num: 5}).should eq 12
end
it "passes page elements to evaluated Javascript" do
greeting = driver.find_xpath("//p[@id='greeting']").first
driver.evaluate_script(%<arguments[1].innerHTML = arguments[0]; arguments[2]>, "newer content", greeting, 7).should eq 7
driver.find_xpath("//p[@id='greeting'][contains(., 'newer content')]").should_not be_empty
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
@ -726,7 +802,7 @@ describe Capybara::Webkit::Driver do
</head>
<body>
<script type="text/javascript">
alert('First alert');
alert('First alert');
</script>
<input type="button" onclick="alert('Second alert')" name="test"/>
</body>
@ -764,12 +840,12 @@ describe Capybara::Webkit::Driver do
end
it 'finds two alert windows in a row' do
driver.accept_modal(:alert, text: 'First alert') do
driver.accept_modal(:alert, text: 'First alert') do
visit('/double')
end
expect {
driver.accept_modal(:alert, text: 'Boom') do
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"
@ -2795,7 +2871,7 @@ CACHE MANIFEST
visit("/")
expect(stderr).to include("http://example.com/path")
expect(stderr).not_to include(driver.current_url)
expect(stderr).not_to include(driver.current_url)
end
it "can block unknown hosts" do

View File

@ -8,7 +8,26 @@ Evaluate::Evaluate(WebPageManager *manager, QStringList &arguments, QObject *par
}
void Evaluate::start() {
QVariant result = page()->currentFrame()->evaluateJavaScript(arguments()[0]);
QString script = arguments()[0];
QString jsonArgs;
if (arguments().length()>1){
jsonArgs = arguments()[1];
} else {
jsonArgs ="[]";
}
QString eval_script = QString("(function(){"
" for(var i=0; i<arguments.length; i++) {"
" arguments[i] = JSON.parse(arguments[i]);"
" if (arguments[i]['ELEMENT']) {"
" arguments[i] = Capybara.getNode(arguments[i]['ELEMENT']);"
" };"
" };"
" return eval(\"%1\");"
" }).apply(null, %2)").arg(script.replace("\"","\\\"").remove("\n"), jsonArgs);
QObject invocation_stub;
invocation_stub.setProperty("allowUnattached", false);
page()->currentFrame()->addToJavaScriptWindowObject("CapybaraInvocation", &invocation_stub);
QVariant result = page()->currentFrame()->evaluateJavaScript(eval_script);
JsonSerializer serializer;
finish(true, serializer.serialize(result));
}

View File

@ -7,7 +7,24 @@ Execute::Execute(WebPageManager *manager, QStringList &arguments, QObject *paren
}
void Execute::start() {
QString script = arguments()[0] + QString("; 'success'");
QString jsonArgs;
if (arguments().length()>1){
jsonArgs = arguments()[1];
} else {
jsonArgs ="[]";
}
QString script = QString("(function(){"
" for(var i=0; i<arguments.length; i++) {"
" arguments[i] = JSON.parse(arguments[i]);"
" if (arguments[i]['ELEMENT']) {"
" arguments[i] = Capybara.getNode(arguments[i]['ELEMENT']);"
" };"
" };"
" %1 }).apply(null, %2); 'success'").arg(arguments()[0], jsonArgs);
QObject invocation_stub;
invocation_stub.setProperty("allowUnattached", false);
page()->currentFrame()->addToJavaScriptWindowObject("CapybaraInvocation", &invocation_stub);
QVariant result = page()->currentFrame()->evaluateJavaScript(script);
if (result.isValid()) {
finish(true);

View File

@ -8,7 +8,6 @@ FrameFocus::FrameFocus(WebPageManager *manager, QStringList &arguments, QObject
}
void FrameFocus::start() {
findFrames();
switch(arguments().length()) {
case 1:
focusId(arguments()[0]);
@ -26,8 +25,10 @@ void FrameFocus::findFrames() {
}
void FrameFocus::focusIndex(int index) {
findFrames();
if (isFrameAtIndex(index)) {
frames[index]->setFocus();
page()->setCurrentFrameParent(frames[index]->parentFrame());
success();
} else {
frameNotFound();
@ -39,9 +40,11 @@ bool FrameFocus::isFrameAtIndex(int index) {
}
void FrameFocus::focusId(QString name) {
findFrames();
for (int i = 0; i < frames.length(); i++) {
if (frames[i]->frameName().compare(name) == 0) {
frames[i]->setFocus();
page()->setCurrentFrameParent(frames[i]->parentFrame());
success();
return;
}
@ -51,10 +54,13 @@ void FrameFocus::focusId(QString name) {
}
void FrameFocus::focusParent() {
if (page()->currentFrame()->parentFrame() == 0) {
// if (page()->currentFrame()->parentFrame() == 0) {
if (page()->currentFrameParent() == 0) {
finish(false, new ErrorMessage("Already at parent frame."));
} else {
page()->currentFrame()->parentFrame()->setFocus();
// page()->currentFrame()->parentFrame()->setFocus();
page()->currentFrameParent()->setFocus();
page()->setCurrentFrameParent(page()->currentFrameParent()->parentFrame());
success();
}
}

View File

@ -37,8 +37,14 @@ InvocationResult JavascriptInvocation::invoke(QWebFrame *frame) {
QVariant result = frame->evaluateJavaScript("Capybara.invoke()");
if (getError().isValid())
return InvocationResult(getError(), true);
else
else {
if (functionName() == "leftClick") {
// Don't trigger the left click from JS incase the frame closes
QVariantMap qm = result.toMap();
leftClick(qm["absoluteX"].toInt(), qm["absoluteY"].toInt());
}
return InvocationResult(result);
}
}
void JavascriptInvocation::leftClick(int x, int y) {

View File

@ -11,6 +11,9 @@ void Node::start() {
QString functionName = functionArguments.takeFirst();
QString allowUnattached = functionArguments.takeFirst();
InvocationResult result = page()->invokeCapybaraFunction(functionName, allowUnattached == "true", functionArguments);
if (functionName == "focus") {
page()->setCurrentFrameParent(page()->currentFrame()->parentFrame());
}
finish(&result);
}

View File

@ -21,6 +21,7 @@ WebPage::WebPage(WebPageManager *manager, QObject *parent) : QWebPage(parent) {
m_uuid = QUuid::createUuid().toString();
m_confirmAction = true;
m_promptAction = false;
m_currentFrameParent = 0;
setForwardUnsupportedContent(true);
loadJavascript();
@ -255,6 +256,7 @@ bool WebPage::javaScriptPrompt(QWebFrame *frame, const QString &message, const Q
void WebPage::loadStarted() {
m_loading = true;
m_currentFrameParent = currentFrame()->parentFrame();
m_errorPageMessage = QString();
}
@ -485,3 +487,13 @@ void WebPage::addModalMessage(bool expectedType, const QString &message, const Q
m_modalMessages << QString();
emit modalReady();
}
QWebFrame* WebPage::currentFrameParent() {
return m_currentFrameParent;
}
void WebPage::setCurrentFrameParent(QWebFrame* frame) {
m_currentFrameParent = frame;
}

View File

@ -55,6 +55,8 @@ class WebPage : public QWebPage {
void resize(int, int);
int modalCount();
QString modalMessage();
void setCurrentFrameParent(QWebFrame* frame);
QWebFrame* currentFrameParent();
public slots:
bool shouldInterruptJavaScript();
@ -105,6 +107,7 @@ class WebPage : public QWebPage {
QList<QVariantMap> m_modalResponses;
QStringList m_modalMessages;
void addModalMessage(bool, const QString &, const QRegExp &);
QWebFrame* m_currentFrameParent;
};
#endif //_WEBPAGE_H

View File

@ -5,7 +5,11 @@ Capybara = {
invoke: function () {
try {
return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments);
if (CapybaraInvocation.functionName == "leftClick") {
return this["verifiedClickPosition"].apply(this, CapybaraInvocation.arguments);
} else {
return this[CapybaraInvocation.functionName].apply(this, CapybaraInvocation.arguments);
}
} catch (e) {
CapybaraInvocation.error = e;
}
@ -204,12 +208,17 @@ Capybara = {
throw new Capybara.UnpositionedElement(this.pathForNode(node), visible);
},
click: function (index, action) {
verifiedClickPosition: function (index) {
var node = this.getNode(index);
node.scrollIntoViewIfNeeded();
var pos = this.clickPosition(node);
CapybaraInvocation.hover(pos.relativeX, pos.relativeY);
this.expectNodeAtPosition(node, pos);
return pos;
},
click: function (index, action) {
var pos = this.verifiedClickPosition(index);
action(pos.absoluteX, pos.absoluteY);
},