Skip to content
This repository has been archived by the owner on Nov 27, 2020. It is now read-only.

Commit

Permalink
Implement evaluate_async_script
Browse files Browse the repository at this point in the history
  • Loading branch information
twalpole committed Nov 2, 2017
1 parent 14d6b35 commit dfa8a0d
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 3 deletions.
5 changes: 5 additions & 0 deletions lib/capybara/poltergeist/browser.rb
Expand Up @@ -11,6 +11,7 @@ class Browser
'Poltergeist.InvalidSelector' => InvalidSelector,
'Poltergeist.StatusFailError' => StatusFailError,
'Poltergeist.NoSuchWindowError' => NoSuchWindowError,
'Poltergeist.ScriptTimeoutError' => ScriptTimeoutError,
'Poltergeist.UnsupportedFeature' => UnsupportedFeature,
'Poltergeist.KeyError' => KeyError,
}
Expand Down Expand Up @@ -125,6 +126,10 @@ def evaluate(script, *args)
command 'evaluate', script, *args
end

def evaluate_async(script, wait_time, *args)
command 'evaluate_async', script, wait_time, *args
end

def execute(script, *args)
command 'execute', script, *args
end
Expand Down
12 changes: 12 additions & 0 deletions lib/capybara/poltergeist/client/browser.coffee
Expand Up @@ -208,6 +208,18 @@ class Poltergeist.Browser
throw new Poltergeist.ObsoleteNode if arg["ELEMENT"]["page_id"] != @currentPage.id
@current_command.sendResponse @currentPage.evaluate("function() { return #{script} }", args...)

evaluate_async: (script, max_wait, args...) ->
for arg in args when @_isElementArgument(arg)
throw new Poltergeist.ObsoleteNode if arg["ELEMENT"]["page_id"] != @currentPage.id
command = @current_command
cb = (result)=>
command.sendResponse(result)
@currentPage.evaluate_async("function() { #{script} }", cb, args...)
setTimeout(=>
command.sendError(new Poltergeist.ScriptTimeoutError)
, max_wait*1000)


