mirror of
https://github.com/teampoltergeist/poltergeist.git
synced 2022-11-09 12:05:00 -05:00
Propagate Javascript errors on the page to Ruby. Fixes #27.
This commit is contained in:
parent
47a310dbdd
commit
0b9d286e3f
10 changed files with 181 additions and 71 deletions
|
@ -196,6 +196,9 @@ makes debugging easier). Running `rake autocompile` will watch the
|
|||
sessions, you might want to use this to reap the child phantomjs
|
||||
process. [Issue #24]
|
||||
|
||||
* Errors produced by Javascript on the page will now generate an
|
||||
exception within Ruby. [Issue #27]
|
||||
|
||||
#### Bug fixes ####
|
||||
|
||||
* Fix bug where we could end up interacting with an obsolete element. [Issue #30]
|
||||
|
|
|
@ -113,7 +113,11 @@ module Capybara::Poltergeist
|
|||
log json.inspect
|
||||
|
||||
if json['error']
|
||||
raise BrowserError.new(json['error'])
|
||||
if json['error']['name'] == 'Poltergeist.JavascriptError'
|
||||
raise JavascriptError.new(json['error'])
|
||||
else
|
||||
raise BrowserError.new(json['error'])
|
||||
end
|
||||
else
|
||||
json['response']
|
||||
end
|
||||
|
|
|
@ -13,37 +13,46 @@ class Poltergeist.Browser
|
|||
|
||||
@page.onLoadFinished = (status) =>
|
||||
if @state == 'loading'
|
||||
@owner.sendResponse(status)
|
||||
this.sendResponse(status)
|
||||
@state = 'default'
|
||||
|
||||
sendResponse: (response) ->
|
||||
errors = @page.errors()
|
||||
|
||||
if errors.length > 0
|
||||
@page.clearErrors()
|
||||
@owner.sendError(new Poltergeist.JavascriptError(errors))
|
||||
else
|
||||
@owner.sendResponse(response)
|
||||
|
||||
visit: (url) ->
|
||||
@state = 'loading'
|
||||
@page.open(url)
|
||||
|
||||
current_url: ->
|
||||
@owner.sendResponse @page.currentUrl()
|
||||
this.sendResponse @page.currentUrl()
|
||||
|
||||
body: ->
|
||||
@owner.sendResponse @page.content()
|
||||
this.sendResponse @page.content()
|
||||
|
||||
source: ->
|
||||
@owner.sendResponse @page.source()
|
||||
this.sendResponse @page.source()
|
||||
|
||||
find: (selector, id) ->
|
||||
@owner.sendResponse @page.find(selector, id)
|
||||
this.sendResponse @page.find(selector, id)
|
||||
|
||||
text: (id) ->
|
||||
@owner.sendResponse @page.get(id).text()
|
||||
this.sendResponse @page.get(id).text()
|
||||
|
||||
attribute: (id, name) ->
|
||||
@owner.sendResponse @page.get(id).getAttribute(name)
|
||||
this.sendResponse @page.get(id).getAttribute(name)
|
||||
|
||||
value: (id) ->
|
||||
@owner.sendResponse @page.get(id).value()
|
||||
this.sendResponse @page.get(id).value()
|
||||
|
||||
set: (id, value) ->
|
||||
@page.get(id).set(value)
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
|
||||
# PhantomJS only allows us to reference the element by CSS selector, not XPath,
|
||||
# so we have to add an attribute to the element to identify it, then remove it
|
||||
|
@ -65,31 +74,31 @@ class Poltergeist.Browser
|
|||
element.removeAttribute('_poltergeist_selected')
|
||||
element.setAttribute('multiple', 'multiple') if multiple
|
||||
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
|
||||
select: (id, value) ->
|
||||
@owner.sendResponse @page.get(id).select(value)
|
||||
this.sendResponse @page.get(id).select(value)
|
||||
|
||||
tag_name: (id) ->
|
||||
@owner.sendResponse @page.get(id).tagName()
|
||||
this.sendResponse @page.get(id).tagName()
|
||||
|
||||
visible: (id) ->
|
||||
@owner.sendResponse @page.get(id).isVisible()
|
||||
this.sendResponse @page.get(id).isVisible()
|
||||
|
||||
evaluate: (script) ->
|
||||
@owner.sendResponse JSON.parse(@page.evaluate("function() { return JSON.stringify(#{script}) }"))
|
||||
this.sendResponse JSON.parse(@page.evaluate("function() { return JSON.stringify(#{script}) }"))
|
||||
|
||||
execute: (script) ->
|
||||
@page.execute("function() { #{script} }")
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
|
||||
push_frame: (id) ->
|
||||
@page.pushFrame(id)
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
|
||||
pop_frame: ->
|
||||
@page.popFrame()
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
|
||||
click: (id) ->
|
||||
# If the click event triggers onLoadStarted, we will transition to the 'loading'
|
||||
|
@ -104,22 +113,22 @@ class Poltergeist.Browser
|
|||
=>
|
||||
if @state == 'clicked'
|
||||
@state = 'default'
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
,
|
||||
10
|
||||
)
|
||||
|
||||
drag: (id, other_id) ->
|
||||
@page.get(id).dragTo(@page.get(other_id))
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
|
||||
trigger: (id, event) ->
|
||||
@page.get(id).trigger(event)
|
||||
@owner.sendResponse(event)
|
||||
this.sendResponse(event)
|
||||
|
||||
reset: ->
|
||||
this.resetPage()
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
|
||||
render: (path, full) ->
|
||||
dimensions = @page.validatedDimensions()
|
||||
|
@ -135,11 +144,11 @@ class Poltergeist.Browser
|
|||
@page.setClipRect(left: 0, top: 0, width: viewport.width, height: viewport.height)
|
||||
@page.render(path)
|
||||
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
|
||||
resize: (width, height) ->
|
||||
@page.setViewportSize(width: width, height: height)
|
||||
@owner.sendResponse(true)
|
||||
this.sendResponse(true)
|
||||
|
||||
exit: ->
|
||||
phantom.exit()
|
||||
|
|
|
@ -17,39 +17,49 @@ Poltergeist.Browser = (function() {
|
|||
}, this);
|
||||
return this.page.onLoadFinished = __bind(function(status) {
|
||||
if (this.state === 'loading') {
|
||||
this.owner.sendResponse(status);
|
||||
this.sendResponse(status);
|
||||
return this.state = 'default';
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
Browser.prototype.sendResponse = function(response) {
|
||||
var errors;
|
||||
errors = this.page.errors();
|
||||
if (errors.length > 0) {
|
||||
this.page.clearErrors();
|
||||
return this.owner.sendError(new Poltergeist.JavascriptError(errors));
|
||||
} else {
|
||||
return this.owner.sendResponse(response);
|
||||
}
|
||||
};
|
||||
Browser.prototype.visit = function(url) {
|
||||
this.state = 'loading';
|
||||
return this.page.open(url);
|
||||
};
|
||||
Browser.prototype.current_url = function() {
|
||||
return this.owner.sendResponse(this.page.currentUrl());
|
||||
return this.sendResponse(this.page.currentUrl());
|
||||
};
|
||||
Browser.prototype.body = function() {
|
||||
return this.owner.sendResponse(this.page.content());
|
||||
return this.sendResponse(this.page.content());
|
||||
};
|
||||
Browser.prototype.source = function() {
|
||||
return this.owner.sendResponse(this.page.source());
|
||||
return this.sendResponse(this.page.source());
|
||||
};
|
||||
Browser.prototype.find = function(selector, id) {
|
||||
return this.owner.sendResponse(this.page.find(selector, id));
|
||||
return this.sendResponse(this.page.find(selector, id));
|
||||
};
|
||||
Browser.prototype.text = function(id) {
|
||||
return this.owner.sendResponse(this.page.get(id).text());
|
||||
return this.sendResponse(this.page.get(id).text());
|
||||
};
|
||||
Browser.prototype.attribute = function(id, name) {
|
||||
return this.owner.sendResponse(this.page.get(id).getAttribute(name));
|
||||
return this.sendResponse(this.page.get(id).getAttribute(name));
|
||||
};
|
||||
Browser.prototype.value = function(id) {
|
||||
return this.owner.sendResponse(this.page.get(id).value());
|
||||
return this.sendResponse(this.page.get(id).value());
|
||||
};
|
||||
Browser.prototype.set = function(id, value) {
|
||||
this.page.get(id).set(value);
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
};
|
||||
Browser.prototype.select_file = function(id, value) {
|
||||
var element, multiple;
|
||||
|
@ -64,31 +74,31 @@ Poltergeist.Browser = (function() {
|
|||
if (multiple) {
|
||||
element.setAttribute('multiple', 'multiple');
|
||||
}
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
};
|
||||
Browser.prototype.select = function(id, value) {
|
||||
return this.owner.sendResponse(this.page.get(id).select(value));
|
||||
return this.sendResponse(this.page.get(id).select(value));
|
||||
};
|
||||
Browser.prototype.tag_name = function(id) {
|
||||
return this.owner.sendResponse(this.page.get(id).tagName());
|
||||
return this.sendResponse(this.page.get(id).tagName());
|
||||
};
|
||||
Browser.prototype.visible = function(id) {
|
||||
return this.owner.sendResponse(this.page.get(id).isVisible());
|
||||
return this.sendResponse(this.page.get(id).isVisible());
|
||||
};
|
||||
Browser.prototype.evaluate = function(script) {
|
||||
return this.owner.sendResponse(JSON.parse(this.page.evaluate("function() { return JSON.stringify(" + script + ") }")));
|
||||
return this.sendResponse(JSON.parse(this.page.evaluate("function() { return JSON.stringify(" + script + ") }")));
|
||||
};
|
||||
Browser.prototype.execute = function(script) {
|
||||
this.page.execute("function() { " + script + " }");
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
};
|
||||
Browser.prototype.push_frame = function(id) {
|
||||
this.page.pushFrame(id);
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
};
|
||||
Browser.prototype.pop_frame = function() {
|
||||
this.page.popFrame();
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
};
|
||||
Browser.prototype.click = function(id) {
|
||||
this.state = 'clicked';
|
||||
|
@ -96,21 +106,21 @@ Poltergeist.Browser = (function() {
|
|||
return setTimeout(__bind(function() {
|
||||
if (this.state === 'clicked') {
|
||||
this.state = 'default';
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
}
|
||||
}, this), 10);
|
||||
};
|
||||
Browser.prototype.drag = function(id, other_id) {
|
||||
this.page.get(id).dragTo(this.page.get(other_id));
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
};
|
||||
Browser.prototype.trigger = function(id, event) {
|
||||
this.page.get(id).trigger(event);
|
||||
return this.owner.sendResponse(event);
|
||||
return this.sendResponse(event);
|
||||
};
|
||||
Browser.prototype.reset = function() {
|
||||
this.resetPage();
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
};
|
||||
Browser.prototype.render = function(path, full) {
|
||||
var dimensions, document, viewport;
|
||||
|
@ -142,14 +152,14 @@ Poltergeist.Browser = (function() {
|
|||
});
|
||||
this.page.render(path);
|
||||
}
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
};
|
||||
Browser.prototype.resize = function(width, height) {
|
||||
this.page.setViewportSize({
|
||||
width: width,
|
||||
height: height
|
||||
});
|
||||
return this.owner.sendResponse(true);
|
||||
return this.sendResponse(true);
|
||||
};
|
||||
Browser.prototype.exit = function() {
|
||||
return phantom.exit();
|
||||
|
|
|
@ -8,12 +8,7 @@ Poltergeist = (function() {
|
|||
try {
|
||||
return this.browser[command.name].apply(this.browser, command.args);
|
||||
} catch (error) {
|
||||
return this.connection.send({
|
||||
error: {
|
||||
name: error.name && error.name() || 'Generic',
|
||||
args: error.args && error.args() || [error.toString()]
|
||||
}
|
||||
});
|
||||
return this.sendError(error);
|
||||
}
|
||||
};
|
||||
Poltergeist.prototype.sendResponse = function(response) {
|
||||
|
@ -21,6 +16,14 @@ Poltergeist = (function() {
|
|||
response: response
|
||||
});
|
||||
};
|
||||
Poltergeist.prototype.sendError = function(error) {
|
||||
return this.connection.send({
|
||||
error: {
|
||||
name: error.name && error.name() || 'Generic',
|
||||
args: error.args && error.args() || [error.toString()]
|
||||
}
|
||||
});
|
||||
};
|
||||
return Poltergeist;
|
||||
})();
|
||||
window.Poltergeist = Poltergeist;
|
||||
|
@ -47,6 +50,18 @@ Poltergeist.ClickFailed = (function() {
|
|||
};
|
||||
return ClickFailed;
|
||||
})();
|
||||
Poltergeist.JavascriptError = (function() {
|
||||
function JavascriptError(errors) {
|
||||
this.errors = errors;
|
||||
}
|
||||
JavascriptError.prototype.name = function() {
|
||||
return "Poltergeist.JavascriptError";
|
||||
};
|
||||
JavascriptError.prototype.args = function() {
|
||||
return [this.errors];
|
||||
};
|
||||
return JavascriptError;
|
||||
})();
|
||||
phantom.injectJs('web_page.js');
|
||||
phantom.injectJs('node.js');
|
||||
phantom.injectJs('connection.js');
|
||||
|
|
|
@ -9,6 +9,7 @@ Poltergeist.WebPage = (function() {
|
|||
this["native"] = require('webpage').create();
|
||||
this.nodes = {};
|
||||
this._source = "";
|
||||
this._errors = [];
|
||||
this.setViewportSize({
|
||||
width: 1024,
|
||||
height: 768
|
||||
|
@ -66,8 +67,12 @@ Poltergeist.WebPage = (function() {
|
|||
WebPage.prototype.onLoadFinishedNative = function() {
|
||||
return this._source || (this._source = this["native"].content);
|
||||
};
|
||||
WebPage.prototype.onConsoleMessage = function(message) {
|
||||
return console.log(message);
|
||||
WebPage.prototype.onConsoleMessage = function(message, line, file) {
|
||||
if (line === 0 && file === "undefined") {
|
||||
return this._errors.push(message);
|
||||
} else {
|
||||
return console.log(message);
|
||||
}
|
||||
};
|
||||
WebPage.prototype.content = function() {
|
||||
return this["native"].content;
|
||||
|
@ -75,6 +80,12 @@ Poltergeist.WebPage = (function() {
|
|||
WebPage.prototype.source = function() {
|
||||
return this._source;
|
||||
};
|
||||
WebPage.prototype.errors = function() {
|
||||
return this._errors;
|
||||
};
|
||||
WebPage.prototype.clearErrors = function() {
|
||||
return this._errors = [];
|
||||
};
|
||||
WebPage.prototype.viewportSize = function() {
|
||||
return this["native"].viewportSize;
|
||||
};
|
||||
|
|
|
@ -7,15 +7,18 @@ class Poltergeist
|
|||
try
|
||||
@browser[command.name].apply(@browser, command.args)
|
||||
catch error
|
||||
@connection.send(
|
||||
error:
|
||||
name: error.name && error.name() || 'Generic',
|
||||
args: error.args && error.args() || [error.toString()]
|
||||
)
|
||||
this.sendError(error)
|
||||
|
||||
sendResponse: (response) ->
|
||||
@connection.send(response: response)
|
||||
|
||||
sendError: (error) ->
|
||||
@connection.send(
|
||||
error:
|
||||
name: error.name && error.name() || 'Generic',
|
||||
args: error.args && error.args() || [error.toString()]
|
||||
)
|
||||
|
||||
# This is necessary because the remote debugger will wrap the
|
||||
# script in a function, causing the Poltergeist variable to
|
||||
# become local.
|
||||
|
@ -26,13 +29,15 @@ class Poltergeist.ObsoleteNode
|
|||
args: -> []
|
||||
|
||||
class Poltergeist.ClickFailed
|
||||
constructor: (selector, position) ->
|
||||
@selector = selector
|
||||
@position = position
|
||||
|
||||
constructor: (@selector, @position) ->
|
||||
name: -> "Poltergeist.ClickFailed"
|
||||
args: -> [@selector, @position]
|
||||
|
||||
class Poltergeist.JavascriptError
|
||||
constructor: (@errors) ->
|
||||
name: -> "Poltergeist.JavascriptError"
|
||||
args: -> [@errors]
|
||||
|
||||
phantom.injectJs('web_page.js')
|
||||
phantom.injectJs('node.js')
|
||||
phantom.injectJs('connection.js')
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
class Poltergeist.WebPage
|
||||
@CALLBACKS = ['onAlert', 'onConsoleMessage', 'onLoadFinished', 'onInitialized',
|
||||
'onLoadStarted', 'onResourceRequested', 'onResourceReceived']
|
||||
|
||||
@DELEGATES = ['open', 'sendEvent', 'uploadFile', 'release', 'render']
|
||||
|
||||
@COMMANDS = ['currentUrl', 'find', 'nodeCall', 'pushFrame', 'popFrame', 'documentSize']
|
||||
|
||||
constructor: ->
|
||||
@native = require('webpage').create()
|
||||
@nodes = {}
|
||||
@_source = ""
|
||||
@_errors = []
|
||||
|
||||
this.setViewportSize(width: 1024, height: 768)
|
||||
|
||||
|
@ -43,8 +46,14 @@ class Poltergeist.WebPage
|
|||
onLoadFinishedNative: ->
|
||||
@_source or= @native.content
|
||||
|
||||
onConsoleMessage: (message) ->
|
||||
console.log(message)
|
||||
onConsoleMessage: (message, line, file) ->
|
||||
if line == 0 && file == "undefined"
|
||||
# file:line will always be "undefined:0" in current release of
|
||||
# PhantomJS ;(
|
||||
@_errors.push(message)
|
||||
else
|
||||
# here line == 1 && file == "". don't ask me why!
|
||||
console.log(message)
|
||||
|
||||
content: ->
|
||||
@native.content
|
||||
|
@ -52,6 +61,12 @@ class Poltergeist.WebPage
|
|||
source: ->
|
||||
@_source
|
||||
|
||||
errors: ->
|
||||
@_errors
|
||||
|
||||
clearErrors: ->
|
||||
@_errors = []
|
||||
|
||||
viewportSize: ->
|
||||
@native.viewportSize
|
||||
|
||||
|
|
|
@ -3,13 +3,15 @@ module Capybara
|
|||
class Error < StandardError
|
||||
end
|
||||
|
||||
class BrowserError < Error
|
||||
class ClientError < Error
|
||||
attr_reader :response
|
||||
|
||||
def initialize(response)
|
||||
@response = response
|
||||
end
|
||||
end
|
||||
|
||||
class BrowserError < ClientError
|
||||
def name
|
||||
response['name']
|
||||
end
|
||||
|
@ -23,12 +25,25 @@ module Capybara
|
|||
end
|
||||
end
|
||||
|
||||
class NodeError < Error
|
||||
attr_reader :node, :response
|
||||
class JavascriptError < ClientError
|
||||
def javascript_messages
|
||||
response['args'].first
|
||||
end
|
||||
|
||||
def message
|
||||
"One or more errors were raised in the Javascript code on the page: #{javascript_messages.inspect} " \
|
||||
"Unfortunately, it is not currently possible to provide a stack trace, or even the line/file where " \
|
||||
"the error occurred. (This is due to lack of support within QtWebKit.) Fixing this is a high " \
|
||||
"priority, but we're not there yet."
|
||||
end
|
||||
end
|
||||
|
||||
class NodeError < ClientError
|
||||
attr_reader :node
|
||||
|
||||
def initialize(node, response)
|
||||
@node = node
|
||||
@response = response
|
||||
@node = node
|
||||
super(response)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -115,5 +115,28 @@ module Capybara::Poltergeist
|
|||
raise "process is still alive"
|
||||
end
|
||||
end
|
||||
|
||||
context 'javascript errors' do
|
||||
it 'propagates an error on the page to a ruby exception' do
|
||||
expect { @driver.execute_script "omg" }.to raise_error(JavascriptError)
|
||||
|
||||
begin
|
||||
@driver.execute_script "omg"
|
||||
rescue JavascriptError => e
|
||||
e.message.should include("omg")
|
||||
e.message.should include("ReferenceError")
|
||||
end
|
||||
end
|
||||
|
||||
it "doesn't re-raise a Javascript error if it's rescued" do
|
||||
begin
|
||||
@driver.execute_script("omg")
|
||||
rescue JavascriptError
|
||||
end
|
||||
|
||||
# should not raise again
|
||||
@driver.evaluate_script("1+1").should == 2
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue