Commit d8d08333 d8d083331dc4ac399fc803b3ce4bfc211c939d89 by Nicolas Perriault

refactored Casper.evaluate() in order to accept arguments!

So basically now you can do:

``` js
casper.evaluate(function(username, password) {
    document.querySelector('#username').value = username;
    document.querySelector('#password').value = password;
    document.querySelector('#submit').click();
}, {
    username: 'Bazoonga',
    password: 'baz00nga'
})
```
1 parent 4e490488
......@@ -120,11 +120,9 @@
* @return string Base64 encoded result
*/
base64encode: function(url) {
return this.evaluate(function() {
return __utils__.getBase64(__casper_params__.url);
}, {
url: url
});
return this.evaluate(function(url) {
return __utils__.getBase64(url);
}, { url: url });
},
/**
......@@ -168,9 +166,9 @@
* @return Casper
*/
captureSelector: function(targetFile, selector) {
return this.capture(targetFile, this.evaluate(function() {
return this.capture(targetFile, this.evaluate(function(selector) {
try {
var clipRect = document.querySelector(__casper_params__.selector).getBoundingClientRect();
var clipRect = document.querySelector(selector).getBoundingClientRect();
return {
top: clipRect.top,
left: clipRect.left,
......@@ -178,11 +176,9 @@
height: clipRect.height
};
} catch (e) {
__utils__.log("Unable to fetch bounds for element " + __casper_params__.selector, "warning");
__utils__.log("Unable to fetch bounds for element " + selector, "warning");
}
}, {
selector: selector
}));
}, { selector: selector }));
},
/**
......@@ -224,8 +220,8 @@
click: function(selector, fallbackToHref) {
fallbackToHref = isType(fallbackToHref, "undefined") ? true : !!fallbackToHref;
this.log("click on selector: " + selector, "debug");
return this.evaluate(function() {
return __utils__.click(__casper_params__.selector, __casper_params__.fallbackToHref);
return this.evaluate(function(selector, fallbackToHref) {
return __utils__.click(selector, fallbackToHref);
}, {
selector: selector,
fallbackToHref: fallbackToHref
......@@ -323,24 +319,12 @@
/**
* Evaluates an expression in the page context, a bit like what
* WebPage#evaluate does, but can also replace values by their
* placeholer names:
* WebPage#evaluate does, but the passed function can also accept
* parameters if a context Object is also passed:
*
* casper.evaluate(function() {
* document.querySelector('#username').value = '%username%';
* document.querySelector('#password').value = '%password%';
* document.querySelector('#submit').click();
* }, {
* username: 'Bazoonga',
* password: 'baz00nga'
* })
*
* As an alternative, CasperJS injects a `__casper_params__` Object
* instance containing all the parameters you passed:
*
* casper.evaluate(function() {
* document.querySelector('#username').value = __casper_params__.username;
* document.querySelector('#password').value = __casper_params__.password;
* casper.evaluate(function(username, password) {
* document.querySelector('#username').value = username;
* document.querySelector('#password').value = password;
* document.querySelector('#submit').click();
* }, {
* username: 'Bazoonga',
......@@ -351,25 +335,15 @@
* arguments to the function.
* TODO: don't forget to keep this backward compatible.
*
* @param function fn The function to be evaluated within current page DOM
* @param object replacements Parameters to pass to the remote environment
* @param Function fn The function to be evaluated within current page DOM
* @param Object context Object containing the parameters to inject into the function
* @return mixed
* @see WebPage#evaluate
*/
evaluate: function(fn, replacements) {
replacements = isType(replacements, "object") ? replacements : {};
this.page.evaluate(replaceFunctionPlaceholders(function() {
window.__casper_params__ = {};
try {
var jsonString = unescape(decodeURIComponent('%replacements%'));
window.__casper_params__ = JSON.parse(jsonString);
} catch (e) {
__utils__.log("Unable to replace parameters: " + e, "error");
}
}, {
replacements: encodeURIComponent(escape(JSON.stringify(replacements).replace("'", "\'")))
}));
return this.page.evaluate(replaceFunctionPlaceholders(fn, replacements));
evaluate: function(fn, context) {
context = isType(context, "object") ? context : {};
var newFn = new phantom.Casper.FunctionArgsInjector(fn, context).process();
return this.page.evaluate(newFn);
},
/**
......@@ -395,11 +369,9 @@
* @return Boolean
*/
exists: function(selector) {
return this.evaluate(function() {
return __utils__.exists(__casper_params__.selector);
}, {
selector: selector
});
return this.evaluate(function(selector) {
return __utils__.exists(selector);
}, { selector: selector });
},
/**
......@@ -411,11 +383,9 @@
* @return Boolean
*/
visible: function(selector) {
return this.evaluate(function() {
return __utils__.visible(__casper_params__.selector);
}, {
selector: selector
});
return this.evaluate(function(selector) {
return __utils__.visible(selector);
}, { selector: selector });
},
/**
......@@ -437,11 +407,9 @@
* @return String
*/
fetchText: function(selector) {
return this.evaluate(function() {
return __utils__.fetchText(__casper_params__.selector);
}, {
selector: selector
});
return this.evaluate(function(selector) {
return __utils__.fetchText(selector);
}, { selector: selector });
},
/**
......@@ -459,8 +427,8 @@
if (!isType(vals, "object")) {
throw "form values must be provided as an object";
}
var fillResults = this.evaluate(function() {
return __utils__.fill(__casper_params__.selector, __casper_params__.values);
var fillResults = this.evaluate(function(selector, values) {
return __utils__.fill(selector, values);
}, {
selector: selector,
values: vals
......@@ -489,15 +457,13 @@
}
// Form submission?
if (submit) {
this.evaluate(function() {
var form = document.querySelector(__casper_params__.selector);
this.evaluate(function(selector) {
var form = document.querySelector(selector);
var method = form.getAttribute('method').toUpperCase() || "GET";
var action = form.getAttribute('action') || "unknown";
__utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info');
form.submit();
}, {
selector: selector
});
}, { selector: selector });
}
},
......@@ -532,8 +498,7 @@
* @return mixed
*/
getGlobal: function(name) {
var result = this.evaluate(function() {
var name = window.__casper_params__.name;
var result = this.evaluate(function(name) {
var result = {};
try {
result.value = JSON.stringify(window[name]);
......@@ -787,13 +752,13 @@
* current retrieved page DOM.
*
* @param function fn The function to be evaluated within current page DOM
* @param object replacements Optional replacements to performs, eg. for '%foo%' => {foo: 'bar'}
* @param object context Optional function parameters context
* @return Casper
* @see Casper#evaluate
*/
thenEvaluate: function(fn, replacements) {
thenEvaluate: function(fn, context) {
return this.then(function(self) {
self.evaluate(fn, replacements);
self.evaluate(fn, context);
});
},
......@@ -820,13 +785,13 @@
*
* @param String location The url to open
* @param function fn The function to be evaluated within current page DOM
* @param object replacements Optional replacements to performs, eg. for '%foo%' => {foo: 'bar'}
* @param object context Optional function parameters context
* @return Casper
* @see Casper#evaluate
* @see Casper#open
*/
thenOpenAndEvaluate: function(location, fn, replacements) {
return this.thenOpen(location).thenEvaluate(fn, replacements);
thenOpenAndEvaluate: function(location, fn, context) {
return this.thenOpen(location).thenEvaluate(fn, context);
},
/**
......@@ -1681,6 +1646,51 @@
};
/**
* Function argument injector.
*
*/
phantom.Casper.FunctionArgsInjector = function(fn, values) {
this.fn = fn;
this.values = typeof values === "object" ? values : {};
this.extract = function(fn) {
var match = /^function\s?(\w+)?\s?\((.*)\)\s?\{([\s\S]*)\}/i.exec(fn.toString().trim());
if (match && match.length > 1) {
var args = match[2].split(',').map(function(arg) {
return arg.replace(new RegExp(/\/\*+.*\*\//ig), "").trim();
}).filter(function(arg) {
return arg;
}) || [];
return {
name: match[1] ? match[1].trim() : null,
args: args,
body: match[3] ? match[3].trim() : ''
};
}
};
this.process = function() {
var fnObj = this.extract(this.fn);
var inject = this.getArgsInjectionString(fnObj.args, this.values);
return 'function ' + (fnObj.name || '') + '(){' + inject + fnObj.body + '}';
};
this.getArgsInjectionString = function(args, values) {
values = typeof values === "object" ? values : {};
var jsonValues = escape(encodeURIComponent(JSON.stringify(values)));
var inject = [
'var __casper_params__ = JSON.parse(decodeURIComponent(unescape(\'' + jsonValues + '\')));'
];
args.forEach(function(arg) {
if (arg in values) {
inject.push('var ' + arg + '=__casper_params__["' + arg + '"];');
}
});
return inject.join('\n') + '\n';
};
};
/**
* JUnit XML (xUnit) exporter for test results.
*
*/
......@@ -1817,7 +1827,7 @@
}
}
}
// Client utils injection
// Client-side utils injection
var injected = page.evaluate(replaceFunctionPlaceholders(function() {
eval("var ClientUtils = " + decodeURIComponent("%utils%"));
__utils__ = new ClientUtils();
......
......@@ -78,7 +78,7 @@ fs.remove(testFile);
// Casper#evaluate()
casper.then(function(self) {
self.test.comment('evaluating');
self.test.comment('Casper.evaluate()');
var params = {
"boolean true": true,
"boolean false": false,
......@@ -92,7 +92,7 @@ casper.then(function(self) {
return __casper_params__;
}, params);
self.test.assertType(casperParams, "object", 'Casper.evaluate() exposes parameters in a dedicated object');
self.test.assertEquals(Object.keys(casperParams).length, 7, 'Casper.evaluate() exposes parameters object has the correct length');
self.test.assertEquals(Object.keys(casperParams).length, 7, 'Casper.evaluate() object containing parameters has the correct length');
for (var param in casperParams) {
self.test.assertEquals(JSON.stringify(casperParams[param]), JSON.stringify(params[param]), 'Casper.evaluate() can pass a ' + param);
self.test.assertEquals(typeof casperParams[param], typeof params[param], 'Casper.evaluate() preserves the ' + param + ' type');
......