execute: (script, args...) ->
for arg in args when @_isElementArgument(arg)
throw new Poltergeist.ObsoleteNode if arg["ELEMENT"]["page_id"] != @currentPage.id
Expand Down
25 changes: 25 additions & 0 deletions lib/capybara/poltergeist/client/compiled/browser.js
Expand Up @@ -266,6 +266,31 @@ Poltergeist.Browser = (function() {
return this.current_command.sendResponse((ref = this.currentPage).evaluate.apply(ref, ["function() { return " + script + " }"].concat(slice.call(args))));
};

Browser.prototype.evaluate_async = function() {
var arg, args, cb, command, i, len, max_wait, ref, script;
script = arguments[0], max_wait = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
for (i = 0, len = args.length; i < len; i++) {
arg = args[i];
if (this._isElementArgument(arg)) {
if (arg["ELEMENT"]["page_id"] !== this.currentPage.id) {
throw new Poltergeist.ObsoleteNode;
}
}
}
command = this.current_command;
cb = (function(_this) {
return function(result) {
return command.sendResponse(result);
};
})(this);
(ref = this.currentPage).evaluate_async.apply(ref, ["function() { " + script + " }", cb].concat(slice.call(args)));
return setTimeout((function(_this) {
return function() {
return command.sendError(new Poltergeist.ScriptTimeoutError);
};
})(this), max_wait * 1000);
};

Browser.prototype.execute = function() {
var arg, args, i, len, ref, script;
script = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
Expand Down
17 changes: 17 additions & 0 deletions lib/capybara/poltergeist/client/compiled/main.js
Expand Up @@ -214,6 +214,23 @@ Poltergeist.NoSuchWindowError = (function(superClass) {

})(Poltergeist.Error);

Poltergeist.ScriptTimeoutError = (function(superClass) {
extend(ScriptTimeoutError, superClass);

function ScriptTimeoutError() {
return ScriptTimeoutError.__super__.constructor.apply(this, arguments);
}

ScriptTimeoutError.prototype.name = "Poltergeist.ScriptTimeoutError";

ScriptTimeoutError.prototype.args = function() {
return [];
};

return ScriptTimeoutError;

})(Poltergeist.Error);

Poltergeist.UnsupportedFeature = (function(superClass) {
extend(UnsupportedFeature, superClass);

Expand Down
40 changes: 38 additions & 2 deletions lib/capybara/poltergeist/client/compiled/web_page.js
@@ -1,11 +1,12 @@
var slice = [].slice,
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
slice = [].slice,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; },
hasProp = {}.hasOwnProperty;

Poltergeist.WebPage = (function() {
var command, delegate, fn1, fn2, i, j, len, len1, ref, ref1;

WebPage.CALLBACKS = ['onConsoleMessage', 'onError', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onResourceError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing'];
WebPage.CALLBACKS = ['onConsoleMessage', 'onError', 'onLoadFinished', 'onInitialized', 'onLoadStarted', 'onResourceRequested', 'onResourceReceived', 'onResourceError', 'onNavigationRequested', 'onUrlChanged', 'onPageCreated', 'onClosing', 'onCallback'];

WebPage.DELEGATES = ['open', 'sendEvent', 'uploadFile', 'render', 'close', 'renderBase64', 'goBack', 'goForward', 'reload'];

Expand All @@ -16,6 +17,7 @@ Poltergeist.WebPage = (function() {
function WebPage(_native) {
var callback, i, len, ref;
this._native = _native;
this._checkForAsyncResult = bind(this._checkForAsyncResult, this);
this._native || (this._native = require('webpage').create());
this.id = 0;
this.source = null;
Expand All @@ -30,6 +32,8 @@ Poltergeist.WebPage = (function() {
this._requestedResources = {};
this._responseHeaders = [];
this._tempHeadersToRemoveOnRedirect = {};
this._asyncResults = {};
this._asyncEvaluationId = 0;
ref = WebPage.CALLBACKS;
for (i = 0, len = ref.length; i < len; i++) {
callback = ref[i];
Expand Down Expand Up @@ -119,6 +123,11 @@ Poltergeist.WebPage = (function() {
return true;
};

WebPage.prototype.onCallbackNative = function(data) {
this._asyncResults[data['command_id']] = data['command_result'];
return true;
};

WebPage.prototype.onResourceRequestedNative = function(request, net) {
var ref2;
this._networkTraffic[request.id] = {
Expand Down Expand Up @@ -568,6 +577,20 @@ Poltergeist.WebPage = (function() {
return result;
};

WebPage.prototype.evaluate_async = function() {
var args, callback, cb, command_id, fn, ref2;
fn = arguments[0], callback = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
command_id = ++this._asyncEvaluationId;
cb = callback;
this.injectAgent();
(ref2 = this["native"]()).evaluate.apply(ref2, ["function(){ var page_id = arguments[0]; var args = []; for(var i=1; i < arguments.length; i++){ if ((typeof(arguments[i]) == 'object') && (typeof(arguments[i]['ELEMENT']) == 'object')){ args.push(window.__poltergeist.get(arguments[i]['ELEMENT']['id']).element); } else { args.push(arguments[i]) } } args.push(function(result){ result = window.__poltergeist.wrapResults(result, page_id); window.callPhantom( { command_id: " + command_id + ", command_result: result } ); }); " + (this.stringifyCall(fn, "args")) + "; return}", this.id].concat(slice.call(args)));
setTimeout((function(_this) {
return function() {
return _this._checkForAsyncResult(command_id, cb);
};
})(this), 10);
};

WebPage.prototype.execute = function() {
var args, fn, ref2;
fn = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
Expand Down Expand Up @@ -643,6 +666,19 @@ Poltergeist.WebPage = (function() {
}
};

WebPage.prototype._checkForAsyncResult = function(command_id, callback) {
if (this._asyncResults.hasOwnProperty(command_id)) {
callback(this._asyncResults[command_id]);
delete this._asyncResults[command_id];
} else {
setTimeout((function(_this) {
return function() {
return _this._checkForAsyncResult(command_id, callback);
};
})(this), 50);
}
};

WebPage.prototype._blockRequest = function(url) {
var blacklisted, useWhitelist, whitelisted;
useWhitelist = this.urlWhitelist.length > 0;
Expand Down
4 changes: 4 additions & 0 deletions lib/capybara/poltergeist/client/main.coffee
Expand Up @@ -74,6 +74,10 @@ class Poltergeist.NoSuchWindowError extends Poltergeist.Error
name: "Poltergeist.NoSuchWindowError"
args: -> []

class Poltergeist.ScriptTimeoutError extends Poltergeist.Error
name: "Poltergeist.ScriptTimeoutError"
args: -> []

class Poltergeist.UnsupportedFeature extends Poltergeist.Error
constructor: (@message) ->
name: "Poltergeist.UnsupportedFeature"
Expand Down
44 changes: 43 additions & 1 deletion lib/capybara/poltergeist/client/web_page.coffee
Expand Up @@ -3,7 +3,7 @@ class Poltergeist.WebPage
'onLoadFinished', 'onInitialized', 'onLoadStarted',
'onResourceRequested', 'onResourceReceived', 'onResourceError',
'onNavigationRequested', 'onUrlChanged', 'onPageCreated',
'onClosing']
'onClosing', 'onCallback']

@DELEGATES = ['open', 'sendEvent', 'uploadFile', 'render', 'close',
'renderBase64', 'goBack', 'goForward', 'reload']
Expand All @@ -29,6 +29,8 @@ class Poltergeist.WebPage
@_requestedResources = {}
@_responseHeaders = []
@_tempHeadersToRemoveOnRedirect = {}
@_asyncResults = {}
@_asyncEvaluationId = 0

for callback in WebPage.CALLBACKS
this.bindCallback(callback)
Expand Down Expand Up @@ -85,6 +87,10 @@ class Poltergeist.WebPage
@errors.push(message: message, stack: stackString)
return true

onCallbackNative: (data) ->
@_asyncResults[data['command_id']] = data['command_result']
true

onResourceRequestedNative: (request, net) ->
@_networkTraffic[request.id] = {
request: request,
Expand Down Expand Up @@ -369,6 +375,32 @@ class Poltergeist.WebPage
return window.__poltergeist.wrapResults(_result, page_id); }", @id, args...)
result

evaluate_async: (fn, callback, args...) ->
command_id = ++@_asyncEvaluationId
cb = callback
this.injectAgent()
this.native().evaluate("function(){
var page_id = arguments[0];
var args = [];
for(var i=1; i < arguments.length; i++){
if ((typeof(arguments[i]) == 'object') && (typeof(arguments[i]['ELEMENT']) == 'object')){
args.push(window.__poltergeist.get(arguments[i]['ELEMENT']['id']).element);
} else {
args.push(arguments[i])
}
}
args.push(function(result){
result = window.__poltergeist.wrapResults(result, page_id);
window.callPhantom( { command_id: #{command_id}, command_result: result } );
});
#{this.stringifyCall(fn, "args")};
return}", @id, args...)

setTimeout( =>
@_checkForAsyncResult(command_id, cb)
, 10)
return

execute: (fn, args...) ->
this.native().evaluate("function() {
for(var i=0; i < arguments.length; i++){
Expand Down Expand Up @@ -426,6 +458,16 @@ class Poltergeist.WebPage
else
throw new Poltergeist.UnsupportedFeature("clearMemoryCache is supported since PhantomJS 2.0.0")

_checkForAsyncResult: (command_id, callback)=>
if @_asyncResults.hasOwnProperty(command_id)
callback(@_asyncResults[command_id])
delete @_asyncResults[command_id]
else
setTimeout(=>
@_checkForAsyncResult(command_id, callback)
, 50)
return

_blockRequest: (url) ->
useWhitelist = @urlWhitelist.length > 0

Expand Down
5 changes: 5 additions & 0 deletions lib/capybara/poltergeist/driver.rb
Expand Up @@ -139,6 +139,11 @@ def evaluate_script(script, *args)
unwrap_script_result(result)
end

def evaluate_async_script(script, *args)
result = browser.evaluate_async(script, session_wait_time, *args.map { |arg| arg.is_a?(Capybara::Poltergeist::Node) ? arg.native : arg})
unwrap_script_result(result)
end

def execute_script(script, *args)
browser.execute(script, *args.map { |arg| arg.is_a?(Capybara::Poltergeist::Node) ? arg.native : arg})
nil
Expand Down
7 changes: 7 additions & 0 deletions lib/capybara/poltergeist/errors.rb
Expand Up @@ -174,6 +174,13 @@ def message
end
end

class ScriptTimeoutError < Error
def message
"Timed out waiting for evaluated script to resturn a value"
end
end


class DeadClient < Error
def initialize(message)
@message = message
Expand Down
19 changes: 19 additions & 0 deletions spec/integration/driver_spec.rb
Expand Up @@ -1424,5 +1424,24 @@ def create_screenshot(file, *args)
})
end
end

context 'evaluate_async_script' do
it 'handles evaluate_async_script value properly' do
@session.using_wait_time(5) do
expect(@session.driver.evaluate_async_script('arguments[0](null)')).to be_nil
expect(@session.driver.evaluate_async_script('arguments[0](false)')).to be false
expect(@session.driver.evaluate_async_script('arguments[0](true)')).to be true
expect(@session.driver.evaluate_async_script("arguments[0]({foo: 'bar'})")).to eq({'foo' => 'bar'})
end
end

it 'will timeout' do
@session.using_wait_time(1) do
expect {
@session.driver.evaluate_async_script('var callback=arguments[0]; setTimeout(function(){callback(true)}, 4000)')
}.to raise_error Capybara::Poltergeist::ScriptTimeoutError
end
end
end
end
end

0 comments on commit dfa8a0d

Please sign in to comment.