Commit b962f381 b962f3811817fb325c7f8021360f96613ef8d521 by Nicolas Perriault

Casper#fill() can now fill input[type=file] fields; also added a new 'faultToler…

…ant' option to the Casper class
1 parent 1e8f2c59
......@@ -146,10 +146,12 @@ Casper constructor accepts a single `options` argument which is an object. Avail
```
Name | Type | Default | Description
——————————————————+——————————+—————————+————————————————————————————————————————————————————————————————————
——————————————————+——————————+—————————+————————————————————————————————————————————————————————————————————————
clientScripts | Array | [] | A collection of script filepaths to include to every page loaded
faultTolerant | Boolean | true | Catch and log exceptions when executing steps in a non-blocking fashion
logLevel | String | "error" | Logging level (see logLevels for available values)
onDie | function | null | A function to be called when Casper#die() is called
onError | function | null | A function to be called when an "error" level event occurs
onPageInitialized | function | null | A function to be called after WebPage instance has been initialized
page | WebPage | null | An existing WebPage instance
pageSettings | Object | {} | PhantomJS's WebPage settings object
......
......@@ -27,8 +27,9 @@
* Main Casper class. Available options are:
*
* Name | Type | Default | Description
* ——————————————————+——————————+—————————+————————————————————————————————————————————————————————————————————
* ——————————————————+——————————+—————————+————————————————————————————————————————————————————————————————————————
* clientScripts | Array | [] | A collection of script filepaths to include to every page loaded
* faultTolerant | Boolean | true | Catch and log exceptions when executing steps in a non-blocking fashion
* logLevel | String | "error" | Logging level (see logLevels for available values)
* onDie | function | null | A function to be called when Casper#die() is called
* onError | function | null | A function to be called when an "error" level event occurs
......@@ -51,6 +52,7 @@
// default options
this.defaults = {
clientScripts: [],
faultTolerant: true,
logLevel: "error",
onDie: null,
onError: null,
......@@ -151,10 +153,14 @@
self.log(stepInfo + self.page.evaluate(function() {
return document.location.href;
}) + ' (HTTP ' + self.currentHTTPStatus + ')', "info");
if (self.options.faultTolerant) {
try {
step(self);
} catch (e) {
self.log("Fatal: " + e, "error");
self.log("Step error: " + e, "error");
}
} else {
step(self);
}
var time = new Date().getTime() - self.startTime;
self.log(stepInfo + "done in " + time + "ms.", "info");
......@@ -195,7 +201,7 @@
* @return Casper
*/
debugHTML: function() {
this.echo(this.page.evaluate(function() {
this.echo(this.evaluate(function() {
return document.body.innerHTML;
}));
return this;
......@@ -207,7 +213,7 @@
* @return Casper
*/
debugPage: function() {
this.echo(this.page.evaluate(function() {
this.echo(this.evaluate(function() {
return document.body.innerText;
}));
return this;
......@@ -299,20 +305,42 @@
* Fills a form with provided field values.
*
* @param String selector A CSS3 selector to the target form to fill
* @param Object values Field values
* @param Object vals Field values
* @param Boolean submit Submit the form?
*/
fill: function(selector, values, submit) {
if (!typeof(values) === "object") {
fill: function(selector, vals, submit) {
if (!typeof(vals) === "object") {
throw "form values must be an object";
}
return this.evaluate(function() {
__utils__.fill('%selector%', JSON.parse('%values%'), JSON.parse('%submit%'));
var fillResults = this.evaluate(function() {
return __utils__.fill('%selector%', JSON.parse('%values%'));
}, {
selector: selector.replace("'", "\'"),
values: JSON.stringify(values).replace("'", "\'"),
submit: JSON.stringify(submit||false)
values: JSON.stringify(vals).replace("'", "\'"),
});
if (!fillResults) {
throw "unable to fill form";
}
// File uploads
if (fillResults.files && fillResults.files.length > 0) {
(function(self) {
fillResults.files.forEach(function(file) {
var fileFieldSelector = [selector, 'input[name="' + file.name + '"]'].join(' ');
self.page.uploadFile(fileFieldSelector, file.path);
});
})(this);
}
// Form submission?
if (submit === true) {
self.evaluate(function() {
var form = document.querySelector('%selector%');
console.log('submitting form to ' + (form.getAttribute('action') || "unknown")
+ ', HTTP ' + (form.getAttribute('method').toUpperCase() || "GET"));
form.submit();
}, {
selector: selector.replace("'", "\'"),
});
}
},
/**
......@@ -602,15 +630,19 @@
*
* @param HTMLElement|String form A form element, or a CSS3 selector to a form element
* @param Object vals Field values
* @return Object An object containing setting result for each field, including file uploads
*/
this.fill = function(form, vals, submit) {
submit = submit || false;
this.fill = function(form, vals) {
var out = {
fields: [],
files: [],
};
if (!(form instanceof HTMLElement) || typeof(form) === "string") {
form = document.querySelector(form);
}
if (!form) {
console.log('form not found or invalid selector provided:');
return;
return out;
}
for (var name in vals) {
if (!vals.hasOwnProperty(name)) {
......@@ -622,13 +654,20 @@
console.log('no field named "' + name + '" in form');
continue;
}
this.setField(field, value);
try {
out.fields[name] = this.setField(field, value);
} catch (e) {
if (e.name === "FileUploadError") {
out.files.push({
name: name,
path: e.path,
});
} else {
throw e;
}
}
if (submit) {
console.log('submitting form to ' + (form.getAttribute('action') || "unknown")
+ ', HTTP ' + (form.getAttribute('method').toUpperCase() || "GET"));
form.submit();
}
return out;
};
/**
......@@ -664,7 +703,7 @@
* @param mixed value The field value to set
*/
this.setField = function(field, value) {
var fields;
var fields, out;
value = value || "";
if (field instanceof NodeList) {
fields = field;
......@@ -673,7 +712,7 @@
if (!field instanceof HTMLElement) {
console.log('invalid field type; only HTMLElement and NodeList are supported');
}
console.log('set "' + field.getAttribute('name') + '" value to ' + value);
console.log('set "' + field.getAttribute('name') + '" field value to ' + value);
var nodeName = field.nodeName.toLowerCase();
switch (nodeName) {
case "input":
......@@ -701,7 +740,11 @@
field.setAttribute('checked', value ? "checked" : "");
break;
case "file":
console.log("file field filling is not supported");
throw {
name: "FileUploadError",
message: "file field must be filled using page.uploadFile",
path: value
};
break;
case "radio":
if (fields) {
......@@ -709,11 +752,11 @@
e.checked = (e.value === value);
});
} else {
console.log('radio elements are empty');
out = 'provided radio elements are empty';
}
break;
default:
console.log("unsupported input field type: " + type);
out = "unsupported input field type: " + type;
break;
}
break;
......@@ -722,10 +765,11 @@
field.value = value;
break;
default:
console.log('unsupported field type: ' + nodeName);
out = 'unsupported field type: ' + nodeName;
break;
}
}
return out;
};
};
/**
......
......@@ -2,9 +2,17 @@ phantom.injectJs('casper.js');
phantom.injectJs('tests/assert.js');
var casper = new phantom.Casper({
faultTolerant: false,
verbose: true,
});
phantom.args.forEach(function(arg) {
var debugMatch = /--loglevel=(\w+)/.exec(arg);
if (debugMatch) {
casper.options.logLevel = debugMatch[1];
}
});
casper.start('tests/site/index.html', function(self) {
self.assertEvalEquals(function() {
return document.title;
......@@ -25,10 +33,11 @@ casper.assert(casper.steps.length === 2, 'then() adds a new navigation step');
casper.then(function(self) {
self.fill('form[action="form.html"]', {
'email': 'chuck@norris.com',
'content': 'Am watching thou',
'check': true,
'choice': 'no'
email: 'chuck@norris.com',
content: 'Am watching thou',
check: true,
choice: 'no',
file: phantom.libraryPath + '/README.md'
});
self.assertEvalEquals(function() {
return document.querySelector('input[name="email"]').value;
......@@ -42,6 +51,15 @@ casper.then(function(self) {
self.assertEvalEquals(function() {
return document.querySelector('input[name="choice"][value="no"]').checked;
}, true, 'fill() can check a form radio button');
self.assertEvalEquals(function() {
return document.querySelector('input[name="choice"][value="no"]').checked;
}, true, 'fill() can check a form radio button');
self.assertEvalEquals(function() {
return document.querySelector('input[name="file"]').files.length === 1;
}, true, 'fill() can select a file to upload');
self.evaluate(function() {
document.querySelector('form[action="form.html"]').submit();
})
});
casper.run(function(self) {
......
......@@ -5,7 +5,7 @@
<title>CasperJS test form</title>
</head>
<body>
<form action="form.html">
<form action="form.html" enctype="multipart/form-data">
<input type="text" name="email" placeholder="email" />
<textarea name="content"></textarea>
<select name="topic">
......@@ -15,6 +15,7 @@
<input type="checkbox" name="check" />
<input type="radio" name="choice" value="yes"/>
<input type="radio" name="choice" value="no"/>
<input type="file" name="file"/>
<input type="submit"/>
</form>
</body>
......