Update casperjs to 1.0.2.

(imported from commit 9e34b51c4588dce6419ea86024b2e8c06346a685)
This commit is contained in:
Tim Abbott
2013-03-05 11:10:02 -05:00
parent d1fb74e627
commit eadb2ea6d3
116 changed files with 2757 additions and 745 deletions

View File

@@ -9,6 +9,7 @@
"maxdepth": 3, "maxdepth": 3,
"maxstatements": 15, "maxstatements": 15,
"maxcomplexity": 7, "maxcomplexity": 7,
"proto": true,
"regexdash": true, "regexdash": true,
"strict": true, "strict": true,
"sub": true, "sub": true,

View File

@@ -1,13 +1,16 @@
branches: branches:
only: only:
- master - "master"
- "1.0"
before_script: before_script:
- "npm install -g jshint"
- "phantomjs --version" - "phantomjs --version"
- "export PHANTOMJS_EXECUTABLE='phantomjs --local-to-remote-url-access=yes --ignore-ssl-errors=yes'" - "export PHANTOMJS_EXECUTABLE='phantomjs --local-to-remote-url-access=yes --ignore-ssl-errors=yes'"
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
script: script:
- "DISPLAY=:99.0 ./bin/casperjs selftest" - "./bin/casperjs selftest"
- "./bin/casperjs __selfcommandtest"
after_script:
- "jshint --config=.jshintconfig ."
notifications: notifications:
irc: irc:
channels: channels:

View File

@@ -1,6 +1,186 @@
CasperJS Changelog CasperJS Changelog
================== ==================
2013-02-08, v1.0.2
------------------
- fixed [#375](https://github.com/n1k0/casperjs/pull/375) - Fixes a bug with getting form values for radio inputs, and introduces a minor optimization to avoid processing the same form fields more than once.
- closed [#373](https://github.com/n1k0/casperjs/issues/373) - added RegExp support to `Casper.waitForText()`
- fixed [#368](https://github.com/n1k0/casperjs/issues/368) - Remote JS error is thrown when a click target is missing after `click()`
- merged PR [#357](https://github.com/n1k0/casperjs/pull/357) - fire the `input` event after setting input value (required to support [angular.js](http://angularjs.org/) apps)
2013-01-17, v1.0.1
------------------
- fixed [#336](https://github.com/n1k0/casperjs/issues/336) - Test result duration may have an exotic value
- Added `casper.mouse.doubleclick()`
- fixed [#343](https://github.com/n1k0/casperjs/issues/343) - Better script checks
- fixed an edge case with xunit export when `phantom.casperScript` may be not defined
2012-12-24, v1.0.0
------------------
### Important Changes & Caveats
- PhantomJS 1.6.x support has been dropped. Both PhantomJS [1.7](http://phantomjs.org/release-1.7.html) & [1.8](http://phantomjs.org/release-1.8.html) will be supported.
- the deprecated `injector` module has been removed from the codebase (RIP dude)
- a [`1.0` maintenance branch](https://github.com/n1k0/casperjs/tree/1.0) has been created
- CasperJS 1.1 development is now taking place on the `master` branch
### Bugfixes & enhancements
- fixed `page.initialized` event didn't get the initialized `WebPage` instance
- fixed a bug preventing `Casper.options.onPageInitialized()` from being called
- fixed [#215](https://github.com/n1k0/casperjs/issues/215) - fixed broken `--fail-fast` option creating an endless loop on error
- fixed `Tester.renderFailureDetails()` which couldn't print failure details correctly in certain circumstances
- fixed `Casper.getHTML()` wasn't retrieving active frame contents when using `Casper.withFrame()`
- fixed [#327](https://github.com/n1k0/casperjs/issues/327) - event handler for `page.confirm` always returns true
- merged PR [#322](https://github.com/n1k0/casperjs/pull/322) - Support number in `Casper.withFrame()`
- fixed [#323](https://github.com/n1k0/casperjs/issues/323) - `thenEvaluate()` should be updated to take the same parameters as `evaluate()`, while maintaining backwards compatibility.
- merged PR [#319](https://github.com/n1k0/casperjs/pull/319), fixed [#209](https://github.com/n1k0/casperjs/issues/209) - test duration has been added to XUnit XML result file.
- `Casper.userAgent()` does not require the instance to be started anymore
- dubious tests now have dedicated color & styling
- added hint printing when a possible `casperjs` command call is detected
2012-12-14, v1.0.0-RC6
----------------------
I'm still expecting a 1.0 stable for Christmas. Feedback: bring it on.
### Important Changes & Caveats
#### Added experimental support for frames
A minimal convenient API has been added to Casper in order to ease the switch of current page context:
```js
casper.start('tests/site/frames.html', function() {
this.test.assertTitle('CasperJS frameset');
});
casper.withFrame('frame1', function() {
this.test.assertTitle('CasperJS frame 1');
});
casper.then(function() {
this.test.assertTitle('CasperJS frameset');
});
```
#### Reverted to emulated mouse events
Native mouse events didn't play well with (i)frames, because the computed element coordinates of the clicked element were erroneous.
So programmatic mouse events are reintroduced back into this corrective RC until a better solution is found.
### Bugfixes & enhancements
- merged [#269](https://github.com/n1k0/casperjs/issues/269) - Windows Batch script: fixed unsupported spaces in path and argument splitting
2012-12-10, v1.0.0-RC5
----------------------
I told you there won't be an 1.0.0-RC5? I lied. Expect 1.0 stable for Christmas, probably.
### Important Changes & Caveats
#### Casper.evaluate() signature compatibility with PhantomJS
`Casper.evaluate()` method signature is now compatible with PhantomJS' one, so you can now write:
```js
casper.evaluate(function(a, b) {
return a === "foo" && b === "bar";
}, "foo", "bar"); // true
```
The old way to pass arguments has been kept backward compatible in order not to break your existing scripts though:
```js
casper.evaluate(function(a, b) {
return a === "foo" && b === "bar";
}, {a: "foo", b: "bar"}); // true
```
#### Specification of planned tests
In order to check that every planned test has actuall been executed, a new optional `planned` parameter has been added to `Tester.done()`:
```js
casper.test.assert(true);
casper.test.assert(true);
casper.test.assert(true);
casper.test.done(4);
```
Will trigger a failure:
```
fail: 4 tests planned, 3 tests executed.
```
That's especially useful in case a given test script is abruptly interrupted leaving you with no obvious way to know it and an erroneous success status.
The whole [CapserJS test suite](https://github.com/n1k0/casperjs/tree/master/tests/) has been migrated to use this new feature.
#### Experimental support for popups
PhantomJS 1.7 ships with support for new opened pages — aka popups. CasperJS can now wait for a popup to be opened and loaded to react accordingly using the new [`Casper.waitForPopup()`](http://casperjs.org/api.html#casper.waitForPopup) and [`Casper.withPopup()`](http://casperjs.org/api.html#casper.withPopup) methods:
```js
casper.start('http://foo.bar/').then(function() {
this.test.assertTitle('Main page title');
this.clickLabel('Open me a popup');
});
// this will wait for the popup to be opened and loaded
casper.waitForPopup(/popup\.html$/, function() {
this.test.assertEquals(this.popups.length, 1);
});
// this will set the popup DOM as the main active one only for time the
// step closure being executed
casper.withPopup(/popup\.html$/, function() {
this.test.assertTitle('Popup title');
});
// next step will automatically revert the current page to the initial one
casper.then(function() {
this.test.assertTitle('Main page title');
});
```
#### `Casper.mouseEvent()` now uses native events for most operations
Native mouse events from PhantomJS bring a far more accurate behavior.
Also, `Casper.mouseEvent()` will now directly trigger an error on failure instead of just logging an `error` event.
### Bugfixes & enhancements
- fixed [#308](https://github.com/n1k0/casperjs/issues/308) & [#309](https://github.com/n1k0/casperjs/issues/309) - proper module error backtraces
- fixed [#306](https://github.com/n1k0/casperjs/issues/306) - Raise an explicit error on invalid test path
- fixed [#300](https://github.com/n1k0/casperjs/issues/300) - Ensure that `findOne()` and `findAll()` observe the scope for XPath expressions, not just when passed CSS selectors
- fixed [#294](https://github.com/n1k0/casperjs/issues/294) - Automatically fail test on any runtime error or timeout
- fixed [#281](https://github.com/n1k0/casperjs/issues/281) - `Casper.evaluate()` should take an array as context not object
- fixed [#266](https://github.com/n1k0/casperjs/issues/266) - Fix `tester` module and its self tests
- fixed [#268](https://github.com/n1k0/casperjs/issues/266) - Wrong message on step timeout
- fixed [#215](https://github.com/n1k0/casperjs/issues/215) - added a `--fail-fast` option to the `casper test` command, in order to terminate a test suite execution as soon as any failure is encountered
- fixed [#274](https://github.com/n1k0/casperjs/issues/274) - some headers couldn't be set
- fixed [#277](https://github.com/n1k0/casperjs/issues/277) - multiline support in `ClientUtils.echo()`
- fixed [#282](https://github.com/n1k0/casperjs/issues/282) - added support for remote client scripts loading with a new `remoteScripts` casper option
- fixed [#290](https://github.com/n1k0/casperjs/issues/#290) - add a simplistic RPM spec file to make it easier to (un)install casperjs
- fixed [`utils.betterTypeOf()`](http://casperjs.org/api.html#casper.betterTypeOf) to properly handle `undefined` and `null` values
- fixed `Casper.die()` and `Casper.evaluateOrDie()` were not printing the error onto the console
- added JSON support to `require()`
- added [`Tester.assertTruthy()`](http://casperjs.org/api.html#tester.assertTruthy) and [`Tester.assertFalsy()`](http://casperjs.org/api.html#tester.assertFalsy)
- added [`Casper.sendKeys()`](http://casperjs.org/api.html#casper.sendKeys) to send native keyboard events to the element matching a given selector
- added [`Casper.getFormValues()`](http://casperjs.org/api.html#casper.getFormValues) to check for the field values of a given form
- added [`Tester.assertTextDoesntExist()`](http://casperjs.org/api.html#tester.assertTextDoesntExist)
- added `Tester.assertFalse()` as an alias of `Tester.assertNot()`
- added `page.resource.requested` and `page.resource.received` events
- added [`translate.js`](https://github.com/n1k0/casperjs/tree/master/samples/translate.js) and [`translate.coffee`](https://github.com/n1k0/casperjs/tree/master/samples/translate.coffee) samples
2012-10-31, v1.0.0-RC4 2012-10-31, v1.0.0-RC4
---------------------- ----------------------

View File

@@ -3,44 +3,51 @@
You can check out the [contribution graphs on github](https://github.com/n1k0/casperjs/graphs/contributors). You can check out the [contribution graphs on github](https://github.com/n1k0/casperjs/graphs/contributors).
``` ```
$ git shortlog -s -n $ git shortlog -s -n | cut -c8-
689 Nicolas Perriault Nicolas Perriault
14 oncletom Brikou CARRE
14 Brikou CARRE oncletom
8 hannyu hannyu
6 Chris Lorenzo Chris Lorenzo
4 pborreli Victor Yap
4 nrabinowitz nrabinowitz
3 Andrew Childs pborreli
3 Solomon White Rob Barreca
3 reina.sweet Andrew Childs
2 Reina Sweet Solomon White
2 Jason Funk reina.sweet
2 Michael Geers Dave Lee
2 Julien Moulin Reina Sweet
2 Donovan Hutchinson Elmar Langholz
2 Clochix Jason Funk
1 Marcel Duran Donovan Hutchinson
1 Mathieu Agopian Julien Moulin
1 Mehdi Kabab Michael Geers
1 Mikko Peltonen Jan Schaumann
1 Pascal Borreli Clochix
1 Rafael Raphaël Benitte
1 Rafael Garcia Tim Bunce
1 Raphaël Benitte alfetopito
1 Andrew de Andrade jean-philippe serafin
1 Tim Bunce snkashis
1 Victor Yap Andrew de Andrade
1 alfetopito Ben Lowery
1 Christophe Benz Chris Winters
1 jean-philippe serafin Christophe Benz
1 Chris Winters Harrison Reiser
1 Ben Lowery Jan Pochyla
1 Jan Pochyla Jan-Martin Fruehwacht
1 Harrison Reiser Julian Gruber
1 Julian Gruber Justin Slattery
1 Justine Tunney Justine Tunney
1 KaroDidi KaroDidi
1 Leandro Boscariol Leandro Boscariol
1 Maisons du monde Maisons du monde
Marcel Duran
Mathieu Agopian
Mehdi Kabab
Mikko Peltonen
Pascal Borreli
Rafael
Rafael Garcia
``` ```

View File

@@ -1,22 +1,5 @@
@ECHO OFF @ECHO OFF
set CASPER_PATH=%~dp0..\ set CASPER_PATH=%~dp0..
set CASPER_BIN=%CASPER_PATH%bin\ set CASPER_BIN=%CASPER_PATH%\bin\
set ARGV=%*
set PHANTOMJS_NATIVE_ARGS=(--cookies-file --config --debug --disk-cache --ignore-ssl-errors --load-images --load-plugins --local-storage-path --local-storage-quota --local-to-remote-url-access --max-disk-cache-size --output-encoding --proxy --proxy-auth --proxy-type --remote-debugger-port --remote-debugger-autorun --script-encoding --web-security) call phantomjs "%CASPER_BIN%bootstrap.js" --casper-path="%CASPER_PATH%" --cli %ARGV%
set PHANTOM_ARGS=
set CASPER_ARGS=
:Loop
if "%1"=="" goto Continue
set IS_PHANTOM_ARG=0
for %%i in %PHANTOMJS_NATIVE_ARGS% do (
if "%%i"=="%1" set IS_PHANTOM_ARG=1
)
if %IS_PHANTOM_ARG%==0 set CASPER_ARGS=%CASPER_ARGS% %1
if %IS_PHANTOM_ARG%==1 set PHANTOM_ARGS=%PHANTOM_ARGS% %1
shift
goto Loop
:Continue
call phantomjs%PHANTOM_ARGS% %CASPER_BIN%bootstrap.js --casper-path=%CASPER_PATH% --cli%CASPER_ARGS%

View File

@@ -33,14 +33,21 @@
if (!phantom) { if (!phantom) {
console.error('CasperJS needs to be executed in a PhantomJS environment http://phantomjs.org/'); console.error('CasperJS needs to be executed in a PhantomJS environment http://phantomjs.org/');
phantom.exit(1);
} }
if (phantom.version.major === 1 && phantom.version.minor < 6) { if (phantom.version.major === 1 && phantom.version.minor < 7) {
console.error('CasperJS needs at least PhantomJS v1.6.0 or later.'); console.error('CasperJS needs at least PhantomJS v1.7 or later.');
phantom.exit(1); phantom.exit(1);
} else { } else {
bootstrap(window); try {
bootstrap(window);
} catch (e) {
console.error(e);
(e.stackArray || []).forEach(function(entry) {
console.error(' In ' + entry.sourceURL + ':' + entry.line);
});
phantom.exit(1);
}
} }
// Polyfills // Polyfills
@@ -106,10 +113,13 @@ function patchRequire(require, requireDirs) {
fileGuesses.push.apply(fileGuesses, [ fileGuesses.push.apply(fileGuesses, [
testPath, testPath,
testPath + '.js', testPath + '.js',
testPath + '.json',
testPath + '.coffee', testPath + '.coffee',
fs.pathJoin(testPath, 'index.js'), fs.pathJoin(testPath, 'index.js'),
fs.pathJoin(testPath, 'index.json'),
fs.pathJoin(testPath, 'index.coffee'), fs.pathJoin(testPath, 'index.coffee'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'), fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.json'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee') fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee')
]); ]);
}); });
@@ -120,16 +130,25 @@ function patchRequire(require, requireDirs) {
} }
} }
if (!file) { if (!file) {
throw new Error("CasperJS couldn't find module " + path); throw new window.CasperError("CasperJS couldn't find module " + path);
} }
if (file in requireCache) { if (file in requireCache) {
return requireCache[file].exports; return requireCache[file].exports;
} }
if (/\.json/i.test(file)) {
var parsed = JSON.parse(fs.read(file));
requireCache[file] = parsed;
return parsed;
}
var scriptCode = (function getScriptCode(file) { var scriptCode = (function getScriptCode(file) {
var scriptCode = fs.read(file); var scriptCode = fs.read(file);
if (/\.coffee$/i.test(file)) { if (/\.coffee$/i.test(file)) {
/*global CoffeeScript*/ /*global CoffeeScript*/
scriptCode = CoffeeScript.compile(scriptCode); try {
scriptCode = CoffeeScript.compile(scriptCode);
} catch (e) {
throw new Error('Unable to compile coffeescript:' + e);
}
} }
return scriptCode; return scriptCode;
})(file); })(file);
@@ -137,8 +156,14 @@ function patchRequire(require, requireDirs) {
try { try {
fn(file, _require, module, module.exports); fn(file, _require, module, module.exports);
} catch (e) { } catch (e) {
var error = new window.CasperError('__mod_error(' + path + '):: ' + e); var error = new window.CasperError('__mod_error(' + path + ':' + e.line + '):: ' + e);
error.file = file; error.file = file;
error.line = e.line;
error.stack = e.stack;
error.stackArray = JSON.parse(JSON.stringify(e.stackArray));
if (error.stackArray.length > 0) {
error.stackArray[0].sourceURL = file;
}
throw error; throw error;
} }
requireCache[file] = module; requireCache[file] = module;
@@ -150,9 +175,20 @@ function patchRequire(require, requireDirs) {
function bootstrap(global) { function bootstrap(global) {
"use strict"; "use strict";
var phantomArgs = require('system').args; var phantomArgs = require('system').args;
/**
* Hooks in default phantomjs error handler to print a hint when a possible
* casperjs command misuse is detected.
*
*/
phantom.onError = function onPhantomError(msg, trace) {
phantom.defaultErrorHandler.apply(phantom, arguments);
if (msg.indexOf("ReferenceError: Can't find variable: casper") === 0) {
console.error('Hint: you may want to use the `casperjs test` command.');
}
};
/** /**
* Loads and initialize the CasperJS environment. * Loads and initialize the CasperJS environment.
*/ */
@@ -230,7 +266,7 @@ function bootstrap(global) {
throw new global.CasperError('Cannot read package file contents: ' + e); throw new global.CasperError('Cannot read package file contents: ' + e);
} }
parts = pkg.version.trim().split("."); parts = pkg.version.trim().split(".");
if (parts < 3) { if (parts.length < 3) {
throw new global.CasperError("Invalid version number"); throw new global.CasperError("Invalid version number");
} }
patchPart = parts[2].split('-'); patchPart = parts[2].split('-');
@@ -264,20 +300,21 @@ function bootstrap(global) {
*/ */
phantom.initCasperCli = function initCasperCli() { phantom.initCasperCli = function initCasperCli() {
var fs = require("fs"); var fs = require("fs");
var baseTestsPath = fs.pathJoin(phantom.casperPath, 'tests');
if (!!phantom.casperArgs.options.version) { if (!!phantom.casperArgs.options.version) {
console.log(phantom.casperVersion.toString()); console.log(phantom.casperVersion.toString());
phantom.exit(0); return phantom.exit();
} else if (phantom.casperArgs.get(0) === "test") { } else if (phantom.casperArgs.get(0) === "test") {
phantom.casperScript = fs.absolute(fs.pathJoin(phantom.casperPath, 'tests', 'run.js')); phantom.casperScript = fs.absolute(fs.pathJoin(baseTestsPath, 'run.js'));
phantom.casperTest = true; phantom.casperTest = true;
phantom.casperArgs.drop("test"); phantom.casperArgs.drop("test");
} else if (phantom.casperArgs.get(0) === "selftest") { } else if (phantom.casperArgs.get(0) === "selftest") {
phantom.casperScript = fs.absolute(fs.pathJoin(phantom.casperPath, 'tests', 'run.js')); phantom.casperScript = fs.absolute(fs.pathJoin(baseTestsPath, 'run.js'));
phantom.casperSelfTest = true; phantom.casperSelfTest = phantom.casperTest = true;
phantom.casperArgs.options.includes = fs.pathJoin(phantom.casperPath, 'tests', 'selftest.js'); phantom.casperArgs.options.includes = fs.pathJoin(baseTestsPath, 'selftest.js');
if (phantom.casperArgs.args.length <= 1) { if (phantom.casperArgs.args.length <= 1) {
phantom.casperArgs.args.push(fs.pathJoin(phantom.casperPath, 'tests', 'suites')); phantom.casperArgs.args.push(fs.pathJoin(baseTestsPath, 'suites'));
} }
phantom.casperArgs.drop("selftest"); phantom.casperArgs.drop("selftest");
} else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) { } else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) {
@@ -287,25 +324,30 @@ function bootstrap(global) {
phantom.casperVersion.toString(), phantom.casperVersion.toString(),
phantom.casperPath, phantomVersion)); phantom.casperPath, phantomVersion));
console.log(fs.read(fs.pathJoin(phantom.casperPath, 'bin', 'usage.txt'))); console.log(fs.read(fs.pathJoin(phantom.casperPath, 'bin', 'usage.txt')));
phantom.exit(0); return phantom.exit(0);
} }
if (!phantom.casperScript) { if (!phantom.casperScript) {
phantom.casperScript = phantom.casperArgs.get(0); phantom.casperScript = phantom.casperArgs.get(0);
} }
if (phantom.casperScript) { if (!fs.isFile(phantom.casperScript)) {
if (!fs.isFile(phantom.casperScript)) { console.error('Unable to open file: ' + phantom.casperScript);
console.error('Unable to open file: ' + phantom.casperScript); return phantom.exit(1);
phantom.exit(1); }
} else {
// filter out the called script name from casper args
phantom.casperArgs.drop(phantom.casperScript);
// passed casperjs script execution // filter out the called script name from casper args
phantom.injectJs(phantom.casperScript); phantom.casperArgs.drop(phantom.casperScript);
}
// passed casperjs script execution
var injected = false;
try {
injected = phantom.injectJs(phantom.casperScript);
} catch (e) {
throw new global.CasperError('Error loading script ' + phantom.casperScript + ': ' + e);
}
if (!injected) {
throw new global.CasperError('Unable to load script ' + phantom.casperScript + '; check file syntax');
} }
}; };

View File

@@ -1,8 +1,17 @@
#!/usr/bin/env python #!/usr/bin/env python
import json
import os import os
import subprocess
import sys import sys
def test_cmd(cmd):
try:
return subprocess.check_output([__file__] + cmd.split(' '))
except subprocess.CalledProcessError as err:
sys.stderr.write('FAIL: %s\n' % ' '.join(err.cmd))
sys.stderr.write(' %s\n' % err.output)
sys.exit(1)
def resolve(path): def resolve(path):
if os.path.islink(path): if os.path.islink(path):
@@ -32,9 +41,25 @@ PHANTOMJS_NATIVE_ARGS = [
'web-security', 'web-security',
] ]
CASPER_ARGS = [] CASPER_ARGS = []
CASPER_PATH = os.path.abspath(os.path.join(os.path.dirname(resolve(__file__)), '..'))
PHANTOMJS_ARGS = [] PHANTOMJS_ARGS = []
SYS_ARGS = sys.argv[1:]
for arg in sys.argv[1:]: if len(SYS_ARGS) and SYS_ARGS[0] == '__selfcommandtest':
print('Starting Python executable tests...')
pkg_version = json.loads(open('package.json').read()).get('version')
scripts_path = os.path.join(CASPER_PATH, 'tests', 'commands')
assert(test_cmd('--help').find(pkg_version) > -1)
assert(test_cmd('--version').strip() == pkg_version)
assert(test_cmd(os.path.join(scripts_path, 'script.js')) == 'it works\n')
test_output = test_cmd('test --no-colors ' + os.path.join(scripts_path, 'mytest.js'))
assert('PASS ok1' in test_output)
assert('PASS ok2' in test_output)
assert('PASS ok3' in test_output)
print('Python executable tests passed.')
sys.exit(0)
for arg in SYS_ARGS:
found = False found = False
for native in PHANTOMJS_NATIVE_ARGS: for native in PHANTOMJS_NATIVE_ARGS:
if arg.startswith('--%s' % native): if arg.startswith('--%s' % native):
@@ -43,7 +68,6 @@ for arg in sys.argv[1:]:
if not found: if not found:
CASPER_ARGS.append(arg) CASPER_ARGS.append(arg)
CASPER_PATH = os.path.abspath(os.path.join(os.path.dirname(resolve(__file__)), '..'))
CASPER_COMMAND = os.environ.get('PHANTOMJS_EXECUTABLE', 'phantomjs').split(' ') CASPER_COMMAND = os.environ.get('PHANTOMJS_EXECUTABLE', 'phantomjs').split(' ')
CASPER_COMMAND.extend(PHANTOMJS_ARGS) CASPER_COMMAND.extend(PHANTOMJS_ARGS)
CASPER_COMMAND.extend([ CASPER_COMMAND.extend([

View File

@@ -1,10 +1,11 @@
Usage: casperjs [options] script.[js|coffee] [script argument [script argument ...]] Usage: casperjs [options] script.[js|coffee] [script argument [script argument ...]]
casperjs [options] test [test path [test path ...]] casperjs [options] test [test path [test path ...]]
casperjs [options] selftest
Options: Options:
--help Prints this help --help Prints this help
--version Prints out CasperJS version --version Prints out CasperJS version
Read the docs http://casperjs.org/ Read the docs http://casperjs.org/

View File

@@ -19,5 +19,5 @@ high-level functions, methods & syntaxic sugar for doing common tasks."
s.bindir = "rubybin" s.bindir = "rubybin"
s.executables = [ "casperjs" ] s.executables = [ "casperjs" ]
s.license = "MIT" s.license = "MIT"
s.requirements = [ "PhantomJS v1.6" ] s.requirements = [ "PhantomJS v1.7" ]
end end

View File

@@ -35,6 +35,7 @@ var events = require('events');
var fs = require('fs'); var fs = require('fs');
var http = require('http'); var http = require('http');
var mouse = require('mouse'); var mouse = require('mouse');
var pagestack = require('pagestack');
var qs = require('querystring'); var qs = require('querystring');
var tester = require('tester'); var tester = require('tester');
var utils = require('utils'); var utils = require('utils');
@@ -75,7 +76,7 @@ exports.selectXPath = selectXPath;
*/ */
var Casper = function Casper(options) { var Casper = function Casper(options) {
"use strict"; "use strict";
/*jshint maxstatements:30*/ /*jshint maxstatements:40*/
// init & checks // init & checks
if (!(this instanceof Casper)) { if (!(this instanceof Casper)) {
return new Casper(options); return new Casper(options);
@@ -110,6 +111,7 @@ var Casper = function Casper(options) {
localToRemoteUrlAccessEnabled: true, localToRemoteUrlAccessEnabled: true,
userAgent: defaultUserAgent userAgent: defaultUserAgent
}, },
remoteScripts: [],
stepTimeout: null, stepTimeout: null,
timeout: null, timeout: null,
verbose: false, verbose: false,
@@ -138,6 +140,7 @@ var Casper = function Casper(options) {
this.mouse = mouse.create(this); this.mouse = mouse.create(this);
this.page = null; this.page = null;
this.pendingWait = false; this.pendingWait = false;
this.popups = pagestack.create();
this.requestUrl = 'about:blank'; this.requestUrl = 'about:blank';
this.resources = []; this.resources = [];
this.result = { this.result = {
@@ -154,12 +157,14 @@ var Casper = function Casper(options) {
this.initErrorHandler(); this.initErrorHandler();
this.on('error', function(msg, backtrace) { this.on('error', function(msg, backtrace) {
if (msg === this.test.SKIP_MESSAGE) {
return;
}
var c = this.getColorizer(); var c = this.getColorizer();
var match = /^(.*): __mod_error(.*):: (.*)/.exec(msg); var match = /^(.*): __mod_error(.*):: (.*)/.exec(msg);
var notices = []; var notices = [];
if (match && match.length === 4) { if (match && match.length === 4) {
notices.push(' in module ' + match[2]); notices.push(' in module ' + match[2]);
notices.push(' NOTICE: errors within modules cannot be backtraced yet.');
msg = match[3]; msg = match[3];
} }
console.error(c.colorize(msg, 'RED_BAR', 80)); console.error(c.colorize(msg, 'RED_BAR', 80));
@@ -217,8 +222,8 @@ Casper.prototype.back = function back() {
Casper.prototype.base64encode = function base64encode(url, method, data) { Casper.prototype.base64encode = function base64encode(url, method, data) {
"use strict"; "use strict";
return this.evaluate(function _evaluate(url, method, data) { return this.evaluate(function _evaluate(url, method, data) {
return window.__utils__.getBase64(url, method, data); return __utils__.getBase64(url, method, data);
}, { url: url, method: method, data: data }); }, url, method, data);
}; };
/** /**
@@ -382,7 +387,14 @@ Casper.prototype.clear = function clear() {
Casper.prototype.click = function click(selector) { Casper.prototype.click = function click(selector) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
return this.mouseEvent('click', selector); var success = this.mouseEvent('click', selector);
this.evaluate(function(selector) {
var element = __utils__.findOne(selector);
if (element) {
element.focus();
}
}, selector);
return success;
}; };
/** /**
@@ -402,6 +414,32 @@ Casper.prototype.clickLabel = function clickLabel(label, tag) {
return this.click(selector); return this.click(selector);
}; };
/**
* Configures HTTP authentication parameters. Will try parsing auth credentials from
* the passed location first, then check for configured settings if any.
*
* @param String location Requested url
* @param Object settings Request settings
* @return Casper
*/
Casper.prototype.configureHttpAuth = function configureHttpAuth(location, settings) {
"use strict";
var username, password, httpAuthMatch = location.match(/^https?:\/\/(.+):(.+)@/i);
this.checkStarted();
if (httpAuthMatch) {
this.page.settings.userName = httpAuthMatch[1];
this.page.settings.password = httpAuthMatch[2];
} else if (utils.isObject(settings) && settings.username) {
this.page.settings.userName = settings.username;
this.page.settings.password = settings.password;
} else {
return;
}
this.emit('http.auth', username, password);
this.log("Setting HTTP authentication for user " + username, "info");
return this;
};
/** /**
* Creates a step definition. * Creates a step definition.
* *
@@ -455,13 +493,13 @@ Casper.prototype.debugPage = function debugPage() {
*/ */
Casper.prototype.die = function die(message, status) { Casper.prototype.die = function die(message, status) {
"use strict"; "use strict";
this.checkStarted();
this.result.status = "error"; this.result.status = "error";
this.result.time = new Date().getTime() - this.startTime; this.result.time = new Date().getTime() - this.startTime;
if (!utils.isString(message) || !message.length) { if (!utils.isString(message) || !message.length) {
message = "Suite explicitely interrupted without any message given."; message = "Suite explicitely interrupted without any message given.";
} }
this.log(message, "error"); this.log(message, "error");
this.echo(message, "ERROR");
this.emit('die', message, status); this.emit('die', message, status);
if (utils.isFunction(this.options.onDie)) { if (utils.isFunction(this.options.onDie)) {
this.options.onDie.call(this, this, message, status); this.options.onDie.call(this, this, message, status);
@@ -549,13 +587,7 @@ Casper.prototype.echo = function echo(text, style, pad) {
* document.querySelector('#username').value = username; * document.querySelector('#username').value = username;
* document.querySelector('#password').value = password; * document.querySelector('#password').value = password;
* document.querySelector('#submit').click(); * document.querySelector('#submit').click();
* }, { * }, 'Bazoonga', 'baz00nga');
* username: 'Bazoonga',
* password: 'baz00nga'
* })
*
* FIXME: waiting for a patch of PhantomJS to allow direct passing of
* arguments to the function.
* *
* @param Function fn The function to be evaluated within current page DOM * @param Function fn The function to be evaluated within current page DOM
* @param Object context Object containing the parameters to inject into the function * @param Object context Object containing the parameters to inject into the function
@@ -565,15 +597,27 @@ Casper.prototype.echo = function echo(text, style, pad) {
Casper.prototype.evaluate = function evaluate(fn, context) { Casper.prototype.evaluate = function evaluate(fn, context) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
// preliminary checks
if (!utils.isFunction(fn) && !utils.isString(fn)) { // phantomjs allows functions defs as string
throw new CasperError("evaluate() only accepts functions or strings");
}
// ensure client utils are always injected // ensure client utils are always injected
this.injectClientUtils(); this.injectClientUtils();
// function context // function context
context = utils.isObject(context) ? context : {}; if (arguments.length === 1) {
// the way this works is kept for BC with older casperjs versions return this.page.evaluate(fn);
var args = Object.keys(context).map(function(arg) { } else if (arguments.length === 2) {
return context[arg]; // check for closure signature if it matches context
}); if (utils.isObject(context) && eval(fn).length === Object.keys(context).length) {
return this.page.evaluate.apply(this.page, [fn].concat(args)); context = utils.objectValues(context);
} else {
context = [context];
}
} else {
// phantomjs-style signature
context = [].slice.call(arguments).slice(1);
}
return this.page.evaluate.apply(this.page, [fn].concat(context));
}; };
/** /**
@@ -606,8 +650,8 @@ Casper.prototype.exists = function exists(selector) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
return this.evaluate(function _evaluate(selector) { return this.evaluate(function _evaluate(selector) {
return window.__utils__.exists(selector); return __utils__.exists(selector);
}, { selector: selector }); }, selector);
}; };
/** /**
@@ -633,8 +677,8 @@ Casper.prototype.fetchText = function fetchText(selector) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
return this.evaluate(function _evaluate(selector) { return this.evaluate(function _evaluate(selector) {
return window.__utils__.fetchText(selector); return __utils__.fetchText(selector);
}, { selector: selector }); }, selector);
}; };
/** /**
@@ -653,11 +697,8 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
} }
this.emit('fill', selector, vals, submit); this.emit('fill', selector, vals, submit);
var fillResults = this.evaluate(function _evaluate(selector, values) { var fillResults = this.evaluate(function _evaluate(selector, values) {
return window.__utils__.fill(selector, values); return __utils__.fill(selector, values);
}, { }, selector, vals);
selector: selector,
values: vals
});
if (!fillResults) { if (!fillResults) {
throw new CasperError("Unable to fill form"); throw new CasperError("Unable to fill form");
} else if (fillResults.errors.length > 0) { } else if (fillResults.errors.length > 0) {
@@ -681,17 +722,17 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
// Form submission? // Form submission?
if (submit) { if (submit) {
this.evaluate(function _evaluate(selector) { this.evaluate(function _evaluate(selector) {
var form = window.__utils__.findOne(selector); var form = __utils__.findOne(selector);
var method = (form.getAttribute('method') || "GET").toUpperCase(); var method = (form.getAttribute('method') || "GET").toUpperCase();
var action = form.getAttribute('action') || "unknown"; var action = form.getAttribute('action') || "unknown";
window.__utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info'); __utils__.log('submitting form to ' + action + ', HTTP ' + method, 'info');
if (typeof form.submit === "function") { if (typeof form.submit === "function") {
form.submit(); form.submit();
} else { } else {
// http://www.spiration.co.uk/post/1232/Submit-is-not-a-function // http://www.spiration.co.uk/post/1232/Submit-is-not-a-function
form.submit.click(); form.submit.click();
} }
}, { selector: selector }); }, selector);
} }
}; };
@@ -732,7 +773,7 @@ Casper.prototype.getPageContent = function getPageContent() {
this.checkStarted(); this.checkStarted();
var contentType = utils.getPropertyPath(this, 'currentResponse.contentType'); var contentType = utils.getPropertyPath(this, 'currentResponse.contentType');
if (!utils.isString(contentType)) { if (!utils.isString(contentType)) {
return this.page.content; return this.page.frameContent;
} }
// for some reason webkit/qtwebkit will always enclose body contents within html tags // for some reason webkit/qtwebkit will always enclose body contents within html tags
var sanitizedHtml = this.evaluate(function checkHtml() { var sanitizedHtml = this.evaluate(function checkHtml() {
@@ -742,7 +783,7 @@ Casper.prototype.getPageContent = function getPageContent() {
return __utils__.findOne('body pre').textContent.trim(); return __utils__.findOne('body pre').textContent.trim();
} }
}); });
return sanitizedHtml ? sanitizedHtml : this.page.content; return sanitizedHtml ? sanitizedHtml : this.page.frameContent;
}; };
/** /**
@@ -772,12 +813,13 @@ Casper.prototype.getCurrentUrl = function getCurrentUrl() {
* @param String attribute The attribute name to lookup * @param String attribute The attribute name to lookup
* @return String The requested DOM element attribute value * @return String The requested DOM element attribute value
*/ */
Casper.prototype.getElementAttribute = Casper.prototype.getElementAttr = function getElementAttr(selector, attribute) { Casper.prototype.getElementAttribute =
Casper.prototype.getElementAttr = function getElementAttr(selector, attribute) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
return this.evaluate(function _evaluate(selector, attribute) { return this.evaluate(function _evaluate(selector, attribute) {
return document.querySelector(selector).getAttribute(attribute); return document.querySelector(selector).getAttribute(attribute);
}, { selector: selector, attribute: attribute }); }, selector, attribute);
}; };
/** /**
@@ -793,14 +835,31 @@ Casper.prototype.getElementBounds = function getElementBounds(selector) {
throw new CasperError("No element matching selector found: " + selector); throw new CasperError("No element matching selector found: " + selector);
} }
var clipRect = this.evaluate(function _evaluate(selector) { var clipRect = this.evaluate(function _evaluate(selector) {
return window.__utils__.getElementBounds(selector); return __utils__.getElementBounds(selector);
}, { selector: selector }); }, selector);
if (!utils.isClipRect(clipRect)) { if (!utils.isClipRect(clipRect)) {
throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector); throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector);
} }
return clipRect; return clipRect;
}; };
/**
* Retrieves information about the node matching the provided selector.
*
* @param String|Objects selector CSS3/XPath selector
* @return Object
*/
Casper.prototype.getElementInfo = function getElementInfo(selector) {
"use strict";
this.checkStarted();
if (!this.exists(selector)) {
throw new CasperError(f("Cannot get informations from %s: element not found.", selector));
}
return this.evaluate(function(selector) {
return __utils__.getElementInfo(selector);
}, selector);
};
/** /**
* Retrieves boundaries for all the DOM elements matching the provided DOM CSS3/XPath selector. * Retrieves boundaries for all the DOM elements matching the provided DOM CSS3/XPath selector.
* *
@@ -814,8 +873,25 @@ Casper.prototype.getElementsBounds = function getElementBounds(selector) {
throw new CasperError("No element matching selector found: " + selector); throw new CasperError("No element matching selector found: " + selector);
} }
return this.evaluate(function _evaluate(selector) { return this.evaluate(function _evaluate(selector) {
return window.__utils__.getElementsBounds(selector); return __utils__.getElementsBounds(selector);
}, { selector: selector }); }, selector);
};
/**
* Retrieves a given form all of its field values.
*
* @param String selector A DOM CSS3/XPath selector
* @return Object
*/
Casper.prototype.getFormValues = function(selector) {
"use strict";
this.checkStarted();
if (!this.exists(selector)) {
throw new CasperError(f('Form matching selector "%s" not found', selector));
}
return this.evaluate(function(selector) {
return __utils__.getFormValues(selector);
}, selector);
}; };
/** /**
@@ -833,19 +909,17 @@ Casper.prototype.getGlobal = function getGlobal(name) {
result.value = JSON.stringify(window[name]); result.value = JSON.stringify(window[name]);
} catch (e) { } catch (e) {
var message = f("Unable to JSON encode window.%s: %s", name, e); var message = f("Unable to JSON encode window.%s: %s", name, e);
window.__utils__.log(message, "error"); __utils__.log(message, "error");
result.error = message; result.error = message;
} }
return result; return result;
}, {'name': name}); }, name);
if (typeof result !== "object") { if (!utils.isObject(result)) {
throw new CasperError(f('Could not retrieve global value for "%s"', name)); throw new CasperError(f('Could not retrieve global value for "%s"', name));
} else if ('error' in result) { } else if ('error' in result) {
throw new CasperError(result.error); throw new CasperError(result.error);
} else if (utils.isString(result.value)) { } else if (utils.isString(result.value)) {
return JSON.parse(result.value); return JSON.parse(result.value);
} else {
return undefined;
} }
}; };
@@ -861,7 +935,7 @@ Casper.prototype.getHTML = function getHTML(selector, outer) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
if (!selector) { if (!selector) {
return this.page.content; return this.page.frameContent;
} }
if (!this.exists(selector)) { if (!this.exists(selector)) {
throw new CasperError("No element matching selector found: " + selector); throw new CasperError("No element matching selector found: " + selector);
@@ -869,7 +943,7 @@ Casper.prototype.getHTML = function getHTML(selector, outer) {
return this.evaluate(function getSelectorHTML(selector, outer) { return this.evaluate(function getSelectorHTML(selector, outer) {
var element = __utils__.findOne(selector); var element = __utils__.findOne(selector);
return outer ? element.outerHTML : element.innerHTML; return outer ? element.outerHTML : element.innerHTML;
}, { selector: selector, outer: !!outer }); }, selector, !!outer);
}; };
/** /**
@@ -892,6 +966,7 @@ Casper.prototype.getTitle = function getTitle() {
*/ */
Casper.prototype.handleReceivedResource = function(resource) { Casper.prototype.handleReceivedResource = function(resource) {
"use strict"; "use strict";
/*jshint maxstatements:20*/
if (resource.stage !== "end") { if (resource.stage !== "end") {
return; return;
} }
@@ -902,6 +977,7 @@ Casper.prototype.handleReceivedResource = function(resource) {
this.currentHTTPStatus = null; this.currentHTTPStatus = null;
this.currentResponse = undefined; this.currentResponse = undefined;
if (utils.isHTTPResource(resource)) { if (utils.isHTTPResource(resource)) {
this.emit('page.resource.received', resource);
this.currentResponse = resource; this.currentResponse = resource;
this.currentHTTPStatus = resource.status; this.currentHTTPStatus = resource.status;
this.emit('http.status.' + resource.status, resource); this.emit('http.status.' + resource.status, resource);
@@ -931,7 +1007,7 @@ Casper.prototype.initErrorHandler = function initErrorHandler() {
}; };
/** /**
* Injects configured client scripts. * Injects configured local client scripts.
* *
* @return Casper * @return Casper
*/ */
@@ -965,7 +1041,7 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
var clientUtilsInjected = this.page.evaluate(function() { var clientUtilsInjected = this.page.evaluate(function() {
return typeof window.__utils__ === "object"; return typeof __utils__ === "object";
}); });
if (true === clientUtilsInjected) { if (true === clientUtilsInjected) {
return; return;
@@ -984,6 +1060,32 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
}.toString().replace('__options', JSON.stringify(this.options))); }.toString().replace('__options', JSON.stringify(this.options)));
}; };
/**
* Loads and include remote client scripts to current page.
*
* @return Casper
*/
Casper.prototype.includeRemoteScripts = function includeRemoteScripts() {
"use strict";
var numScripts = this.options.remoteScripts.length, loaded = 0;
if (numScripts === 0) {
return this;
}
this.waitStart();
this.options.remoteScripts.forEach(function(scriptUrl) {
this.log(f("Loading remote script: %s", scriptUrl), "debug");
this.page.includeJs(scriptUrl, function() {
loaded++;
this.log(f("Remote script %s loaded", scriptUrl), "debug");
if (loaded === numScripts) {
this.log("All remote scripts loaded.", "debug");
this.waitDone();
}
}.bind(this));
}.bind(this));
return this;
};
/** /**
* Logs a message. * Logs a message.
* *
@@ -1041,22 +1143,18 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
if (!this.exists(selector)) { if (!this.exists(selector)) {
throw new CasperError(f("Cannot dispatch %s event on nonexistent selector: %s", type, selector)); throw new CasperError(f("Cannot dispatch %s event on nonexistent selector: %s", type, selector));
} }
var eventSuccess = this.evaluate(function(type, selector) { if (this.evaluate(function(type, selector) {
return window.__utils__.mouseEvent(type, selector); return window.__utils__.mouseEvent(type, selector);
}, { }, type, selector)) {
type: type, return true;
selector: selector
});
if (!eventSuccess) {
// fallback onto native QtWebKit mouse events
try {
this.mouse.processEvent(type, selector);
} catch (e) {
this.log(f("Couldn't emulate '%s' event on %s: %s", type, selector, e), "error");
return false;
}
} }
return true; // fallback onto native QtWebKit mouse events
try {
return this.mouse.processEvent(type, selector);
} catch (e) {
this.log(f("Couldn't emulate '%s' event on %s: %s", type, selector, e), "error");
}
return false;
}; };
/** /**
@@ -1066,7 +1164,7 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
* *
* - String method: The HTTP method to use * - String method: The HTTP method to use
* - Object data: The data to use to perform the request, eg. {foo: 'bar'} * - Object data: The data to use to perform the request, eg. {foo: 'bar'}
* - Array headers: An array of request headers, eg. [{'Cache-Control': 'max-age=0'}] * - Object headers: Custom request headers object, eg. {'Cache-Control': 'max-age=0'}
* *
* @param String location The url to open * @param String location The url to open
* @param Object settings The request settings (optional) * @param Object settings The request settings (optional)
@@ -1074,16 +1172,12 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
*/ */
Casper.prototype.open = function open(location, settings) { Casper.prototype.open = function open(location, settings) {
"use strict"; "use strict";
/*jshint maxstatements:30*/
var baseCustomHeaders = this.page.customHeaders,
customHeaders = settings && settings.headers || {};
this.checkStarted(); this.checkStarted();
// settings validation settings = utils.isObject(settings) ? settings : {};
if (!settings) { settings.method = settings.method || "get";
settings = {
method: "get"
};
}
if (!utils.isObject(settings)) {
throw new CasperError("open(): request settings must be an Object");
}
// http method // http method
// taken from https://github.com/ariya/phantomjs/blob/master/src/webpage.cpp#L302 // taken from https://github.com/ariya/phantomjs/blob/master/src/webpage.cpp#L302
var methods = ["get", "head", "put", "post", "delete"]; var methods = ["get", "head", "put", "post", "delete"];
@@ -1101,28 +1195,21 @@ Casper.prototype.open = function open(location, settings) {
// clean location // clean location
location = utils.cleanUrl(location); location = utils.cleanUrl(location);
// current request url // current request url
this.configureHttpAuth(location, settings);
this.requestUrl = this.filter('open.location', location) || location; this.requestUrl = this.filter('open.location', location) || location;
// http auth
if (settings.username && settings.password) {
this.setHttpAuth(settings.username, settings.password);
} else {
var httpAuthMatch = location.match(/^https?:\/\/(.+):(.+)@/i);
if (httpAuthMatch) {
var httpAuth = {
username: httpAuthMatch[1],
password: httpAuthMatch[2]
};
this.setHttpAuth(httpAuth.username, httpAuth.password);
}
}
this.emit('open', this.requestUrl, settings); this.emit('open', this.requestUrl, settings);
this.log(f('opening url: %s, HTTP %s', this.requestUrl, settings.method.toUpperCase()), "debug"); this.log(f('opening url: %s, HTTP %s', this.requestUrl, settings.method.toUpperCase()), "debug");
// reset resources
this.resources = [];
// custom headers
this.page.customHeaders = utils.mergeObjects(utils.clone(baseCustomHeaders), customHeaders);
// perfom request
this.page.openUrl(this.requestUrl, { this.page.openUrl(this.requestUrl, {
operation: settings.method, operation: settings.method,
data: settings.data, data: settings.data
headers: settings.headers
}, this.page.settings); }, this.page.settings);
this.resources = []; // revert base custom headers
this.page.customHeaders = baseCustomHeaders;
return this; return this;
}; };
@@ -1203,8 +1290,7 @@ Casper.prototype.run = function run(onComplete, time) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
if (!this.steps || this.steps.length < 1) { if (!this.steps || this.steps.length < 1) {
this.log("No steps defined, aborting", "error"); throw new CasperError('No steps defined, aborting');
return this;
} }
this.log(f("Running suite: %d step%s", this.steps.length, this.steps.length > 1 ? "s" : ""), "info"); this.log(f("Running suite: %d step%s", this.steps.length, this.steps.length > 1 ? "s" : ""), "info");
this.emit('run.start'); this.emit('run.start');
@@ -1232,7 +1318,7 @@ Casper.prototype.runStep = function runStep(step) {
if ((self.test.currentSuiteNum + "-" + self.step) === stepNum) { if ((self.test.currentSuiteNum + "-" + self.step) === stepNum) {
self.emit('step.timeout'); self.emit('step.timeout');
if (utils.isFunction(self.options.onStepTimeout)) { if (utils.isFunction(self.options.onStepTimeout)) {
self.options.onStepTimeout.call(self, self.options.onStepTimeout, stepNum); self.options.onStepTimeout.call(self, self.options.stepTimeout, stepNum);
} }
} }
clearInterval(stepTimeoutCheckInterval); clearInterval(stepTimeoutCheckInterval);
@@ -1251,22 +1337,53 @@ Casper.prototype.runStep = function runStep(step) {
}; };
/** /**
* Sets HTTP authentication parameters. * Sends keys to given element.
* *
* @param String username The HTTP_AUTH_USER value * @param String selector A DOM CSS3 compatible selector
* @param String password The HTTP_AUTH_PW value * @param String keys A string representing the sequence of char codes to send
* @param Object options Options
* @return Casper
*/
Casper.prototype.sendKeys = function(selector, keys, options) {
"use strict";
this.checkStarted();
options = utils.mergeObjects({
eventType: 'keypress'
}, options || {});
var elemInfos = this.getElementInfo(selector),
tag = elemInfos.nodeName.toLowerCase(),
type = utils.getPropertyPath(elemInfos, 'attributes.type'),
supported = ["color", "date", "datetime", "datetime-local", "email",
"hidden", "month", "number", "password", "range", "search",
"tel", "text", "time", "url", "week"];
var isTextInput = false;
if (tag === 'textarea' || (tag === 'input' && supported.indexOf(type) !== -1)) {
// clicking on the input element brings it focus
isTextInput = true;
this.click(selector);
}
this.page.sendEvent(options.eventType, keys);
if (isTextInput) {
// remove the focus
this.evaluate(function(selector) {
__utils__.findOne(selector).blur();
}, selector);
}
return this;
};
/**
* Sets current WebPage instance the credentials for HTTP authentication.
*
* @param String username
* @param String password
* @return Casper * @return Casper
*/ */
Casper.prototype.setHttpAuth = function setHttpAuth(username, password) { Casper.prototype.setHttpAuth = function setHttpAuth(username, password) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
if (!utils.isString(username) || !utils.isString(password)) {
throw new CasperError("Both username and password must be strings");
}
this.page.settings.userName = username; this.page.settings.userName = username;
this.page.settings.password = password; this.page.settings.password = password;
this.emit('http.auth', username, password);
this.log("Setting HTTP authentication for user " + username, "info");
return this; return this;
}; };
@@ -1279,10 +1396,12 @@ Casper.prototype.setHttpAuth = function setHttpAuth(username, password) {
*/ */
Casper.prototype.start = function start(location, then) { Casper.prototype.start = function start(location, then) {
"use strict"; "use strict";
/*jshint maxstatements:30*/
this.emit('starting'); this.emit('starting');
this.log('Starting...', "info"); this.log('Starting...', "info");
this.startTime = new Date().getTime(); this.startTime = new Date().getTime();
this.history = []; this.history = [];
this.popups = pagestack.create();
this.steps = []; this.steps = [];
this.step = 0; this.step = 0;
// Option checks // Option checks
@@ -1290,13 +1409,8 @@ Casper.prototype.start = function start(location, then) {
this.log(f("Unknown log level '%d', defaulting to 'warning'", this.options.logLevel), "warning"); this.log(f("Unknown log level '%d', defaulting to 'warning'", this.options.logLevel), "warning");
this.options.logLevel = "warning"; this.options.logLevel = "warning";
} }
// WebPage
if (!utils.isWebPage(this.page)) { if (!utils.isWebPage(this.page)) {
if (utils.isWebPage(this.options.page)) { this.page = this.mainPage = utils.isWebPage(this.options.page) ? this.options.page : createPage(this);
this.page = this.options.page;
} else {
this.page = createPage(this);
}
} }
this.page.settings = utils.mergeObjects(this.page.settings, this.options.pageSettings); this.page.settings = utils.mergeObjects(this.page.settings, this.options.pageSettings);
if (utils.isClipRect(this.options.clipRect)) { if (utils.isClipRect(this.options.clipRect)) {
@@ -1305,8 +1419,7 @@ Casper.prototype.start = function start(location, then) {
if (utils.isObject(this.options.viewportSize)) { if (utils.isObject(this.options.viewportSize)) {
this.page.viewportSize = this.options.viewportSize; this.page.viewportSize = this.options.viewportSize;
} }
this.started = true; // timeout handling
this.emit('started');
if (utils.isNumber(this.options.timeout) && this.options.timeout > 0) { if (utils.isNumber(this.options.timeout) && this.options.timeout > 0) {
this.log(f("Execution timeout set to %dms", this.options.timeout), "info"); this.log(f("Execution timeout set to %dms", this.options.timeout), "info");
setTimeout(function _check(self) { setTimeout(function _check(self) {
@@ -1316,10 +1429,12 @@ Casper.prototype.start = function start(location, then) {
} }
}, this.options.timeout, this); }, this.options.timeout, this);
} }
this.started = true;
this.emit('started');
if (utils.isString(location) && location.length > 0) { if (utils.isString(location) && location.length > 0) {
return this.thenOpen(location, utils.isFunction(then) ? then : this.createStep(function _step() { return this.thenOpen(location, utils.isFunction(then) ? then : this.createStep(function _step() {
this.log("start page is loaded", "debug"); this.log("start page is loaded", "debug");
})); }, {skipLog: true}));
} }
return this; return this;
}; };
@@ -1409,8 +1524,9 @@ Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref)
Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) { Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
var args = [fn].concat([].slice.call(arguments, 1));
return this.then(function _step() { return this.then(function _step() {
this.evaluate(fn, context); this.evaluate.apply(this, args);
}); });
}; };
@@ -1472,8 +1588,10 @@ Casper.prototype.toString = function toString() {
*/ */
Casper.prototype.userAgent = function userAgent(agent) { Casper.prototype.userAgent = function userAgent(agent) {
"use strict"; "use strict";
this.checkStarted(); this.options.pageSettings.userAgent = agent;
this.options.pageSettings.userAgent = this.page.settings.userAgent = agent; if (this.started && this.page) {
this.page.settings.userAgent = agent;
}
return this; return this;
}; };
@@ -1510,8 +1628,8 @@ Casper.prototype.visible = function visible(selector) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
return this.evaluate(function _evaluate(selector) { return this.evaluate(function _evaluate(selector) {
return window.__utils__.visible(selector); return __utils__.visible(selector);
}, { selector: selector }); }, selector);
}; };
/** /**
@@ -1617,6 +1735,28 @@ Casper.prototype.waitFor = function waitFor(testFx, then, onTimeout, timeout) {
}); });
}; };
/**
* Waits for a popup page having its url matching the provided pattern to be opened
* and loaded.
*
* @param String|RegExp urlPattern The popup url pattern
* @param Function then The next step function (optional)
* @param Function onTimeout Function to call on operation timeout (optional)
* @param Number timeout Timeout in milliseconds (optional)
* @return Casper
*/
Casper.prototype.waitForPopup = function waitForPopup(urlPattern, then, onTimeout, timeout) {
"use strict";
return this.waitFor(function() {
try {
this.popups.find(urlPattern);
return true;
} catch (e) {
return false;
}
}, then, onTimeout, timeout);
};
/** /**
* Waits until a given resource is loaded * Waits until a given resource is loaded
* *
@@ -1656,20 +1796,24 @@ Casper.prototype.waitForSelector = function waitForSelector(selector, then, onTi
}; };
/** /**
* Waits until the page contains given HTML text. * Waits until the page contains given HTML text or matches a given RegExp.
* *
* @param String text Text to wait for * @param String|RegExp pattern Text or RegExp to wait for
* @param Function then The next step to perform (optional) * @param Function then The next step to perform (optional)
* @param Function onTimeout A callback function to call on timeout (optional) * @param Function onTimeout A callback function to call on timeout (optional)
* @param Number timeout The max amount of time to wait, in milliseconds (optional) * @param Number timeout The max amount of time to wait, in milliseconds (optional)
* @return Casper * @return Casper
*/ */
Casper.prototype.waitForText = function(text, then, onTimeout, timeout) { Casper.prototype.waitForText = function(pattern, then, onTimeout, timeout) {
"use strict"; "use strict";
this.checkStarted(); this.checkStarted();
timeout = timeout ? timeout : this.options.waitTimeout; timeout = timeout ? timeout : this.options.waitTimeout;
return this.waitFor(function _check() { return this.waitFor(function _check() {
return this.getPageContent().indexOf(text) !== -1; var content = this.getPageContent();
if (utils.isRegExp(pattern)) {
return pattern.test(content);
}
return content.indexOf(pattern) !== -1;
}, then, onTimeout, timeout); }, then, onTimeout, timeout);
}; };
@@ -1730,6 +1874,73 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on
}, then, onTimeout, timeout); }, then, onTimeout, timeout);
}; };
/**
* Makes the provided frame page as the currently active one. Note that the
* active page will be reverted when finished.
*
* @param String|Number frameInfo Target frame name or number
* @param Function then Next step function
* @return Casper
*/
Casper.prototype.withFrame = function withFrame(frameInfo, then) {
"use strict";
this.then(function _step() {
if (utils.isNumber(frameInfo)) {
if (frameInfo > this.page.childFramesCount() - 1) {
throw new CasperError(f('Frame number "%d" is out of bounds.', frameInfo));
}
} else if (this.page.childFramesName().indexOf(frameInfo) === -1) {
throw new CasperError(f('No frame named "%s" was found.', frameInfo));
}
// make the frame page the currently active one
this.page.switchToChildFrame(frameInfo);
});
try {
this.then(then);
} catch (e) {
// revert to main page on error
this.warn("Error while processing frame step: " + e);
this.page.switchToMainFrame();
throw e;
}
return this.then(function _step() {
// revert to main page
this.page.switchToMainFrame();
});
};
/**
* Makes the provided frame page as the currently active one. Note that the
* active page will be reverted when finished.
*
* @param String|RegExp|WebPage popup Target frame page information
* @param Function then Next step function
* @return Casper
*/
Casper.prototype.withPopup = function withPopup(popupInfo, then) {
"use strict";
this.then(function _step() {
var popupPage = this.popups.find(popupInfo);
if (!utils.isFunction(then)) {
throw new CasperError("withPopup() requires a step function.");
}
// make the popup page the currently active one
this.page = popupPage;
});
try {
this.then(then);
} catch (e) {
// revert to main page on error
this.log("error while processing popup step: " + e, "error");
this.page = this.mainPage;
throw e;
}
return this.then(function _step() {
// revert to main page
this.page = this.mainPage;
});
};
/** /**
* Changes the current page zoom factor. * Changes the current page zoom factor.
* *
@@ -1771,6 +1982,7 @@ exports.Casper = Casper;
* @return WebPage * @return WebPage
*/ */
function createPage(casper) { function createPage(casper) {
/*jshint maxstatements:20*/
"use strict"; "use strict";
var page = require('webpage').create(); var page = require('webpage').create();
page.onAlert = function onAlert(message) { page.onAlert = function onAlert(message) {
@@ -1781,17 +1993,21 @@ function createPage(casper) {
} }
}; };
page.onConfirm = function onConfirm(message) { page.onConfirm = function onConfirm(message) {
return casper.filter('page.confirm', message) || true; if ('page.confirm' in casper._filters) {
return casper.filter('page.confirm', message);
}
return true;
}; };
page.onConsoleMessage = function onConsoleMessage(msg) { page.onConsoleMessage = function onConsoleMessage(msg) {
// client utils casper console message // client utils casper console message
var consoleTest = /^\[casper\.echo\]\s?(.*)/.exec(msg); var consoleTest = /^\[casper\.echo\]\s?([\s\S]*)/.exec(msg);
if (consoleTest && consoleTest.length === 2) { if (consoleTest && consoleTest.length === 2) {
casper.echo(consoleTest[1]); casper.echo(consoleTest[1]);
return; // don't trigger remote.message event for these return; // don't trigger remote.message event for these
} }
// client utils log messages // client utils log messages
var logLevel = "info", logTest = /^\[casper:(\w+)\]\s?(.*)/.exec(msg); var logLevel = "info",
logTest = /^\[casper:(\w+)\]\s?([\s\S]*)/m.exec(msg);
if (logTest && logTest.length === 3) { if (logTest && logTest.length === 3) {
logLevel = logTest[1]; logLevel = logTest[1];
msg = logTest[2]; msg = logTest[2];
@@ -1803,9 +2019,9 @@ function createPage(casper) {
casper.emit('page.error', msg, trace); casper.emit('page.error', msg, trace);
}; };
page.onInitialized = function onInitialized() { page.onInitialized = function onInitialized() {
casper.emit('page.initialized', this); casper.emit('page.initialized', page);
if (utils.isFunction(casper.options.onPageInitialized)) { if (utils.isFunction(casper.options.onPageInitialized)) {
this.log("Post-configuring WebPage instance", "debug"); casper.log("Post-configuring WebPage instance", "debug");
casper.options.onPageInitialized.call(casper, page); casper.options.onPageInitialized.call(casper, page);
} }
}; };
@@ -1814,6 +2030,7 @@ function createPage(casper) {
casper.emit('load.started'); casper.emit('load.started');
}; };
page.onLoadFinished = function onLoadFinished(status) { page.onLoadFinished = function onLoadFinished(status) {
/*jshint maxstatements:20*/
if (status !== "success") { if (status !== "success") {
casper.emit('load.failed', { casper.emit('load.failed', {
status: status, status: status,
@@ -1831,7 +2048,10 @@ function createPage(casper) {
casper.options.onLoadError.call(casper, casper, casper.requestUrl, status); casper.options.onLoadError.call(casper, casper, casper.requestUrl, status);
} }
} }
// local client scripts
casper.injectClientScripts(); casper.injectClientScripts();
// remote client scripts
casper.includeRemoteScripts();
// Client-side utils injection // Client-side utils injection
casper.injectClientUtils(); casper.injectClientUtils();
// history // history
@@ -1847,6 +2067,17 @@ function createPage(casper) {
} }
casper.emit('navigation.requested', url, navigationType, navigationLocked, isMainFrame); casper.emit('navigation.requested', url, navigationType, navigationLocked, isMainFrame);
}; };
page.onPageCreated = function onPageCreated(popupPage) {
casper.emit('popup.created', popupPage);
popupPage.onLoadFinished = function onLoadFinished() {
casper.popups.push(popupPage);
casper.emit('popup.loaded', popupPage);
};
popupPage.onClosing = function onClosing(closedPopup) {
casper.popups.clean(closedPopup);
casper.emit('popup.closed', closedPopup);
};
};
page.onPrompt = function onPrompt(message, value) { page.onPrompt = function onPrompt(message, value) {
return casper.filter('page.prompt', message, value); return casper.filter('page.prompt', message, value);
}; };
@@ -1860,6 +2091,9 @@ function createPage(casper) {
}; };
page.onResourceRequested = function onResourceRequested(request) { page.onResourceRequested = function onResourceRequested(request) {
casper.emit('resource.requested', request); casper.emit('resource.requested', request);
if (request.url === casper.requestUrl) {
casper.emit('page.resource.requested', request);
}
if (utils.isFunction(casper.options.onResourceRequested)) { if (utils.isFunction(casper.options.onResourceRequested)) {
casper.options.onResourceRequested.call(casper, casper, request); casper.options.onResourceRequested.call(casper, casper, request);
} }

View File

@@ -41,6 +41,7 @@
* Casper client-side helpers. * Casper client-side helpers.
*/ */
exports.ClientUtils = function ClientUtils(options) { exports.ClientUtils = function ClientUtils(options) {
/*jshint maxstatements:40*/
// private members // private members
var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var BASE64_DECODE_CHARS = new Array( var BASE64_DECODE_CHARS = new Array(
@@ -200,6 +201,7 @@
* @return Object An object containing setting result for each field, including file uploads * @return Object An object containing setting result for each field, including file uploads
*/ */
this.fill = function fill(form, vals) { this.fill = function fill(form, vals) {
/*jshint maxcomplexity:8*/
var out = { var out = {
errors: [], errors: [],
fields: [], fields: [],
@@ -260,7 +262,7 @@
try { try {
var pSelector = this.processSelector(selector); var pSelector = this.processSelector(selector);
if (pSelector.type === 'xpath') { if (pSelector.type === 'xpath') {
return this.getElementsByXPath(pSelector.path); return this.getElementsByXPath(pSelector.path, scope);
} else { } else {
return scope.querySelectorAll(pSelector.path); return scope.querySelectorAll(pSelector.path);
} }
@@ -281,7 +283,7 @@
try { try {
var pSelector = this.processSelector(selector); var pSelector = this.processSelector(selector);
if (pSelector.type === 'xpath') { if (pSelector.type === 'xpath') {
return this.getElementByXPath(pSelector.path); return this.getElementByXPath(pSelector.path, scope);
} else { } else {
return scope.querySelector(pSelector.path); return scope.querySelector(pSelector.path);
} }
@@ -390,14 +392,43 @@
} }
}; };
/**
* Retrieves information about the node matching the provided selector.
*
* @param String|Object selector CSS3/XPath selector
* @return Object
*/
this.getElementInfo = function getElementInfo(selector) {
var element = this.findOne(selector);
var bounds = this.getElementBounds(selector);
var attributes = {};
[].forEach.call(element.attributes, function(attr) {
attributes[attr.name.toLowerCase()] = attr.value;
});
return {
nodeName: element.nodeName.toLowerCase(),
attributes: attributes,
tag: element.outerHTML,
html: element.innerHTML,
text: element.innerText,
x: bounds.left,
y: bounds.top,
width: bounds.width,
height: bounds.height,
visible: this.visible(selector)
};
};
/** /**
* Retrieves a single DOM element matching a given XPath expression. * Retrieves a single DOM element matching a given XPath expression.
* *
* @param String expression The XPath expression * @param String expression The XPath expression
* @param HTMLElement|null scope Element to search child elements within
* @return HTMLElement or null * @return HTMLElement or null
*/ */
this.getElementByXPath = function getElementByXPath(expression) { this.getElementByXPath = function getElementByXPath(expression, scope) {
var a = document.evaluate(expression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); scope = scope || this.options.scope;
var a = document.evaluate(expression, scope, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (a.snapshotLength > 0) { if (a.snapshotLength > 0) {
return a.snapshotItem(0); return a.snapshotItem(0);
} }
@@ -406,12 +437,14 @@
/** /**
* Retrieves all DOM elements matching a given XPath expression. * Retrieves all DOM elements matching a given XPath expression.
* *
* @param String expression The XPath expression * @param String expression The XPath expression
* @param HTMLElement|null scope Element to search child elements within
* @return Array * @return Array
*/ */
this.getElementsByXPath = function getElementsByXPath(expression) { this.getElementsByXPath = function getElementsByXPath(expression, scope) {
scope = scope || this.options.scope;
var nodes = []; var nodes = [];
var a = document.evaluate(expression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var a = document.evaluate(expression, scope, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for (var i = 0; i < a.snapshotLength; i++) { for (var i = 0; i < a.snapshotLength; i++) {
nodes.push(a.snapshotItem(i)); nodes.push(a.snapshotItem(i));
} }
@@ -445,7 +478,7 @@
if (type === 'radio') { if (type === 'radio') {
var value; var value;
[].forEach.call(inputs, function(radio) { [].forEach.call(inputs, function(radio) {
value = radio.checked ? radio.value : undefined; value = radio.checked ? radio.value : value;
}); });
return value; return value;
} else if (type === 'checkbox') { } else if (type === 'checkbox') {
@@ -466,6 +499,25 @@
} }
}; };
/**
* Retrieves a given form all of its field values.
*
* @param String selector A DOM CSS3/XPath selector
* @return Object
*/
this.getFormValues = function getFormValues(selector) {
var form = this.findOne(selector);
var values = {};
var self = this;
[].forEach.call(form.elements, function(element) {
var name = element.getAttribute('name');
if (name && !values[name]) {
values[name] = self.getFieldValue(name);
}
});
return values;
};
/** /**
* Logs a message. Will format the message a way CasperJS will be able * Logs a message. Will format the message a way CasperJS will be able
* to log phantomjs side. * to log phantomjs side.
@@ -569,24 +621,21 @@
* Performs an AJAX request. * Performs an AJAX request.
* *
* @param String url Url. * @param String url Url.
* @param String method HTTP method. * @param String method HTTP method (default: GET).
* @param Object data Request parameters. * @param Object data Request parameters.
* @param Boolean async Asynchroneous request? (default: false) * @param Boolean async Asynchroneous request? (default: false)
* @return String Response text. * @return String Response text.
*/ */
this.sendAJAX = function sendAJAX(url, method, data, async) { this.sendAJAX = function sendAJAX(url, method, data, async) {
var xhr = new XMLHttpRequest(), dataString = ""; var xhr = new XMLHttpRequest(),
if (typeof method !== "string" || ["GET", "POST"].indexOf(method.toUpperCase()) === -1) { dataString = "",
method = "GET"; dataList = [];
} else { method = method && method.toUpperCase() || "GET";
method = method.toUpperCase();
}
xhr.open(method, url, !!async); xhr.open(method, url, !!async);
this.log("getBinary(): Using HTTP method: '" + method + "'", "debug"); this.log("sendAJAX(): Using HTTP method: '" + method + "'", "debug");
xhr.overrideMimeType("text/plain; charset=x-user-defined"); xhr.overrideMimeType("text/plain; charset=x-user-defined");
if (method === "POST") { if (method === "POST") {
if (typeof data === "object") { if (typeof data === "object") {
var dataList = [];
for (var k in data) { for (var k in data) {
dataList.push(encodeURIComponent(k) + "=" + encodeURIComponent(data[k].toString())); dataList.push(encodeURIComponent(k) + "=" + encodeURIComponent(data[k].toString()));
} }
@@ -595,7 +644,7 @@
} else if (typeof data === "string") { } else if (typeof data === "string") {
dataString = data; dataString = data;
} }
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
} }
xhr.send(method === "POST" ? dataString : null); xhr.send(method === "POST" ? dataString : null);
return xhr.responseText; return xhr.responseText;
@@ -609,6 +658,7 @@
* @param mixed value The field value to set * @param mixed value The field value to set
*/ */
this.setField = function setField(field, value) { this.setField = function setField(field, value) {
/*jshint maxcomplexity:99 */
var logValue, fields, out; var logValue, fields, out;
value = logValue = (value || ""); value = logValue = (value || "");
if (field instanceof NodeList) { if (field instanceof NodeList) {
@@ -694,10 +744,14 @@
out = 'Unsupported field type: ' + nodeName; out = 'Unsupported field type: ' + nodeName;
break; break;
} }
// firing the `change` event
var changeEvent = document.createEvent("HTMLEvents"); // firing the `change` and `input` events
changeEvent.initEvent('change', true, true); ['change', 'input'].forEach(function(name) {
field.dispatchEvent(changeEvent); var event = document.createEvent("HTMLEvents");
event.initEvent(name, true, true);
field.dispatchEvent(event);
});
// blur the field // blur the field
try { try {
field.blur(); field.blur();

View File

@@ -64,7 +64,8 @@ var Colorizer = function Colorizer() {
'WARNING': { fg: 'red', bold: true }, 'WARNING': { fg: 'red', bold: true },
'GREEN_BAR': { fg: 'white', bg: 'green', bold: true }, 'GREEN_BAR': { fg: 'white', bg: 'green', bold: true },
'RED_BAR': { fg: 'white', bg: 'red', bold: true }, 'RED_BAR': { fg: 'white', bg: 'red', bold: true },
'INFO_BAR': { bg: 'cyan', fg: 'white', bold: true } 'INFO_BAR': { bg: 'cyan', fg: 'white', bold: true },
'WARN_BAR': { bg: 'yellow', fg: 'white', bold: true }
}; };
/** /**

View File

@@ -19,6 +19,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE. // USE OR OTHER DEALINGS IN THE SOFTWARE.
/*global CasperError*/
var isArray = Array.isArray; var isArray = Array.isArray;
function EventEmitter() { function EventEmitter() {
@@ -230,6 +232,17 @@ EventEmitter.prototype.filter = function() {
return filter.apply(this, Array.prototype.splice.call(arguments, 1)); return filter.apply(this, Array.prototype.splice.call(arguments, 1));
}; };
EventEmitter.prototype.removeAllFilters = function(type) {
if (arguments.length === 0) {
this._filters = {};
return this;
}
if (type && this._filters && this._filters[type]) {
this._filters[type] = null;
}
return this;
};
EventEmitter.prototype.setFilter = function(type, filterFn) { EventEmitter.prototype.setFilter = function(type, filterFn) {
if (!this._filters) { if (!this._filters) {
this._filters = {}; this._filters = {};

View File

@@ -62,6 +62,7 @@ responseHeaders.prototype.get = function get(name){
*/ */
exports.augmentResponse = function(response) { exports.augmentResponse = function(response) {
"use strict"; "use strict";
/*jshint proto:true*/
if (!utils.isHTTPResource(response)) { if (!utils.isHTTPResource(response)) {
return; return;
} }

View File

@@ -1,97 +0,0 @@
/*!
* Casper is a navigation utility for PhantomJS.
*
* Documentation: http://casperjs.org/
* Repository: http://github.com/n1k0/casperjs
*
* Copyright (c) 2011-2012 Nicolas Perriault
*
* Part of source code is Copyright Joyent, Inc. and other Node contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
/*global CasperError console encodeURIComponent escape exports require*/
// WARNING: this module is deprecated since CasperJS 1.0.0-RC3
var utils = require('utils');
exports.create = function create(fn) {
"use strict";
return new FunctionArgsInjector(fn);
};
/**
* Function argument injector.
*
* FIXME: use new Function() instead of eval()
*/
var FunctionArgsInjector = function FunctionArgsInjector(fn) {
"use strict";
console.error('Warning: the injector module has been deprecated.');
if (!utils.isFunction(fn)) {
throw new CasperError("FunctionArgsInjector() can only process functions");
}
this.fn = fn;
this.extract = function extract(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 _map(arg) {
return arg.replace(new RegExp(/\/\*+.*\*\//ig), "").trim();
}).filter(function _filter(arg) {
return arg;
}) || [];
return {
name: match[1] ? match[1].trim() : null,
args: args,
body: match[3] ? match[3].trim() : ''
};
}
};
this.process = function process(values) {
var fnObj = this.extract(this.fn);
if (!utils.isObject(fnObj)) {
throw new CasperError("Unable to process function " + this.fn.toString());
}
var inject = this.getArgsInjectionString(fnObj.args, values);
var newFn = new Function([inject, fnObj.body].join('\n'));
newFn.name = fnObj.name || '';
return newFn;
};
this.getArgsInjectionString = function getArgsInjectionString(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 _forEach(arg) {
if (arg in values) {
inject.push('var ' + arg + '=__casper_params__["' + arg + '"];');
}
});
return inject.join('\n') + '\n';
};
};
exports.FunctionArgsInjector = FunctionArgsInjector;

View File

@@ -43,11 +43,13 @@ var Mouse = function Mouse(casper) {
throw new CasperError('Mouse() needs a Casper instance'); throw new CasperError('Mouse() needs a Casper instance');
} }
var slice = Array.prototype.slice; var slice = Array.prototype.slice,
nativeEvents = ['mouseup', 'mousedown', 'click', 'mousemove'];
var nativeEvents = ['mouseup', 'mousedown', 'click', 'mousemove']; if (utils.gteVersion(phantom.version, '1.8.0')) {
var emulatedEvents = ['mouseover', 'mouseout']; nativeEvents.push('doubleclick');
var supportedEvents = nativeEvents.concat(emulatedEvents); }
var emulatedEvents = ['mouseover', 'mouseout'],
supportedEvents = nativeEvents.concat(emulatedEvents);
function computeCenter(selector) { function computeCenter(selector) {
var bounds = casper.getElementBounds(selector); var bounds = casper.getElementBounds(selector);
@@ -72,8 +74,7 @@ var Mouse = function Mouse(casper) {
throw new CasperError('Mouse.processEvent(): Too few arguments'); throw new CasperError('Mouse.processEvent(): Too few arguments');
case 1: case 1:
// selector // selector
var selector = args[0]; casper.page.sendEvent.apply(casper.page, [type].concat(computeCenter(args[0])));
casper.page.sendEvent.apply(casper.page, [type].concat(computeCenter(selector)));
break; break;
case 2: case 2:
// coordinates // coordinates
@@ -95,6 +96,10 @@ var Mouse = function Mouse(casper) {
processEvent('click', arguments); processEvent('click', arguments);
}; };
this.doubleclick = function doubleclick() {
processEvent('doubleclick', arguments);
};
this.down = function down() { this.down = function down() {
processEvent('mousedown', arguments); processEvent('mousedown', arguments);
}; };

View File

@@ -0,0 +1,166 @@
/*!
* Casper is a navigation utility for PhantomJS.
*
* Documentation: http://casperjs.org/
* Repository: http://github.com/n1k0/casperjs
*
* Copyright (c) 2011-2012 Nicolas Perriault
*
* Part of source code is Copyright Joyent, Inc. and other Node contributors.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
*/
/*global CasperError console exports phantom require*/
var utils = require('utils');
var f = utils.format;
function create() {
"use strict";
return new Stack();
}
exports.create = create;
/**
* Popups container. Implements Array prototype.
*
*/
var Stack = function Stack(){};
exports.Stack = Stack;
Stack.prototype = [];
/**
* Cleans the stack from closed popup.
*
* @param WebPage closed Closed popup page instance
* @return Number New stack length
*/
Stack.prototype.clean = function clean(closed) {
"use strict";
var closedIndex = -1;
this.forEach(function(popup, index) {
if (closed === popup) {
closedIndex = index;
}
});
if (closedIndex > -1) {
this.splice(closedIndex, 1);
}
return this.length;
};
/**
* Finds a popup matching the provided information. Information can be:
*
* - RegExp: matching page url
* - String: strict page url value
* - WebPage: a direct WebPage instance
*
* @param Mixed popupInfo
* @return WebPage
*/
Stack.prototype.find = function find(popupInfo) {
"use strict";
var popup, type = utils.betterTypeOf(popupInfo);
switch (type) {
case "regexp":
popup = this.findByRegExp(popupInfo);
break;
case "string":
popup = this.findByURL(popupInfo);
break;
case "qtruntimeobject": // WebPage
popup = popupInfo;
if (!utils.isWebPage(popup) || !this.some(function(popupPage) {
if (popupInfo.id && popupPage.id) {
return popupPage.id === popup.id;
}
return popupPage.url === popup.url;
})) {
throw new CasperError("Invalid or missing popup.");
}
break;
default:
throw new CasperError(f("Invalid popupInfo type: %s.", type));
}
return popup;
};
/**
* Finds the first popup which url matches a given RegExp.
*
* @param RegExp regexp
* @return WebPage
*/
Stack.prototype.findByRegExp = function findByRegExp(regexp) {
"use strict";
var popup = this.filter(function(popupPage) {
return regexp.test(popupPage.url);
})[0];
if (!popup) {
throw new CasperError(f("Couldn't find popup with url matching pattern %s", regexp));
}
return popup;
};
/**
* Finds the first popup matching a given url.
*
* @param String url The child WebPage url
* @return WebPage
*/
Stack.prototype.findByURL = function findByURL(string) {
"use strict";
var popup = this.filter(function(popupPage) {
return popupPage.url.indexOf(string) !== -1;
})[0];
if (!popup) {
throw new CasperError(f("Couldn't find popup with url containing '%s'", string));
}
return popup;
};
/**
* Returns a human readable list of current active popup urls.
*
* @return Array Mapped stack.
*/
Stack.prototype.list = function list() {
"use strict";
return this.map(function(popup) {
try {
return popup.url;
} catch (e) {
return '<deleted>';
}
});
};
/**
* String representation of current instance.
*
* @return String
*/
Stack.prototype.toString = function toString() {
"use strict";
return f("[Object Stack], having %d popup(s)" % this.length);
};

View File

@@ -48,13 +48,20 @@ exports.create = function create(casper, options) {
*/ */
var Tester = function Tester(casper, options) { var Tester = function Tester(casper, options) {
"use strict"; "use strict";
/*jshint maxstatements:30*/
if (!utils.isCasperObject(casper)) { if (!utils.isCasperObject(casper)) {
throw new CasperError("Tester needs a Casper instance"); throw new CasperError("Tester needs a Casper instance");
} }
var self = this;
this.casper = casper; this.casper = casper;
this.SKIP_MESSAGE = '__termination__';
this.aborted = false;
this.executed = 0;
this.currentTestFile = null; this.currentTestFile = null;
this.currentSuiteNum = 0; this.currentSuiteNum = 0;
this.exporter = require('xunit').create(); this.exporter = require('xunit').create();
@@ -66,9 +73,11 @@ var Tester = function Tester(casper, options) {
this.running = false; this.running = false;
this.suites = []; this.suites = [];
this.options = utils.mergeObjects({ this.options = utils.mergeObjects({
failFast: false, // terminates a suite as soon as a test fails?
failText: "FAIL", // text to use for a successful test failText: "FAIL", // text to use for a successful test
passText: "PASS", // text to use for a failed test passText: "PASS", // text to use for a failed test
pad: 80 // maximum number of chars for a result line pad: 80 , // maximum number of chars for a result line
warnText: "WARN" // text to use for a dubious test
}, options); }, options);
// properties // properties
@@ -76,25 +85,39 @@ var Tester = function Tester(casper, options) {
passed: 0, passed: 0,
failed: 0, failed: 0,
passes: [], passes: [],
failures: [] failures: [],
passesTime: [],
failuresTime: []
}; };
// measuring test duration
this.currentTestStartTime = new Date();
this.lastAssertTime = 0;
this.configure(); this.configure();
this.on('success', function onSuccess(success) { this.on('success', function onSuccess(success) {
this.testResults.passes.push(success); this.testResults.passes.push(success);
this.exporter.addSuccess(fs.absolute(success.file), success.message || success.standard); var timeElapsed = new Date() - this.currentTestStartTime;
this.testResults.passesTime.push(timeElapsed - this.lastAssertTime);
this.exporter.addSuccess(fs.absolute(success.file), success.message || success.standard, timeElapsed - this.lastAssertTime);
this.lastAssertTime = timeElapsed;
}); });
this.on('fail', function onFail(failure) { this.on('fail', function onFail(failure) {
// export // export
var timeElapsed = new Date() - this.currentTestStartTime;
this.testResults.failuresTime.push(timeElapsed - this.lastAssertTime);
this.exporter.addFailure( this.exporter.addFailure(
fs.absolute(failure.file), fs.absolute(failure.file),
failure.message || failure.standard, failure.message || failure.standard,
failure.standard || "test failed", failure.standard || "test failed",
failure.type || "unknown" failure.type || "unknown",
(timeElapsed - this.lastAssertTime)
); );
this.lastAssertTime = timeElapsed;
this.testResults.failures.push(failure); this.testResults.failures.push(failure);
// special printing // special printing
if (failure.type) { if (failure.type) {
this.comment(' type: ' + failure.type); this.comment(' type: ' + failure.type);
@@ -116,6 +139,33 @@ var Tester = function Tester(casper, options) {
} }
} }
}); });
// casper events
this.casper.on('error', function onCasperError(msg, backtrace) {
if (!phantom.casperTest) {
return;
}
if (msg === self.SKIP_MESSAGE) {
this.warn(f('--fail-fast: aborted remaining tests in "%s"', self.currentTestFile));
self.aborted = true;
return self.done();
}
var line = 0;
if (!utils.isString(msg)) {
try {
line = backtrace[0].line;
} catch (e) {}
}
self.uncaughtError(msg, self.currentTestFile, line);
self.done();
});
this.casper.on('step.error', function onStepError(e) {
if (e.message !== self.SKIP_MESSAGE) {
self.uncaughtError(e, self.currentTestFile);
}
self.done();
});
}; };
// Tester class is an EventEmitter // Tester class is an EventEmitter
@@ -138,6 +188,7 @@ exports.Tester = Tester;
*/ */
Tester.prototype.assert = Tester.prototype.assertTrue = function assert(subject, message, context) { Tester.prototype.assert = Tester.prototype.assertTrue = function assert(subject, message, context) {
"use strict"; "use strict";
this.executed++;
return this.processAssertionResult(utils.mergeObjects({ return this.processAssertionResult(utils.mergeObjects({
success: subject === true, success: subject === true,
type: "assert", type: "assert",
@@ -195,7 +246,7 @@ Tester.prototype.assertNotEquals = function assertNotEquals(subject, shouldnt, m
* *
* @param Function fn A function to be evaluated in remote DOM * @param Function fn A function to be evaluated in remote DOM
* @param String message Test description * @param String message Test description
* @param Object params Object containing the parameters to inject into the function (optional) * @param Object params Object/Array containing the parameters to inject into the function (optional)
* @return Object An assertion result object * @return Object An assertion result object
*/ */
Tester.prototype.assertEval = Tester.prototype.assertEvaluate = function assertEval(fn, message, params) { Tester.prototype.assertEval = Tester.prototype.assertEvaluate = function assertEval(fn, message, params) {
@@ -247,7 +298,7 @@ Tester.prototype.assertField = function assertField(inputName, expected, messag
"use strict"; "use strict";
var actual = this.casper.evaluate(function(inputName) { var actual = this.casper.evaluate(function(inputName) {
return __utils__.getFieldValue(inputName); return __utils__.getFieldValue(inputName);
}, { inputName: inputName }); }, inputName);
return this.assert(this.testEquals(actual, expected), message, { return this.assert(this.testEquals(actual, expected), message, {
type: 'assertField', type: 'assertField',
standard: f('"%s" input field has the value "%s"', inputName, expected), standard: f('"%s" input field has the value "%s"', inputName, expected),
@@ -267,7 +318,7 @@ Tester.prototype.assertField = function assertField(inputName, expected, messag
* @param String message Test description * @param String message Test description
* @return Object An assertion result object * @return Object An assertion result object
*/ */
Tester.prototype.assertExists = Tester.prototype.assertExist = this.assertSelectorExists = Tester.prototype.assertSelectorExist = function assertExists(selector, message) { Tester.prototype.assertExists = Tester.prototype.assertExist = Tester.prototype.assertSelectorExists = Tester.prototype.assertSelectorExist = function assertExists(selector, message) {
"use strict"; "use strict";
return this.assert(this.casper.exists(selector), message, { return this.assert(this.casper.exists(selector), message, {
type: "assertExists", type: "assertExists",
@@ -327,6 +378,9 @@ Tester.prototype.assertHttpStatus = function assertHttpStatus(status, message) {
*/ */
Tester.prototype.assertMatch = Tester.prototype.assertMatches = function assertMatch(subject, pattern, message) { Tester.prototype.assertMatch = Tester.prototype.assertMatches = function assertMatch(subject, pattern, message) {
"use strict"; "use strict";
if (utils.betterTypeOf(pattern) !== "regexp") {
throw new CasperError('Invalid regexp.');
}
return this.assert(pattern.test(subject), message, { return this.assert(pattern.test(subject), message, {
type: "assertMatch", type: "assertMatch",
standard: "Subject matches the provided pattern", standard: "Subject matches the provided pattern",
@@ -344,7 +398,7 @@ Tester.prototype.assertMatch = Tester.prototype.assertMatches = function assertM
* @param String message Test description * @param String message Test description
* @return Object An assertion result object * @return Object An assertion result object
*/ */
Tester.prototype.assertNot = function assertNot(condition, message) { Tester.prototype.assertNot = Tester.prototype.assertFalse = function assertNot(condition, message) {
"use strict"; "use strict";
return this.assert(!condition, message, { return this.assert(!condition, message, {
type: "assertNot", type: "assertNot",
@@ -382,7 +436,7 @@ Tester.prototype.assertNotVisible = Tester.prototype.assertInvisible = function
* @param String message Test description * @param String message Test description
* @return Object An assertion result object * @return Object An assertion result object
*/ */
Tester.prototype.assertRaises = Tester.prototype.assertRaise = this.assertThrows = function assertRaises(fn, args, message) { Tester.prototype.assertRaises = Tester.prototype.assertRaise = Tester.prototype.assertThrows = function assertRaises(fn, args, message) {
"use strict"; "use strict";
var context = { var context = {
type: "assertRaises", type: "assertRaises",
@@ -418,6 +472,27 @@ Tester.prototype.assertResourceExists = Tester.prototype.assertResourceExist = f
}); });
}; };
/**
* Asserts that given text doesn't exist in the document body.
*
* @param String text Text not to be found
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertTextDoesntExist = Tester.prototype.assertTextDoesntExist = function assertTextDoesntExist(text, message) {
"use strict";
var textFound = (this.casper.evaluate(function _evaluate() {
return document.body.textContent || document.body.innerText;
}).indexOf(text) === -1);
return this.assert(textFound, message, {
type: "assertTextDoesntExists",
standard: "Text doesn't exist within the document body",
values: {
text: text
}
});
};
/** /**
* Asserts that given text exists in the document body. * Asserts that given text exists in the document body.
* *
@@ -439,6 +514,44 @@ Tester.prototype.assertTextExists = Tester.prototype.assertTextExist = function
}); });
}; };
/**
* Asserts a subject is truthy.
*
* @param Mixed subject Test subject
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertTruthy = function assertTruthy(subject, message) {
"use strict";
/*jshint eqeqeq:false*/
return this.assert(utils.isTruthy(subject), message, {
type: "assertTruthy",
standard: "Subject is truthy",
values: {
subject: subject
}
});
};
/**
* Asserts a subject is falsy.
*
* @param Mixed subject Test subject
* @param String message Test description
* @return Object An assertion result object
*/
Tester.prototype.assertFalsy = function assertFalsy(subject, message) {
"use strict";
/*jshint eqeqeq:false*/
return this.assert(utils.isFalsy(subject), message, {
type: "assertFalsy",
standard: "Subject is falsy",
values: {
subject: subject
}
});
};
/** /**
* Asserts that given text exists in the provided selector. * Asserts that given text exists in the provided selector.
* *
@@ -447,11 +560,11 @@ Tester.prototype.assertTextExists = Tester.prototype.assertTextExist = function
* @param String message Test description * @param String message Test description
* @return Object An assertion result object * @return Object An assertion result object
*/ */
Tester.prototype.assertSelectorHasText = function assertSelectorHasText(selector, text, message) { Tester.prototype.assertSelectorHasText = Tester.prototype.assertSelectorContains = function assertSelectorHasText(selector, text, message) {
"use strict"; "use strict";
var textFound = this.casper.fetchText(selector).indexOf(text) !== -1; var textFound = this.casper.fetchText(selector).indexOf(text) !== -1;
return this.assert(textFound, message, { return this.assert(textFound, message, {
type: "assertTextInSelector", type: "assertSelectorHasText",
standard: f('Found "%s" within the selector "%s"', text, selector), standard: f('Found "%s" within the selector "%s"', text, selector),
values: { values: {
selector: selector, selector: selector,
@@ -468,11 +581,11 @@ Tester.prototype.assertSelectorHasText = function assertSelectorHasText(selector
* @param String message Test description * @param String message Test description
* @return Object An assertion result object * @return Object An assertion result object
*/ */
Tester.prototype.assertSelectorDoesntHaveText = function assertSelectorDoesntHaveText(selector, text, message) { Tester.prototype.assertSelectorDoesntHaveText = Tester.prototype.assertSelectorDoesntContain = function assertSelectorDoesntHaveText(selector, text, message) {
"use strict"; "use strict";
var textFound = this.casper.fetchText(selector).indexOf(text) === -1; var textFound = this.casper.fetchText(selector).indexOf(text) === -1;
return this.assert(textFound, message, { return this.assert(textFound, message, {
type: "assertNoTextInSelector", type: "assertSelectorDoesntHaveText",
standard: f('Did not find "%s" within the selector "%s"', text, selector), standard: f('Did not find "%s" within the selector "%s"', text, selector),
values: { values: {
selector: selector, selector: selector,
@@ -510,6 +623,9 @@ Tester.prototype.assertTitle = function assertTitle(expected, message) {
*/ */
Tester.prototype.assertTitleMatch = Tester.prototype.assertTitleMatches = function assertTitleMatch(pattern, message) { Tester.prototype.assertTitleMatch = Tester.prototype.assertTitleMatches = function assertTitleMatch(pattern, message) {
"use strict"; "use strict";
if (utils.betterTypeOf(pattern) !== "regexp") {
throw new CasperError('Invalid regexp.');
}
var currentTitle = this.casper.getTitle(); var currentTitle = this.casper.getTitle();
return this.assert(pattern.test(currentTitle), message, { return this.assert(pattern.test(currentTitle), message, {
type: "assertTitle", type: "assertTitle",
@@ -601,6 +717,19 @@ Tester.prototype.bar = function bar(text, style) {
this.casper.echo(text, style, this.options.pad); this.casper.echo(text, style, this.options.pad);
}; };
/**
* Retrieves the sum of all durations of the tests which were
* executed in the current suite
*
* @return Number duration of all tests executed until now (in the current suite)
*/
Tester.prototype.calculateSuiteDuration = function calculateSuiteDuration() {
"use strict";
return this.testResults.passesTime.concat(this.testResults.failuresTime).reduce(function add(a, b) {
return a + b;
}, 0);
};
/** /**
* Render a colorized output. Basically a proxy method for * Render a colorized output. Basically a proxy method for
* Casper.Colorizer#colorize() * Casper.Colorizer#colorize()
@@ -635,7 +764,7 @@ Tester.prototype.configure = function configure() {
// specific timeout callbacks // specific timeout callbacks
this.casper.options.onStepTimeout = function test_onStepTimeout(timeout, step) { this.casper.options.onStepTimeout = function test_onStepTimeout(timeout, step) {
tester.fail(f("Step timeout occured at step %d (%dms)", step, timeout)); tester.fail(f("Step timeout occured at step %s (%dms)", step, timeout));
}; };
this.casper.options.onTimeout = function test_onTimeout(timeout) { this.casper.options.onTimeout = function test_onTimeout(timeout) {
@@ -645,29 +774,19 @@ Tester.prototype.configure = function configure() {
this.casper.options.onWaitTimeout = function test_onWaitTimeout(timeout) { this.casper.options.onWaitTimeout = function test_onWaitTimeout(timeout) {
tester.fail(f("Wait timeout occured (%dms)", timeout)); tester.fail(f("Wait timeout occured (%dms)", timeout));
}; };
// events
this.casper.on('error', function(msg, backtrace) {
var line = 0;
try {
line = backtrace[0].line;
} catch (e) {}
tester.uncaughtError(msg, tester.currentTestFile, line);
tester.done();
});
this.casper.on('step.error', function onStepError(e) {
tester.uncaughtError(e, tester.currentTestFile);
tester.done();
});
}; };
/** /**
* Declares the current test suite done. * Declares the current test suite done.
* *
* @param Number planned Number of planned tests
*/ */
Tester.prototype.done = function done() { Tester.prototype.done = function done(planned) {
"use strict"; "use strict";
if (planned > 0 && planned !== this.executed) {
this.fail(f('%s: %d tests planned, %d tests executed',
this.currentTestFile, planned, this.executed));
}
this.emit('test.done'); this.emit('test.done');
this.running = false; this.running = false;
}; };
@@ -784,6 +903,27 @@ Tester.prototype.getPasses = function getPasses() {
}; };
}; };
/**
* Retrieves the array where all the durations of failed tests are stored
*
* @return Array durations of failed tests
*/
Tester.prototype.getFailuresTime = function getFailuresTime() {
"use strict";
return this.testResults.failuresTime;
}
/**
* Retrieves the array where all the durations of passed tests are stored
*
* @return Array durations of passed tests
*/
Tester.prototype.getPassesTime = function getPassesTime() {
"use strict";
return this.testResults.passesTime;
}
/** /**
* Writes an info-style formatted message to stdout. * Writes an info-style formatted message to stdout.
* *
@@ -816,21 +956,23 @@ Tester.prototype.pass = function pass(message) {
*/ */
Tester.prototype.processAssertionResult = function processAssertionResult(result) { Tester.prototype.processAssertionResult = function processAssertionResult(result) {
"use strict"; "use strict";
var eventName, style, status; var eventName= 'success',
if (result.success === true) { message = result.message || result.standard,
eventName = 'success'; style = 'INFO',
style = 'INFO';
status = this.options.passText; status = this.options.passText;
this.testResults.passed++; if (!result.success) {
} else {
eventName = 'fail'; eventName = 'fail';
style = 'RED_BAR'; style = 'RED_BAR';
status = this.options.failText; status = this.options.failText;
this.testResults.failed++; this.testResults.failed++;
} else {
this.testResults.passed++;
} }
var message = result.message || result.standard;
this.casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' ')); this.casper.echo([this.colorize(status, style), this.formatMessage(message)].join(' '));
this.emit(eventName, result); this.emit(eventName, result);
if (this.options.failFast && !result.success) {
throw this.SKIP_MESSAGE;
}
return result; return result;
}; };
@@ -852,7 +994,7 @@ Tester.prototype.renderFailureDetails = function renderFailureDetails(failures)
message = failure.message; message = failure.message;
this.casper.echo(f('In %s:%s', failure.file, line)); this.casper.echo(f('In %s:%s', failure.file, line));
this.casper.echo(f(' %s: %s', type, message || failure.standard || "(no message was entered)"), "COMMENT"); this.casper.echo(f(' %s: %s', type, message || failure.standard || "(no message was entered)"), "COMMENT");
}); }.bind(this));
}; };
/** /**
@@ -867,8 +1009,8 @@ Tester.prototype.renderResults = function renderResults(exit, status, save) {
var total = this.testResults.passed + this.testResults.failed, statusText, style, result; var total = this.testResults.passed + this.testResults.failed, statusText, style, result;
var exitStatus = ~~(status || (this.testResults.failed > 0 ? 1 : 0)); var exitStatus = ~~(status || (this.testResults.failed > 0 ? 1 : 0));
if (total === 0) { if (total === 0) {
statusText = this.options.failText; statusText = this.options.warnText;
style = 'RED_BAR'; style = 'WARN_BAR';
result = f("%s Looks like you didn't run any test.", statusText); result = f("%s Looks like you didn't run any test.", statusText);
} else { } else {
if (this.testResults.failed > 0) { if (this.testResults.failed > 0) {
@@ -878,8 +1020,9 @@ Tester.prototype.renderResults = function renderResults(exit, status, save) {
statusText = this.options.passText; statusText = this.options.passText;
style = 'GREEN_BAR'; style = 'GREEN_BAR';
} }
result = f('%s %s tests executed, %d passed, %d failed.', result = f('%s %s tests executed in %ss, %d passed, %d failed.',
statusText, total, this.testResults.passed, this.testResults.failed); statusText, total, utils.ms2seconds(this.calculateSuiteDuration()),
this.testResults.passed, this.testResults.failed);
} }
this.casper.echo(result, style, this.options.pad); this.casper.echo(result, style, this.options.pad);
if (this.testResults.failed > 0) { if (this.testResults.failed > 0) {
@@ -931,16 +1074,23 @@ Tester.prototype.runSuites = function runSuites() {
this.casper.exit(1); this.casper.exit(1);
} }
self.currentSuiteNum = 0; self.currentSuiteNum = 0;
self.currentTestStartTime = new Date();
self.lastAssertTime = 0;
var interval = setInterval(function _check(self) { var interval = setInterval(function _check(self) {
if (self.running) { if (self.running) {
return; return;
} }
if (self.currentSuiteNum === testFiles.length) { if (self.currentSuiteNum === testFiles.length || self.aborted) {
self.emit('tests.complete'); self.emit('tests.complete');
clearInterval(interval); clearInterval(interval);
self.exporter.setSuiteDuration(self.calculateSuiteDuration());
self.aborted = false;
} else { } else {
self.runTest(testFiles[self.currentSuiteNum]); self.runTest(testFiles[self.currentSuiteNum]);
self.exporter.setSuiteDuration(self.calculateSuiteDuration());
self.currentSuiteNum++; self.currentSuiteNum++;
self.passesTime = [];
self.failuresTime = [];
} }
}, 100, this); }, 100, this);
}; };
@@ -953,6 +1103,7 @@ Tester.prototype.runTest = function runTest(testFile) {
"use strict"; "use strict";
this.bar(f('Test file: %s', testFile), 'INFO_BAR'); this.bar(f('Test file: %s', testFile), 'INFO_BAR');
this.running = true; // this.running is set back to false with done() this.running = true; // this.running is set back to false with done()
this.executed = 0;
this.exec(testFile); this.exec(testFile);
}; };

View File

@@ -34,16 +34,26 @@
* Provides a better typeof operator equivalent, able to retrieve the array * Provides a better typeof operator equivalent, able to retrieve the array
* type. * type.
* *
* CAVEAT: this function does not necessarilly map to classical js "type" names,
* notably a `null` will map to "null" instead of "object".
*
* @param mixed input * @param mixed input
* @return String * @return String
* @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/ * @see http://javascriptweblog.wordpress.com/2011/08/08/fixing-the-javascript-typeof-operator/
*/ */
function betterTypeOf(input) { function betterTypeOf(input) {
"use strict"; "use strict";
try { switch (input) {
return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase(); case undefined:
} catch (e) { return 'undefined';
return typeof input; case null:
return 'null';
default:
try {
return Object.prototype.toString.call(input).match(/^\[object\s(.*)\]$/)[1].toLowerCase();
} catch (e) {
return typeof input;
}
} }
} }
exports.betterTypeOf = betterTypeOf; exports.betterTypeOf = betterTypeOf;
@@ -69,6 +79,18 @@ function cleanUrl(url) {
} }
exports.cleanUrl = cleanUrl; exports.cleanUrl = cleanUrl;
/**
* Clones an object.
*
* @param Mixed o
* @return Mixed
*/
function clone(o) {
"use strict";
return JSON.parse(JSON.stringify(o));
}
exports.clone = clone;
/** /**
* Dumps a JSON representation of passed value to the console. Used for * Dumps a JSON representation of passed value to the console. Used for
* debugging purpose only. * debugging purpose only.
@@ -264,6 +286,18 @@ function isClipRect(value) {
} }
exports.isClipRect = isClipRect; exports.isClipRect = isClipRect;
/**
* Checks that the subject is falsy.
*
* @param Mixed subject Test subject
* @return Boolean
*/
function isFalsy(subject) {
"use strict";
/*jshint eqeqeq:false*/
return !subject;
}
exports.isFalsy = isFalsy;
/** /**
* Checks if value is a javascript Function * Checks if value is a javascript Function
* *
@@ -337,6 +371,18 @@ function isObject(value) {
} }
exports.isObject = isObject; exports.isObject = isObject;
/**
* Checks if value is a RegExp
*
* @param mixed value
* @return Boolean
*/
function isRegExp(value) {
"use strict";
return isType(value, "regexp");
}
exports.isRegExp = isRegExp;
/** /**
* Checks if value is a javascript String * Checks if value is a javascript String
* *
@@ -349,6 +395,19 @@ function isString(value) {
} }
exports.isString = isString; exports.isString = isString;
/**
* Checks that the subject is truthy.
*
* @param Mixed subject Test subject
* @return Boolean
*/
function isTruthy(subject) {
"use strict";
/*jshint eqeqeq:false*/
return !!subject;
}
exports.isTruthy = isTruthy;
/** /**
* Shorthands for checking if a value is of the given type. Can check for * Shorthands for checking if a value is of the given type. Can check for
* arrays. * arrays.
@@ -433,20 +492,32 @@ exports.isWebPage = isWebPage;
function mergeObjects(origin, add) { function mergeObjects(origin, add) {
"use strict"; "use strict";
for (var p in add) { for (var p in add) {
try { if (add[p] && add[p].constructor === Object) {
if (add[p].constructor === Object) { if (origin[p] && origin[p].constructor === Object) {
origin[p] = mergeObjects(origin[p], add[p]); origin[p] = mergeObjects(origin[p], add[p]);
} else { } else {
origin[p] = add[p]; origin[p] = clone(add[p]);
} }
} catch(e) { } else {
origin[p] = add[p]; origin[p] = add[p];
} }
} }
return origin; return origin;
} }
exports.mergeObjects = mergeObjects; exports.mergeObjects = mergeObjects;
/**
* Converts milliseconds to seconds and rounds the results to 3 digits accuracy.
*
* @param Number milliseconds
* @return Number seconds
*/
function ms2seconds(milliseconds) {
"use strict";
return Math.round(milliseconds / 1000 * 1000) / 1000;
}
exports.ms2seconds = ms2seconds;
/** /**
* Creates an (SG|X)ML node element. * Creates an (SG|X)ML node element.
* *
@@ -456,7 +527,7 @@ exports.mergeObjects = mergeObjects;
*/ */
function node(name, attributes) { function node(name, attributes) {
"use strict"; "use strict";
var _node = document.createElement(name); var _node = document.createElement(name);
for (var attrName in attributes) { for (var attrName in attributes) {
var value = attributes[attrName]; var value = attributes[attrName];
if (attributes.hasOwnProperty(attrName) && isString(attrName)) { if (attributes.hasOwnProperty(attrName) && isString(attrName)) {
@@ -467,6 +538,20 @@ function node(name, attributes) {
} }
exports.node = node; exports.node = node;
/**
* Maps an object to an array made from its values.
*
* @param Object obj
* @return Array
*/
function objectValues(obj) {
"use strict";
return Object.keys(obj).map(function(arg) {
return obj[arg];
});
}
exports.objectValues = objectValues;
/** /**
* Serializes a value using JSON. * Serializes a value using JSON.
* *
@@ -506,3 +591,62 @@ function unique(array) {
return r; return r;
} }
exports.unique = unique; exports.unique = unique;
/**
* Compare two version numbers represented as strings.
*
* @param String a Version a
* @param String b Version b
* @return Number
*/
function cmpVersion(a, b) {
"use strict";
var i, cmp, len, re = /(\.0)+[^\.]*$/;
function versionToString(version) {
if (isObject(version)) {
try {
return [version.major, version.minor, version.patch].join('.');
} catch (e) {}
}
return version;
}
a = versionToString(a);
b = versionToString(b);
a = (a + '').replace(re, '').split('.');
b = (b + '').replace(re, '').split('.');
len = Math.min(a.length, b.length);
for (i = 0; i < len; i++) {
cmp = parseInt(a[i], 10) - parseInt(b[i], 10);
if (cmp !== 0) {
return cmp;
}
}
return a.length - b.length;
}
exports.cmpVersion = cmpVersion;
/**
* Checks if a version number string is greater or equals another.
*
* @param String a Version a
* @param String b Version b
* @return Boolean
*/
function gteVersion(a, b) {
"use strict";
return cmpVersion(a, b) >= 0;
}
exports.gteVersion = gteVersion;
/**
* Checks if a version number string is less than another.
*
* @param String a Version a
* @param String b Version b
* @return Boolean
*/
function ltVersion(a, b) {
"use strict";
return cmpVersion(a, b) < 0;
}
exports.ltVersion = ltVersion;

View File

@@ -45,7 +45,7 @@ var fs = require('fs');
function generateClassName(classname) { function generateClassName(classname) {
"use strict"; "use strict";
classname = classname.replace(phantom.casperPath, "").trim(); classname = classname.replace(phantom.casperPath, "").trim();
var script = classname || phantom.casperScript; var script = classname || phantom.casperScript || "";
if (script.indexOf(fs.workingDirectory) === 0) { if (script.indexOf(fs.workingDirectory) === 0) {
script = script.substring(fs.workingDirectory.length + 1); script = script.substring(fs.workingDirectory.length + 1);
} }
@@ -55,6 +55,12 @@ function generateClassName(classname) {
if (~script.indexOf('.')) { if (~script.indexOf('.')) {
script = script.substring(0, script.lastIndexOf('.')); script = script.substring(0, script.lastIndexOf('.'));
} }
// If we have trimmed our string down to nothing, default to script name
if (!script && phantom.casperScript) {
script = phantom.casperScript;
}
return script || "unknown"; return script || "unknown";
} }
@@ -86,13 +92,18 @@ exports.XUnitExporter = XUnitExporter;
* *
* @param String classname * @param String classname
* @param String name * @param String name
* @param Number duration Test duration in milliseconds
*/ */
XUnitExporter.prototype.addSuccess = function addSuccess(classname, name) { XUnitExporter.prototype.addSuccess = function addSuccess(classname, name, duration) {
"use strict"; "use strict";
this._xml.appendChild(utils.node('testcase', { var snode = utils.node('testcase', {
classname: generateClassName(classname), classname: generateClassName(classname),
name: name name: name
})); });
if (duration !== undefined) {
snode.setAttribute('time', utils.ms2seconds(duration));
}
this._xml.appendChild(snode);
}; };
/** /**
@@ -102,13 +113,17 @@ XUnitExporter.prototype.addSuccess = function addSuccess(classname, name) {
* @param String name * @param String name
* @param String message * @param String message
* @param String type * @param String type
* @param Number duration Test duration in milliseconds
*/ */
XUnitExporter.prototype.addFailure = function addFailure(classname, name, message, type) { XUnitExporter.prototype.addFailure = function addFailure(classname, name, message, type, duration) {
"use strict"; "use strict";
var fnode = utils.node('testcase', { var fnode = utils.node('testcase', {
classname: generateClassName(classname), classname: generateClassName(classname),
name: name name: name
}); });
if (duration !== undefined) {
fnode.setAttribute('time', utils.ms2seconds(duration));
}
var failure = utils.node('failure', { var failure = utils.node('failure', {
type: type || "unknown" type: type || "unknown"
}); });
@@ -117,6 +132,18 @@ XUnitExporter.prototype.addFailure = function addFailure(classname, name, messag
this._xml.appendChild(fnode); this._xml.appendChild(fnode);
}; };
/**
* Adds test suite duration
*
* @param Number duration Test duration in milliseconds
*/
XUnitExporter.prototype.setSuiteDuration = function setSuiteDuration(duration) {
"use strict";
if (!isNaN(duration)) {
this._xml.setAttribute("time", utils.ms2seconds(duration));
}
};
/** /**
* Retrieves generated XML object - actually an HTMLElement. * Retrieves generated XML object - actually an HTMLElement.
* *

View File

@@ -1,7 +1,7 @@
{ {
"name": "casperjs", "name": "casperjs",
"description": "Navigation scripting & testing utility for PhantomJS", "description": "Navigation scripting & testing utility for PhantomJS",
"version": "1.0.0-RC4", "version": "1.0.2",
"keywords": [ "keywords": [
"phantomjs", "phantomjs",
"javascript" "javascript"
@@ -14,7 +14,7 @@
} }
], ],
"dependencies": { "dependencies": {
"http://www.phantomjs.org/": "1.6" "http://www.phantomjs.org/": "1.7"
}, },
"bugs": { "bugs": {
"url": "https://github.com/n1k0/casperjs/issues" "url": "https://github.com/n1k0/casperjs/issues"

View File

@@ -0,0 +1,203 @@
%define name casperjs
%define version 1.0.0
%define release 1_1
%define prefix /usr
%define mybuilddir %{_builddir}/%{name}-%{version}-root
Summary: open source navigation scripting & testing utility written in Javascript
Name: %{name}
Version: %{version}
License: BSD
Release: %{release}
Packager: Jan Schaumann <jschauma@etsy.com>
Group: Utilities/Misc
Source: %{name}-%{version}.tar.gz
BuildRoot: /tmp/%{name}-%{version}-root
Requires: phantomjs
%description
CasperJS is an open source navigation scripting & testing utility written
in Javascript and based on PhantomJS. It eases the process of defining a
full navigation scenario and provides useful high-level functions, methods
& syntactic sugar for doing common tasks
%prep
%setup -q
%setup
mkdir -p %{mybuilddir}%{prefix}/bin
mkdir -p %{mybuilddir}%{prefix}/share/%{name}/bin
mkdir -p %{mybuilddir}%{prefix}/share/%{name}/modules
mkdir -p %{mybuilddir}%{prefix}/share/%{name}/samples
mkdir -p %{mybuilddir}%{prefix}/share/%{name}/tests
%install
cp bin/%{name} %{mybuilddir}%{prefix}/share/%{name}/bin/
ln -s %{prefix}/share/%{name}/bin/%{name} %{mybuilddir}%{prefix}/bin/%{name}
cp bin/bootstrap.js %{mybuilddir}%{prefix}/share/%{name}/bin/
# Yes, this tool needs this file in the 'bin' directory.
cp bin/usage.txt %{mybuilddir}%{prefix}/share/%{name}/bin/
cp CHANGELOG.md %{mybuilddir}%{prefix}/share/%{name}/
cp CONTRIBUTING.md %{mybuilddir}%{prefix}/share/%{name}/
cp CONTRIBUTORS.md %{mybuilddir}%{prefix}/share/%{name}/
cp LICENSE.md %{mybuilddir}%{prefix}/share/%{name}/
cp README.md %{mybuilddir}%{prefix}/share/%{name}/
cp package.json %{mybuilddir}%{prefix}/share/%{name}/
cp -R modules/* %{mybuilddir}%{prefix}/share/%{name}/modules/
cp -R samples/* %{mybuilddir}%{prefix}/share/%{name}/samples/
cp -R tests/* %{mybuilddir}%{prefix}/share/%{name}/tests/
%files
%defattr(0444,root,root)
%attr(0555,root,root)%{prefix}/bin/%{name}
%attr(0555,root,root)%{prefix}/share/%{name}/bin/%{name}
%attr(0555,root,root)%{prefix}/share/%{name}/bin/bootstrap.js
%{prefix}/share/%{name}/bin/usage.txt
%{prefix}/share/%{name}/CHANGELOG.md
%{prefix}/share/%{name}/CONTRIBUTING.md
%{prefix}/share/%{name}/CONTRIBUTORS.md
%{prefix}/share/%{name}/LICENSE.md
%{prefix}/share/%{name}/README.md
%{prefix}/share/%{name}/package.json
%{prefix}/share/%{name}/modules/casper.js
%{prefix}/share/%{name}/modules/cli.js
%{prefix}/share/%{name}/modules/clientutils.js
%{prefix}/share/%{name}/modules/colorizer.js
%{prefix}/share/%{name}/modules/events.js
%{prefix}/share/%{name}/modules/http.js
%{prefix}/share/%{name}/modules/mouse.js
%{prefix}/share/%{name}/modules/querystring.js
%{prefix}/share/%{name}/modules/tester.js
%{prefix}/share/%{name}/modules/utils.js
%{prefix}/share/%{name}/modules/vendors/coffee-script.js
%{prefix}/share/%{name}/modules/xunit.js
%{prefix}/share/%{name}/samples/bbcshots.coffee
%{prefix}/share/%{name}/samples/bbcshots.js
%{prefix}/share/%{name}/samples/cliplay.coffee
%{prefix}/share/%{name}/samples/cliplay.js
%{prefix}/share/%{name}/samples/customevents.coffee
%{prefix}/share/%{name}/samples/customevents.js
%{prefix}/share/%{name}/samples/customlogging.coffee
%{prefix}/share/%{name}/samples/customlogging.js
%{prefix}/share/%{name}/samples/download.coffee
%{prefix}/share/%{name}/samples/download.js
%{prefix}/share/%{name}/samples/dynamic.coffee
%{prefix}/share/%{name}/samples/dynamic.js
%{prefix}/share/%{name}/samples/each.coffee
%{prefix}/share/%{name}/samples/each.js
%{prefix}/share/%{name}/samples/events.coffee
%{prefix}/share/%{name}/samples/events.js
%{prefix}/share/%{name}/samples/extends.coffee
%{prefix}/share/%{name}/samples/extends.js
%{prefix}/share/%{name}/samples/googlelinks.coffee
%{prefix}/share/%{name}/samples/googlelinks.js
%{prefix}/share/%{name}/samples/googlematch.coffee
%{prefix}/share/%{name}/samples/googlematch.js
%{prefix}/share/%{name}/samples/googlepagination.coffee
%{prefix}/share/%{name}/samples/googlepagination.js
%{prefix}/share/%{name}/samples/googletesting.coffee
%{prefix}/share/%{name}/samples/googletesting.js
%{prefix}/share/%{name}/samples/logcolor.coffee
%{prefix}/share/%{name}/samples/logcolor.js
%{prefix}/share/%{name}/samples/metaextract.coffee
%{prefix}/share/%{name}/samples/metaextract.js
%{prefix}/share/%{name}/samples/multirun.coffee
%{prefix}/share/%{name}/samples/multirun.js
%{prefix}/share/%{name}/samples/screenshot.coffee
%{prefix}/share/%{name}/samples/screenshot.js
%{prefix}/share/%{name}/samples/statushandlers.coffee
%{prefix}/share/%{name}/samples/statushandlers.js
%{prefix}/share/%{name}/samples/steptimeout.coffee
%{prefix}/share/%{name}/samples/steptimeout.js
%{prefix}/share/%{name}/samples/timeout.coffee
%{prefix}/share/%{name}/samples/timeout.js
%{prefix}/share/%{name}/tests/site/field-array.html
%{prefix}/share/%{name}/tests/site/images/phantom.png
%{prefix}/share/%{name}/tests/site/result.html
%{prefix}/share/%{name}/tests/site/multiple-forms.html
%{prefix}/share/%{name}/tests/site/global.html
%{prefix}/share/%{name}/tests/site/elementattribute.html
%{prefix}/share/%{name}/tests/site/urls.html
%{prefix}/share/%{name}/tests/site/mouse-events.html
%{prefix}/share/%{name}/tests/site/index.html
%{prefix}/share/%{name}/tests/site/click.html
%{prefix}/share/%{name}/tests/site/page1.html
%{prefix}/share/%{name}/tests/site/prompt.html
%{prefix}/share/%{name}/tests/site/error.html
%{prefix}/share/%{name}/tests/site/dummy.js
%{prefix}/share/%{name}/tests/site/page2.html
%{prefix}/share/%{name}/tests/site/alert.html
%{prefix}/share/%{name}/tests/site/form.html
%{prefix}/share/%{name}/tests/site/confirm.html
%{prefix}/share/%{name}/tests/site/resources.html
%{prefix}/share/%{name}/tests/site/test.html
%{prefix}/share/%{name}/tests/site/page3.html
%{prefix}/share/%{name}/tests/site/visible.html
%{prefix}/share/%{name}/tests/site/waitFor.html
%{prefix}/share/%{name}/tests/sample_modules/csmodule.coffee
%{prefix}/share/%{name}/tests/sample_modules/jsmodule.js
%{prefix}/share/%{name}/tests/testdir/03_a.js
%{prefix}/share/%{name}/tests/testdir/02_b/abc.js
%{prefix}/share/%{name}/tests/testdir/04/02_do.js
%{prefix}/share/%{name}/tests/testdir/04/01_init.js
%{prefix}/share/%{name}/tests/testdir/01_a/abc.js
%{prefix}/share/%{name}/tests/testdir/01_a/def.js
%{prefix}/share/%{name}/tests/testdir/03_b.js
%{prefix}/share/%{name}/tests/suites/casper/capture.js
%{prefix}/share/%{name}/tests/suites/casper/prompt.js
%{prefix}/share/%{name}/tests/suites/casper/resources.coffee
%{prefix}/share/%{name}/tests/suites/casper/auth.js
%{prefix}/share/%{name}/tests/suites/casper/alert.js
%{prefix}/share/%{name}/tests/suites/casper/wait.js
%{prefix}/share/%{name}/tests/suites/casper/flow.coffee
%{prefix}/share/%{name}/tests/suites/casper/events.js
%{prefix}/share/%{name}/tests/suites/casper/evaluate.js
%{prefix}/share/%{name}/tests/suites/casper/logging.js
%{prefix}/share/%{name}/tests/suites/casper/xpath.js
%{prefix}/share/%{name}/tests/suites/casper/elementattribute.js
%{prefix}/share/%{name}/tests/suites/casper/viewport.js
%{prefix}/share/%{name}/tests/suites/casper/.casper
%{prefix}/share/%{name}/tests/suites/casper/steps.js
%{prefix}/share/%{name}/tests/suites/casper/exists.js
%{prefix}/share/%{name}/tests/suites/casper/click.js
%{prefix}/share/%{name}/tests/suites/casper/mouseevents.js
%{prefix}/share/%{name}/tests/suites/casper/fetchtext.js
%{prefix}/share/%{name}/tests/suites/casper/urls.js
%{prefix}/share/%{name}/tests/suites/casper/open.js
%{prefix}/share/%{name}/tests/suites/casper/agent.js
%{prefix}/share/%{name}/tests/suites/casper/formfill.js
%{prefix}/share/%{name}/tests/suites/casper/request.js
%{prefix}/share/%{name}/tests/suites/casper/confirm.js
%{prefix}/share/%{name}/tests/suites/casper/history.js
%{prefix}/share/%{name}/tests/suites/casper/debug.js
%{prefix}/share/%{name}/tests/suites/casper/global.js
%{prefix}/share/%{name}/tests/suites/casper/encode.js
%{prefix}/share/%{name}/tests/suites/casper/onerror.js
%{prefix}/share/%{name}/tests/suites/casper/start.js
%{prefix}/share/%{name}/tests/suites/casper/hooks.js
%{prefix}/share/%{name}/tests/suites/casper/headers.js
%{prefix}/share/%{name}/tests/suites/casper/visible.js
%{prefix}/share/%{name}/tests/suites/coffee.coffee
%{prefix}/share/%{name}/tests/suites/require.js
%{prefix}/share/%{name}/tests/suites/cli.js
%{prefix}/share/%{name}/tests/suites/fs.js
%{prefix}/share/%{name}/tests/suites/.casper
%{prefix}/share/%{name}/tests/suites/tester.js
%{prefix}/share/%{name}/tests/suites/clientutils.js
%{prefix}/share/%{name}/tests/suites/http_status.js
%{prefix}/share/%{name}/tests/suites/xunit.js
%{prefix}/share/%{name}/tests/suites/utils.js
%{prefix}/share/%{name}/tests/selftest.js
%{prefix}/share/%{name}/tests/run.js
%changelog
* Mon Dec 24 2012 Nicolas Perriault <nicolas@perriault.net>
- removed 'injector.js' module
* Mon Dec 10 2012 Jan Schaumann <jschauma@etsy.com>
- include 'tests'
* Mon Nov 26 2012 Jan Schaumann <jschauma@etsy.com>
- first rpm version

View File

@@ -0,0 +1,25 @@
#!/bin/sh
#
# A silly little helper script to build the RPM.
set -e
name=${1:?"Usage: build <toolname>"}
name=${name%.spec}
topdir=$(mktemp -d)
version=$(awk '/define version/ { print $NF }' ${name}.spec)
builddir=${TMPDIR:-/tmp}/${name}-${version}
sourcedir="${topdir}/SOURCES"
buildroot="${topdir}/BUILD/${name}-${version}-root"
mkdir -p ${topdir}/RPMS ${topdir}/SRPMS ${topdir}/SOURCES ${topdir}/BUILD
mkdir -p ${buildroot} ${builddir}
echo "=> Copying sources..."
( cd .. && tar cf - ./[A-Z]* ./package.json ./bin ./samples ./tests ./modules | tar xf - -C ${builddir} )
echo "=> Creating source tarball under ${sourcedir}..."
( cd ${builddir}/.. && tar zcf ${sourcedir}/${name}-${version}.tar.gz ${name}-${version} )
echo "=> Building RPM..."
#rpmbuild --define "_topdir ${topdir}" --buildroot ${buildroot} --clean -bb ${name}.spec
rpm=$(rpmbuild --define "_topdir ${topdir}" --buildroot ${buildroot} --clean -bb ${name}.spec 2>/dev/null | \
awk '/\/RPMS\// { print $2; }')
cp ${rpm} ${TMPDIR:-/tmp}/
rm -fr ${topdir}
echo ${TMPDIR:-/tmp}/${rpm##*/}

View File

@@ -59,4 +59,4 @@ if system(CASPER_COMMAND.join(" ")).nil?
puts "Fatal: Did you install phantomjs?" puts "Fatal: Did you install phantomjs?"
end end
exit $?.exitstatus exit $?.exitstatus || 1

View File

@@ -11,7 +11,7 @@ images = []
casper.hide = (selector) -> casper.hide = (selector) ->
@evaluate (selector) -> @evaluate (selector) ->
document.querySelector(selector).style.display = "none" document.querySelector(selector).style.display = "none"
, selector: selector , selector
casper.start "http://www.bbc.co.uk/", -> casper.start "http://www.bbc.co.uk/", ->
nbLinks = @evaluate -> nbLinks = @evaluate ->
@@ -21,15 +21,16 @@ casper.start "http://www.bbc.co.uk/", ->
@hide ".nav_left" @hide ".nav_left"
@hide ".nav_right" @hide ".nav_right"
@mouse.move "#promo2_carousel" @mouse.move "#promo2_carousel"
@waitUntilVisible ".autoplay.nav_pause", ->
@echo "Moving over pause button" casper.waitUntilVisible ".autoplay.nav_pause", ->
@mouse.move ".autoplay.nav_pause" @echo "Moving over pause button"
@click ".autoplay.nav_pause" @mouse.move ".autoplay.nav_pause"
@echo "Clicked on pause button" @click ".autoplay.nav_pause"
@waitUntilVisible ".autoplay.nav_play", -> @echo "Clicked on pause button"
@echo "Carousel has been paused" @waitUntilVisible ".autoplay.nav_play", ->
# hide play button @echo "Carousel has been paused"
@hide ".autoplay" # hide play button
@hide ".autoplay"
# Capture carrousel area # Capture carrousel area
next = -> next = ->

View File

@@ -1,7 +1,9 @@
/* /*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* Create a mosaic image from all headline photos on BBC homepage * Create a mosaic image from all headline photos on BBC homepage
*/ */
var casper = require("casper").create(); var casper = require("casper").create();
var nbLinks = 0; var nbLinks = 0;
var currentLink = 1; var currentLink = 1;
@@ -12,9 +14,7 @@ var buildPage, next;
casper.hide = function(selector) { casper.hide = function(selector) {
this.evaluate(function(selector) { this.evaluate(function(selector) {
document.querySelector(selector).style.display = "none"; document.querySelector(selector).style.display = "none";
}, { }, selector);
selector: selector
});
}; };
casper.start("http://www.bbc.co.uk/", function() { casper.start("http://www.bbc.co.uk/", function() {
@@ -26,16 +26,17 @@ casper.start("http://www.bbc.co.uk/", function() {
this.hide(".nav_left"); this.hide(".nav_left");
this.hide(".nav_right"); this.hide(".nav_right");
this.mouse.move("#promo2_carousel"); this.mouse.move("#promo2_carousel");
this.waitUntilVisible(".autoplay.nav_pause", function() { });
this.echo("Moving over pause button");
this.mouse.move(".autoplay.nav_pause"); casper.waitUntilVisible(".autoplay.nav_pause", function() {
this.click(".autoplay.nav_pause"); this.echo("Moving over pause button");
this.echo("Clicked on pause button"); this.mouse.move(".autoplay.nav_pause");
this.waitUntilVisible(".autoplay.nav_play", function() { this.click(".autoplay.nav_pause");
this.echo("Carousel has been paused"); this.echo("Clicked on pause button");
// hide play button this.waitUntilVisible(".autoplay.nav_play", function() {
this.hide(".autoplay"); this.echo("Carousel has been paused");
}); // hide play button
this.hide(".autoplay");
}); });
}); });

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var casper = require("casper").create(); var casper = require("casper").create();
var dump = require("utils").dump; var dump = require("utils").dump;

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var casper = require("casper").create(); var casper = require("casper").create();
// listening to a custom event // listening to a custom event

View File

@@ -2,29 +2,27 @@
A basic custom logging implementation. The idea is to (extremely) verbosely A basic custom logging implementation. The idea is to (extremely) verbosely
log every received resource. log every received resource.
### ###
casper = require("casper").create casper = require("casper").create
###
Every time a resource is received, a new log entry is added to the stack
at the 'verbose' level.
@param Object resource A phantomjs resource object
###
onResourceReceived: (self, resource) ->
infos = []
props = [
"url"
"status"
"statusText"
"redirectURL"
"bodySize"
]
infos.push resource[prop] for prop in props
infos.push "[#{header.name}: #{header.value}]" for header in resource.headers
@log infos.join(", "), "verbose"
verbose: true # we want to see the log printed out to the console verbose: true # we want to see the log printed out to the console
logLevel: "verbose" # of course we want to see logs to our new level :) logLevel: "verbose" # of course we want to see logs to our new level :)
###
Every time a resource is received, a new log entry is added to the stack
at the 'verbose' level.
###
casper.on 'resource.received', (resource) ->
infos = []
props = [
"url"
"status"
"statusText"
"redirectURL"
"bodySize"
]
infos.push resource[prop] for prop in props
infos.push "[#{header.name}: #{header.value}]" for header in resource.headers
@log infos.join(", "), "verbose"
# add a new 'verbose' logging level at the lowest priority # add a new 'verbose' logging level at the lowest priority
casper.logLevels = ["verbose"].concat casper.logLevels casper.logLevels = ["verbose"].concat casper.logLevels

View File

@@ -1,38 +1,37 @@
/* /*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* A basic custom logging implementation. The idea is to (extremely) verbosely * A basic custom logging implementation. The idea is to (extremely) verbosely
* log every received resource. * log every received resource.
*/ */
var casper = require("casper").create({ var casper = require("casper").create({
/*
Every time a resource is received, a new log entry is added to the stack at
the 'verbose' level.
*/
onResourceReceived: function(self, resource) {
var header, infos, prop, props, _i, _j, _len, _len1, _ref;
infos = [];
props = [
"url",
"status",
"statusText",
"redirectURL",
"bodySize"
];
for (_i = 0, _len = props.length; _i < _len; _i++) {
prop = props[_i];
infos.push(resource[prop]);
}
_ref = resource.headers;
for (_j = 0, _len1 = _ref.length; _j < _len1; _j++) {
header = _ref[_j];
infos.push("[" + header.name + ": " + header.value + "]");
}
this.log(infos.join(", "), "verbose");
},
verbose: true, verbose: true,
logLevel: "verbose" logLevel: "verbose"
}); });
/**
* Every time a resource is received, a new log entry is added to the stack at
* the 'verbose' level.
*/
casper.on('resource.received', function(resource) {
var infos = [];
var props = [
"url",
"status",
"statusText",
"redirectURL",
"bodySize"
];
props.forEach(function(prop) {
infos.push(resource[prop]);
});
resource.headers.forEach(function(header) {
infos.push("[" + header.name + ": " + header.value + "]");
});
this.log(infos.join(", "), "verbose");
});
// add a new 'verbose' logging level at the lowest priority // add a new 'verbose' logging level at the lowest priority
casper.logLevels = ["verbose"].concat(casper.logLevels); casper.logLevels = ["verbose"].concat(casper.logLevels);

View File

@@ -1,4 +1,7 @@
/* /*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* download the google logo image onto the local filesystem * download the google logo image onto the local filesystem
*/ */

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var casper = require("casper").create({ var casper = require("casper").create({
verbose: true verbose: true
}); });

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var casper = require("casper").create(); var casper = require("casper").create();
var links = [ var links = [

View File

@@ -1,7 +1,9 @@
/* /*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* This script will add a custom HTTP status code handler, here for 404 pages. * This script will add a custom HTTP status code handler, here for 404 pages.
*/ */
var casper = require("casper").create(); var casper = require("casper").create();
casper.on("http.status.200", function(resource) { casper.on("http.status.200", function(resource) {

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var casper = require("casper").create({ var casper = require("casper").create({
loadImages: false, loadImages: false,
logLevel: "debug", logLevel: "debug",
@@ -34,4 +37,4 @@ Object.keys(links).forEach(function(url) {
fantomas.run(function() { fantomas.run(function() {
this.renderJSON(links); this.renderJSON(links);
this.exit(); this.exit();
}); });

View File

@@ -1,3 +1,5 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var links = []; var links = [];
var casper = require("casper").create(); var casper = require("casper").create();

View File

@@ -14,7 +14,7 @@ casper = require("casper").create verbose: true
casper.fetchScore = -> casper.fetchScore = ->
@evaluate -> @evaluate ->
result = document.querySelector('#resultStats').innerText result = __utils__.findOne('#resultStats').innerText
parseInt /Environ ([0-9\s]{1,}).*/.exec(result)[1].replace(/\s/g, '') parseInt /Environ ([0-9\s]{1,}).*/.exec(result)[1].replace(/\s/g, '')
terms = casper.cli.args # terms are passed through command-line arguments terms = casper.cli.args # terms are passed through command-line arguments

View File

@@ -1,4 +1,7 @@
/* /*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* Takes provided terms passed as arguments and query google for the number of * Takes provided terms passed as arguments and query google for the number of
* estimated results each have. * estimated results each have.
* *
@@ -16,7 +19,7 @@ var casper = require("casper").create({
casper.fetchScore = function() { casper.fetchScore = function() {
return this.evaluate(function() { return this.evaluate(function() {
var result = document.querySelector('#resultStats').innerText; var result = __utils__.findOne('#resultStats').innerText;
return parseInt(/Environ ([0-9\s]{1,}).*/.exec(result)[1].replace(/\s/g, ''), 10); return parseInt(/Environ ([0-9\s]{1,}).*/.exec(result)[1].replace(/\s/g, ''), 10);
}); });
}; };

View File

@@ -1,4 +1,7 @@
/* /*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* Capture multiple pages of google search results * Capture multiple pages of google search results
* *
* Usage: $ casperjs googlepagination.coffee my search terms * Usage: $ casperjs googlepagination.coffee my search terms

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var casper = require("casper").create({ var casper = require("casper").create({
logLevel: "debug" logLevel: "debug"
}); });

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var casper = require("casper").create({ var casper = require("casper").create({
verbose: true, verbose: true,
logLevel: "debug" logLevel: "debug"

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var casper = require("casper").create(); var casper = require("casper").create();
var url = casper.cli.get(0); var url = casper.cli.get(0);
var metas = []; var metas = [];

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var casper = require("casper").create({ var casper = require("casper").create({
verbose: true verbose: true
}); });

View File

@@ -16,8 +16,8 @@ if not twitterAccount or not filename or not /\.(png|jpg|pdf)$/i.test filename
.echo("Usage: $ casperjs screenshot.coffee <twitter-account> <filename.[jpg|png|pdf]>") .echo("Usage: $ casperjs screenshot.coffee <twitter-account> <filename.[jpg|png|pdf]>")
.exit(1) .exit(1)
casper.start "https://twitter.com/#!/#{twitterAccount}", -> casper.start "https://twitter.com/#{twitterAccount}", ->
@waitForSelector ".tweet-row", (-> @waitForSelector ".stream-container", (->
@captureSelector filename, "html" @captureSelector filename, "html"
@echo "Saved screenshot of #{@getCurrentUrl()} to #{filename}" @echo "Saved screenshot of #{@getCurrentUrl()} to #{filename}"
), (-> ), (->

View File

@@ -1,6 +1,9 @@
/* /*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* This script will capture a screenshot of a twitter account page * This script will capture a screenshot of a twitter account page
* Usage: $ casperjs screenshot.coffee <twitter-account> <filename.[jpg|png|pdf]> * Usage: $ casperjs screenshot.js <twitter-account> <filename.[jpg|png|pdf]>
*/ */
var casper = require("casper").create({ var casper = require("casper").create({
@@ -15,13 +18,13 @@ var filename = casper.cli.get(1);
if (!twitterAccount || !filename || !/\.(png|jpg|pdf)$/i.test(filename)) { if (!twitterAccount || !filename || !/\.(png|jpg|pdf)$/i.test(filename)) {
casper casper
.echo("Usage: $ casperjs screenshot.coffee <twitter-account> <filename.[jpg|png|pdf]>") .echo("Usage: $ casperjs screenshot.js <twitter-account> <filename.[jpg|png|pdf]>")
.exit(1) .exit(1)
; ;
} }
casper.start("https://twitter.com/#!/" + twitterAccount, function() { casper.start("https://twitter.com/" + twitterAccount, function() {
this.waitForSelector(".tweet-row", (function() { this.waitForSelector(".stream-container", (function() {
this.captureSelector(filename, "html"); this.captureSelector(filename, "html");
this.echo("Saved screenshot of " + (this.getCurrentUrl()) + " to " + filename); this.echo("Saved screenshot of " + (this.getCurrentUrl()) + " to " + filename);
}), (function() { }), (function() {

View File

@@ -1,4 +1,7 @@
/* /*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* This script will add a custom HTTP status code handler, here for 404 pages. * This script will add a custom HTTP status code handler, here for 404 pages.
*/ */

View File

@@ -1,3 +1,6 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
var failed = []; var failed = [];
var start = null; var start = null;
var links = [ var links = [

View File

@@ -1,4 +1,7 @@
/* /*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* Just a silly game. * Just a silly game.
* *
* $ casperjs samples/timeout.js 500 * $ casperjs samples/timeout.js 500

View File

@@ -0,0 +1,23 @@
###
Translation using the Google Translate Service.
Usage:
$ casperjs translate.coffee --target=fr "hello world"
bonjour tout le monde
###
system = require("system")
casper = require("casper").create()
format = require("utils").format
source = casper.cli.get("source") or "auto"
target = casper.cli.get("target")
text = casper.cli.get(0)
result = undefined
casper.warn("The --target option is mandatory.").exit 1 unless target
casper.start(format("http://translate.google.com/#%s/%s/%s", source, target, text), ->
@fill "form#gt-form", text: text
).waitForSelector "span.hps", -> @echo @fetchText("#result_box")
casper.run()

View File

@@ -0,0 +1,30 @@
/*jshint strict:false*/
/*global CasperError console phantom require*/
/**
* Translation using the Google Translate Service.
*
* Usage:
*
* $ casperjs translate.js --target=fr "hello world"
* bonjour tout le monde
*/
var system = require('system'),
casper = require('casper').create(),
format = require('utils').format,
source = casper.cli.get('source') || 'auto',
target = casper.cli.get('target'),
text = casper.cli.get(0),
result;
if (!target) {
casper.warn('The --target option is mandatory.').exit(1);
}
casper.start(format('http://translate.google.com/#%s/%s/%s', source, target, text), function() {
this.fill('form#gt-form', {text: text});
}).waitForSelector('span.hps', function() {
this.echo(this.fetchText("#result_box"));
});
casper.run();

View File

@@ -0,0 +1,14 @@
/*jshint strict:false*/
/*global CasperError casper console phantom require*/
casper.start('about:blank', function() {
this.test.pass('ok1');
});
casper.then(function() {
this.test.pass('ok2');
});
casper.run(function() {
this.test.pass('ok3');
this.test.done();
});

View File

@@ -0,0 +1,3 @@
var casper = require('casper').create();
casper.echo('it works');
casper.exit();

View File

@@ -1,4 +1,4 @@
/*global phantom*/ /*global phantom CasperError*/
if (!phantom.casperLoaded) { if (!phantom.casperLoaded) {
console.log('This script must be invoked using the casperjs executable'); console.log('This script must be invoked using the casperjs executable');
@@ -21,10 +21,8 @@ function checkSelfTest(tests) {
var isCasperTest = false; var isCasperTest = false;
tests.forEach(function(test) { tests.forEach(function(test) {
var testDir = fs.absolute(fs.dirname(test)); var testDir = fs.absolute(fs.dirname(test));
if (fs.isDirectory(testDir)) { if (fs.isDirectory(testDir) && fs.exists(fs.pathJoin(testDir, '.casper'))) {
if (fs.exists(fs.pathJoin(testDir, '.casper'))) { isCasperTest = true;
isCasperTest = true;
}
} }
}); });
return isCasperTest; return isCasperTest;
@@ -52,52 +50,72 @@ function checkIncludeFile(include) {
return absInclude; return absInclude;
} }
// parse some options from cli function checkArgs() {
casper.options.verbose = casper.cli.get('direct') || false;
casper.options.logLevel = casper.cli.get('log-level') || "error";
if (casper.cli.get('no-colors') === true) {
var cls = 'Dummy';
casper.options.colorizerType = cls;
casper.colorizer = colorizer.create(cls);
}
// test paths are passed as args
if (casper.cli.args.length) {
tests = casper.cli.args.filter(function(path) {
"use strict";
return fs.isFile(path) || fs.isDirectory(path);
});
} else {
casper.echo('No test path passed, exiting.', 'RED_BAR', 80);
casper.exit(1);
}
// check for casper selftests
if (!phantom.casperSelfTest && checkSelfTest(tests)) {
casper.warn('To run casper self tests, use the `selftest` command.');
casper.exit(1);
}
// includes handling
this.loadIncludes.forEach(function(include){
"use strict"; "use strict";
var container; // parse some options from cli
if (casper.cli.has(include)) { casper.options.verbose = casper.cli.get('direct') || false;
container = casper.cli.get(include).split(',').map(function(file) { casper.options.logLevel = casper.cli.get('log-level') || "error";
return checkIncludeFile(file); if (casper.cli.get('no-colors') === true) {
}).filter(function(file) { var cls = 'Dummy';
return utils.isString(file); casper.options.colorizerType = cls;
}); casper.colorizer = colorizer.create(cls);
casper.test.loadIncludes[include] = utils.unique(container);
} }
}); casper.test.options.failFast = casper.cli.get('fail-fast') || false;
// test suites completion listener // test paths are passed as args
casper.test.on('tests.complete', function() { if (casper.cli.args.length) {
tests = casper.cli.args.filter(function(path) {
if (fs.isFile(path) || fs.isDirectory(path)) {
return true;
}
throw new CasperError(f("Invalid test path: %s", path));
});
} else {
casper.echo('No test path passed, exiting.', 'RED_BAR', 80);
casper.exit(1);
}
// check for casper selftests
if (!phantom.casperSelfTest && checkSelfTest(tests)) {
casper.warn('To run casper self tests, use the `selftest` command.');
casper.exit(1);
}
}
function initRunner() {
"use strict"; "use strict";
this.renderResults(true, undefined, casper.cli.get('xunit') || undefined); // includes handling
}); loadIncludes.forEach(function(include){
var container;
if (casper.cli.has(include)) {
container = casper.cli.get(include).split(',').map(function(file) {
return checkIncludeFile(file);
}).filter(function(file) {
return utils.isString(file);
});
casper.test.loadIncludes[include] = utils.unique(container);
}
});
// run all the suites // test suites completion listener
casper.test.runSuites.apply(casper.test, tests); casper.test.on('tests.complete', function() {
this.renderResults(true, undefined, casper.cli.get('xunit') || undefined);
if (this.options.failFast && this.testResults.failures.length > 0) {
casper.warn('Test suite failed fast, all tests may not have been executed.');
}
});
}
var error;
try {
checkArgs();
} catch (e) {
error = true;
casper.warn(e);
casper.exit(1);
}
if (!error) {
initRunner();
casper.test.runSuites.apply(casper.test, tests);
}

View File

@@ -0,0 +1 @@
{"ok": true}

View File

@@ -1,5 +1 @@
try exports.ok = true
exports.ok = true
catch e
casper.test.fail('error in coffeescript module code: ' + e)
casper.test.done()

View File

@@ -1,7 +1 @@
/*global casper*/ exports.ok = true;
try {
exports.ok = true;
} catch (e) {
casper.test.fail('error in js module code' + e);
casper.test.done()
}

View File

@@ -28,7 +28,11 @@ service = server.listen(testServerPort, function(request, response) {
console.log(utils.format('Test server url not found: %s (file: %s)', request.url, pageFile), "warning"); console.log(utils.format('Test server url not found: %s (file: %s)', request.url, pageFile), "warning");
response.write("404 - NOT FOUND"); response.write("404 - NOT FOUND");
} else { } else {
response.statusCode = 200; var headers = {};
if (/js$/.test(pageFile)) {
headers['Content-Type'] = "application/javascript";
}
response.writeHead(200, headers);
response.write(fs.read(pageFile)); response.write(fs.read(pageFile));
} }
response.close(); response.close();

View File

@@ -16,10 +16,11 @@
test2: false, test2: false,
test3: false, test3: false,
test4: false, test4: false,
testdown: [], testdown: [],
testup: [], testup: [],
testmove: [], testmove: [],
testclick: [] testclick: [],
testdoubleclick: []
}; };
document.querySelector('#test4').onclick = function(event) { document.querySelector('#test4').onclick = function(event) {
results.test4 = true; results.test4 = true;
@@ -34,6 +35,9 @@
window.onmousemove = function(event) { window.onmousemove = function(event) {
results.testmove = [event.x, event.y]; results.testmove = [event.x, event.y];
}; };
window.ondblclick = function(event) {
results.testdoubleclick = [event.x, event.y];
};
})(window); })(window);
</script> </script>
</body> </body>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>CasperJS test form</title>
</head>
<body>
<form action="result.html" enctype="multipart/form-data">
<input type="text" name="foo[bar]">
<input type="text" name="foo[baz]">
<input type="submit" name="submit" value="submit">
</form>
</body>
</html>

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>CasperJS frame 1</title>
</head>
<body id="f1">
<h1>This is frame 1.</h1>
</body>
</html>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>CasperJS frame 2</title>
</head>
<body id="f2">
<h1>This is frame 2.</h1>
<p><a href="frame3.html" target="frame2">frame 3</a></p>
</body>
</html>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>CasperJS frame 3</title>
</head>
<body id="f3">
<h1>This is frame 3.</h1>
<p><a href="frame2.html">frame 2</a></p>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>CasperJS test frames</title>
</head>
<frameset cols="50%,50%%">
<frame src="frame1.html" name="frame1">
<frame src="frame2.html" name="frame2">
</frameset>
</html>

View File

@@ -4,6 +4,11 @@
<script type="text/javascript"> <script type="text/javascript">
var myGlobal = 'awesome string'; var myGlobal = 'awesome string';
var myUnencodableGlobal = document; var myUnencodableGlobal = document;
var myObject = {
foo: {
bar: 'baz'
}
};
</script> </script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,6 @@
(function() {
var elem = document.createElement('div');
elem.setAttribute('id', 'include1');
elem.appendChild(document.createTextNode('include1'));
document.querySelector('body').appendChild(elem);
})();

View File

@@ -0,0 +1,6 @@
(function() {
var elem = document.createElement('div');
elem.setAttribute('id', 'include2');
elem.appendChild(document.createTextNode('include2'));
document.querySelector('body').appendChild(elem);
})();

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>CasperJS test popup</title>
</head>
<a href="." class="close">close</a>
<body>
<a href="/tests/site/form.html" target="_blank">new window</a>
<script>
var w = window.open("http://localhost:54321/tests/site/index.html",
"popup", "menubar=no, status=no, scrollbars=no, menubar=no, width=400, height=300");
document.querySelector('a').onclick = function onclick(evt) {
evt.preventDefault();
w.close();
};
</script>
</body>
</html>

View File

@@ -22,5 +22,5 @@ casper.thenOpen('tests/site/index.html');
casper.run(function() { casper.run(function() {
this.removeListener('resource.requested', fetchUA); this.removeListener('resource.requested', fetchUA);
this.test.done(); this.test.done(3);
}); });

View File

@@ -0,0 +1,14 @@
/*global casper*/
/*jshint strict:false*/
var ok = false;
casper.on('remote.alert', function(message) {
ok = message === 'plop';
});
casper.start('tests/site/alert.html').run(function() {
this.test.assert(ok, 'alert event has been intercepted');
this.removeAllListeners('remote.alert');
this.test.done(1);
});

View File

@@ -0,0 +1,24 @@
/*global casper*/
/*jshint strict:false maxstatements:99*/
casper.start('tests/site/index.html');
casper.configureHttpAuth('http://localhost/');
casper.test.assertEquals(casper.page.settings.userName, undefined);
casper.test.assertEquals(casper.page.settings.password, undefined);
casper.configureHttpAuth('http://niko:plop@localhost/');
casper.test.assertEquals(casper.page.settings.userName, 'niko');
casper.test.assertEquals(casper.page.settings.password, 'plop');
casper.configureHttpAuth('http://localhost/', {username: 'john', password: 'doe'});
casper.test.assertEquals(casper.page.settings.userName, 'john');
casper.test.assertEquals(casper.page.settings.password, 'doe');
casper.configureHttpAuth('http://niko:plop@localhost/', {username: 'john', password: 'doe'});
casper.test.assertEquals(casper.page.settings.userName, 'niko');
casper.test.assertEquals(casper.page.settings.password, 'plop');
casper.run(function() {
this.test.done(8);
});

View File

@@ -13,21 +13,19 @@ casper.start('tests/site/index.html', function() {
this.test.assert(fs.isFile(testFile), 'Casper.capture() captured a screenshot'); this.test.assert(fs.isFile(testFile), 'Casper.capture() captured a screenshot');
}); });
if (phantom.version.major === 1 && phantom.version.minor >= 6) { casper.thenOpen('tests/site/index.html', function() {
casper.thenOpen('tests/site/index.html', function() { this.test.comment('Casper.captureBase64()');
this.test.comment('Casper.captureBase64()'); this.test.assert(this.captureBase64('png').length > 0,
this.test.assert(this.captureBase64('png').length > 0, 'Casper.captureBase64() rendered a page capture as base64');
'Casper.captureBase64() rendered a page capture as base64'); this.test.assert(this.captureBase64('png', 'ul').length > 0,
this.test.assert(this.captureBase64('png', 'ul').length > 0, 'Casper.captureBase64() rendered a capture from a selector as base64');
'Casper.captureBase64() rendered a capture from a selector as base64'); this.test.assert(this.captureBase64('png', {top: 0, left: 0, width: 30, height: 30}).length > 0,
this.test.assert(this.captureBase64('png', {top: 0, left: 0, width: 30, height: 30}).length > 0, 'Casper.captureBase64() rendered a capture from a clipRect as base64');
'Casper.captureBase64() rendered a capture from a clipRect as base64'); });
});
}
casper.run(function() { casper.run(function() {
try { try {
fs.remove(testFile); fs.remove(testFile);
} catch(e) {} } catch(e) {}
this.test.done(); this.test.done(4);
}); });

View File

@@ -1,5 +1,7 @@
/*global casper*/ /*global casper*/
/*jshint strict:false*/ /*jshint strict:false maxstatements: 99*/
var utils = require('utils');
casper.start('tests/site/index.html', function() { casper.start('tests/site/index.html', function() {
this.click('a[href="test.html"]'); this.click('a[href="test.html"]');
}); });
@@ -56,8 +58,26 @@ casper.then(function() {
this.mouse.move(200, 100); this.mouse.move(200, 100);
results = this.getGlobal('results'); results = this.getGlobal('results');
this.test.assertEquals(results.testmove, [200, 100], 'Mouse.move() has moved to the specified position'); this.test.assertEquals(results.testmove, [200, 100], 'Mouse.move() has moved to the specified position');
if (utils.gteVersion(phantom.version, '1.8.0')) {
this.test.comment('Mouse.doubleclick()');
this.mouse.doubleclick(200, 100);
results = this.getGlobal('results');
this.test.assertEquals(results.testdoubleclick, [200, 100],
'Mouse.doubleclick() double-clicked the specified position');
} else {
this.test.pass("Mouse.doubleclick() requires PhantomJS >= 1.8");
}
});
// element focus on click
casper.then(function() {
this.page.content = '<form><input type="text" name="foo"></form>'
this.click('form input[name=foo]')
this.page.sendEvent('keypress', 'bar');
this.test.assertEquals(this.getFormValues('form')['foo'], 'bar', 'Casper.click() sets the focus on clicked element');
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(23);
}); });

View File

@@ -1,23 +1,29 @@
/*global casper*/ /*global casper*/
/*jshint strict:false*/ /*jshint strict:false*/
// skip this test for phantom versions < 1.5 var received;
if (phantom.version.major === 1 && phantom.version.minor < 6) {
casper.test.comment('Skipped tests, PhantomJS 1.6 required');
casper.test.done();
} else {
var received;
casper.setFilter('page.confirm', function(message) {
received = message;
return true;
});
casper.start('tests/site/confirm.html', function() {
this.test.assert(this.getGlobal('confirmed'), 'confirmation dialog accepted');
});
casper.then(function() {
//remove the page.confirm event filter so we can add a new one
casper.removeAllFilters('page.confirm')
casper.setFilter('page.confirm', function(message) { casper.setFilter('page.confirm', function(message) {
received = message; return false;
return true;
}); });
});
casper.start('tests/site/confirm.html', function() { casper.thenOpen('/tests/site/confirm.html', function() {
this.test.assert(this.getGlobal('confirmed'), 'confirmation received'); this.test.assertNot(this.getGlobal('confirmed'), 'confirmation dialog canceled');
}); });
casper.run(function() { casper.run(function() {
this.test.assertEquals(received, 'are you sure?', 'confirmation message is ok'); this.test.assertEquals(received, 'are you sure?', 'confirmation message is ok');
this.test.done(); this.test.done(3);
}); });
}

View File

@@ -6,5 +6,5 @@ casper.start('tests/site/index.html', function() {
}); });
casper.run(function() { casper.run(function() {
casper.test.done(); casper.test.done(2);
}); });

View File

@@ -6,5 +6,5 @@ casper.start('tests/site/elementattribute.html', function() {
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(1);
}); });

View File

@@ -20,5 +20,5 @@ casper.start('file://' + phantom.casperPath + '/tests/site/index.html', function
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(2);
}); });

View File

@@ -1,5 +1,5 @@
/*global casper*/ /*global casper*/
/*jshint strict:false*/ /*jshint strict:false maxparams:99*/
casper.test.comment('Casper.evaluate()'); casper.test.comment('Casper.evaluate()');
casper.start(); casper.start();
@@ -32,4 +32,56 @@ casper.test.assertEquals(result.toString(),
['boolean', 'boolean', 'number', 'number', 'string', 'object', 'object', 'function'].toString(), ['boolean', 'boolean', 'number', 'number', 'string', 'object', 'object', 'function'].toString(),
'Casper.evaluate() handles passed argument context correcly'); 'Casper.evaluate() handles passed argument context correcly');
casper.test.done(); // no context
casper.test.assertEquals(casper.evaluate(function() {
return 42;
}), 42, 'Casper.evaluate() handles evaluation with no context passed');
// object context (previous casperjs versions compatibility mode)
casper.test.assertEquals(casper.evaluate(function(a) {
return [a];
}, {a: "foo"}), ["foo"], 'Casper.evaluate() accepts an object as arguments context');
casper.test.assertEquals(casper.evaluate(function(a, b) {
return [a, b];
}, {a: "foo", b: "bar"}), ["foo", "bar"], 'Casper.evaluate() accepts an object as arguments context');
casper.test.assertEquals(casper.evaluate(function(a, b, c) {
return [a, b, c];
}, {a: "foo", b: "bar", c: "baz"}), ["foo", "bar", "baz"], 'Casper.evaluate() accepts an object as arguments context');
// array context
casper.test.assertEquals(casper.evaluate(function(a) {
return [a];
}, ["foo"]), ["foo"], 'Casper.evaluate() accepts an array as arguments context');
casper.test.assertEquals(casper.evaluate(function(a, b) {
return [a, b];
}, ["foo", "bar"]), ["foo", "bar"], 'Casper.evaluate() accepts an array as arguments context');
casper.test.assertEquals(casper.evaluate(function(a, b, c) {
return [a, b, c];
}, ["foo", "bar", "baz"]), ["foo", "bar", "baz"], 'Casper.evaluate() accepts an array as arguments context');
// natural arguments context (phantomjs equivalent)
casper.test.assertEquals(casper.evaluate(function(a) {
return [a];
}, "foo"), ["foo"], 'Casper.evaluate() accepts natural arguments context');
casper.test.assertEquals(casper.evaluate(function(a, b) {
return [a, b];
}, "foo", "bar"), ["foo", "bar"], 'Casper.evaluate() accepts natural arguments context');
casper.test.assertEquals(casper.evaluate(function(a, b, c) {
return [a, b, c];
}, "foo", "bar", "baz"), ["foo", "bar", "baz"], 'Casper.evaluate() accepts natural arguments context');
casper.start().thenEvaluate(function(a, b) {
window.a = a
window.b = b;
}, "foo", "bar");
casper.then(function() {
this.test.comment('Casper.thenEvaluate()');
this.test.assertEquals(this.getGlobal('a'), "foo", "Casper.thenEvaluate() sets args");
this.test.assertEquals(this.getGlobal('b'), "bar",
"Casper.thenEvaluate() sets args the same way evaluate() does");
});
casper.run(function() {
this.test.done(13);
});

View File

@@ -37,4 +37,4 @@ casper.test.assertEquals(casper.foo, 42, "filter() applies the correct context")
delete casper.foo; delete casper.foo;
casper.test.done(); casper.test.done(5);

View File

@@ -7,5 +7,5 @@ casper.start('tests/site/index.html', function() {
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(1);
}); });

View File

@@ -7,5 +7,5 @@ casper.start('tests/site/index.html', function() {
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(1);
}); });

View File

@@ -35,4 +35,4 @@ casper.then ->
casper.then -> casper.then ->
@test.assertEquals ++step, 13, "last step" @test.assertEquals ++step, 13, "last step"
casper.run(-> @test.done()) casper.run(-> @test.done(13))

View File

@@ -41,6 +41,23 @@ casper.start('tests/site/form.html', function() {
!document.querySelector('input[name="checklist[]"][value="2"]').checked && !document.querySelector('input[name="checklist[]"][value="2"]').checked &&
document.querySelector('input[name="checklist[]"][value="3"]').checked); document.querySelector('input[name="checklist[]"][value="3"]').checked);
}, true, 'Casper.fill() can fill a list of checkboxes'); }, true, 'Casper.fill() can fill a list of checkboxes');
});
casper.then(function() {
this.test.comment('Casper.getFormValues()');
this.test.assertEquals(this.getFormValues('form'), {
"check": true,
"checklist[]": ["1", "3"],
"choice": "no",
"content": "Am watching thou",
"email": "chuck@norris.com",
"file": "C:\\fakepath\\README.md",
"password": "chuck",
"submit": "submit",
"topic": "bar"
}, 'Casper.getFormValues() retrieves filled values');
this.test.comment('submitting form');
this.click('input[type="submit"]'); this.click('input[type="submit"]');
}); });
@@ -54,6 +71,33 @@ casper.then(function() {
this.test.assertUrlMatch(/topic=bar/, 'Casper.fill() select field was submitted'); this.test.assertUrlMatch(/topic=bar/, 'Casper.fill() select field was submitted');
}); });
casper.thenOpen('tests/site/form.html', function() {
this.fill('form[action="result.html"]', {
email: 'chuck@norris.com',
password: 'chuck',
content: 'Am watching thou',
check: true,
choice: 'yes',
topic: 'bar',
file: phantom.libraryPath + '/README.md',
'checklist[]': ['1', '3']
});
});
casper.then(function() {
this.test.assertEquals(this.getFormValues('form'), {
"check": true,
"checklist[]": ["1", "3"],
"choice": "yes",
"content": "Am watching thou",
"email": "chuck@norris.com",
"file": "C:\\fakepath\\README.md",
"password": "chuck",
"submit": "submit",
"topic": "bar"
}, 'Casper.getFormValues() correctly retrieves values from radio inputs regardless of order');
});
casper.thenOpen('tests/site/form.html', function() { casper.thenOpen('tests/site/form.html', function() {
this.test.comment('Unexistent fields'); this.test.comment('Unexistent fields');
this.test.assertRaises(this.fill, ['form[action="result.html"]', { this.test.assertRaises(this.fill, ['form[action="result.html"]', {
@@ -67,12 +111,21 @@ casper.thenOpen('tests/site/multiple-forms.html', function() {
this.fill('form[name="f2"]', { this.fill('form[name="f2"]', {
yo: "ok" yo: "ok"
}, true); }, true);
}); }).then(function() {
casper.then(function() {
this.test.assertUrlMatch(/\?f=f2&yo=ok$/, 'Casper.fill() handles multiple forms'); this.test.assertUrlMatch(/\?f=f2&yo=ok$/, 'Casper.fill() handles multiple forms');
}); });
casper.run(function() { // issue #267: array syntax field names
this.test.done(); casper.thenOpen('tests/site/field-array.html', function() {
this.test.comment('Field arrays');
this.fill('form', {
'foo[bar]': "bar",
'foo[baz]': "baz"
}, true);
}).then(function() {
this.test.assertUrlMatch('?foo[bar]=bar&foo[baz]=baz', 'Casper.fill() handles array syntax field names');
});
casper.run(function() {
this.test.done(20);
}); });

View File

@@ -0,0 +1,43 @@
/*global casper __utils__*/
/*jshint strict:false*/
casper.start('tests/site/frames.html');
casper.withFrame('frame1', function() {
this.test.assertTitle('CasperJS frame 1');
this.test.assertExists("#f1");
this.test.assertDoesntExist("#f2");
this.test.assertEval(function() {
return '__utils__' in window && 'getBinary' in __utils__;
}, '__utils__ object is available in child frame');
this.test.assertMatches(this.page.frameContent, /This is frame 1/);
this.test.assertMatches(this.getHTML(), /This is frame 1/);
});
casper.withFrame('frame2', function() {
this.test.assertTitle('CasperJS frame 2');
this.test.assertExists("#f2");
this.test.assertDoesntExist("#f1");
this.test.assertEval(function() {
return '__utils__' in window && 'getBinary' in __utils__;
}, '__utils__ object is available in other child frame');
this.clickLabel('frame 3');
});
casper.withFrame('frame2', function() {
this.test.assertTitle('CasperJS frame 3');
});
casper.withFrame(0, function() {
this.test.assertTitle('CasperJS frame 1');
this.test.assertExists("#f1");
this.test.assertDoesntExist("#f2");
});
casper.withFrame(1, function() {
this.test.assertTitle('CasperJS frame 3');
});
casper.run(function() {
this.test.assertTitle('CasperJS test frames');
this.test.done(16);
});

View File

@@ -2,10 +2,14 @@
/*jshint strict:false*/ /*jshint strict:false*/
casper.start('tests/site/global.html', function() { casper.start('tests/site/global.html', function() {
this.test.comment('Casper.getGlobal()'); this.test.comment('Casper.getGlobal()');
this.test.assertEquals(this.getGlobal('myGlobal'), 'awesome string', 'Casper.getGlobal() can retrieve a remote global variable'); this.test.assertEquals(this.getGlobal('myGlobal'), 'awesome string',
this.test.assertRaises(this.getGlobal, ['myUnencodableGlobal'], 'Casper.getGlobal() does not fail trying to encode an unencodable global'); 'Casper.getGlobal() can retrieve a remote global variable');
this.test.assertEquals(this.getGlobal('myObject').foo.bar, 'baz',
'Casper.getGlobal() can retrieves a serializable object');
this.test.assertRaises(this.getGlobal, ['myUnencodableGlobal'],
'Casper.getGlobal() does not fail trying to encode an unserializable global');
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(3);
}); });

View File

@@ -37,5 +37,5 @@ casper.thenOpen('http://localhost:8090/', function thenLocalhost(response) {
casper.run(function() { casper.run(function() {
server.close(); server.close();
this.test.done(); this.test.done(4);
}); });

View File

@@ -19,5 +19,5 @@ casper.then(function() {
casper.run(function() { casper.run(function() {
this.test.assert(this.history.length > 0, 'Casper.history contains urls'); this.test.assert(this.history.length > 0, 'Casper.history contains urls');
this.test.assertMatch(this.history[0], /tests\/site\/page1\.html$/, 'Casper.history has the correct first url'); this.test.assertMatch(this.history[0], /tests\/site\/page1\.html$/, 'Casper.history has the correct first url');
this.test.done(); this.test.done(4);
}); });

View File

@@ -39,5 +39,5 @@ casper.then(function() {
casper.run(function() { casper.run(function() {
this.options.onAlert = null; this.options.onAlert = null;
this.test.done(); this.test.done(5);
}); });

View File

@@ -0,0 +1,15 @@
/*jshint strict:false*/
/*global CasperError casper console phantom require*/
casper.start('tests/site/form.html', function() {
this.sendKeys('input[name="email"]', 'duke@nuk.em');
this.sendKeys('textarea', "Damn, Im looking good.");
var values = this.getFormValues('form');
this.test.assertEquals(values['email'], 'duke@nuk.em',
'Casper.sendKeys() sends keys to given input');
this.test.assertEquals(values['content'], "Damn, Im looking good.",
'Casper.sendKeys() sends keys to given textarea');
});
casper.run(function() {
this.test.done(2);
});

View File

@@ -0,0 +1,23 @@
/*jshint strict:false*/
/*global CasperError casper console phantom require*/
var utils = require('utils')
if (utils.ltVersion(phantom.version, '1.8.0')) {
// https://github.com/n1k0/casperjs/issues/101
casper.warn('document.location is broken under phantomjs < 1.8');
casper.test.done();
} else {
casper.start('tests/site/index.html', function() {
this.evaluate(function() {
document.location = '/tests/site/form.html';
});
});
casper.then(function() {
this.test.assertUrlMatches(/form\.html$/);
});
casper.run(function() {
this.test.done();
});
}

View File

@@ -36,5 +36,5 @@ casper.then(function() {
casper.run(function() { casper.run(function() {
this.test.assertEquals(this.result.log.length, 3, 'Casper.log() logged messages'); this.test.assertEquals(this.result.log.length, 3, 'Casper.log() logged messages');
this.test.done(); this.test.done(4);
}); });

View File

@@ -25,5 +25,5 @@ casper.then(function() {
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(16);
}); });

View File

@@ -17,5 +17,5 @@ casper.thenOpen('tests/site/error.html', function() {
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(2);
}); });

View File

@@ -48,7 +48,7 @@ var t = casper.test, current = 0, tests = [
username: 'bob', username: 'bob',
password: 'sinclar' password: 'sinclar'
}, "Casper.thenOpen() used the expected HTTP auth settings"); }, "Casper.thenOpen() used the expected HTTP auth settings");
}, }
]; ];
casper.start(); casper.start();
@@ -131,5 +131,5 @@ casper.thenOpen('tests/site/index.html', {
casper.run(function() { casper.run(function() {
this.removeAllListeners('open'); this.removeAllListeners('open');
t.done(); t.done(16);
}); });

View File

@@ -0,0 +1,86 @@
/*jshint strict:false*/
/*global CasperError casper console phantom require*/
var utils = require('utils');
var x = require('casper').selectXPath;
casper.on('popup.created', function(popup) {
this.test.pass('"popup.created" event is fired');
this.test.assert(utils.isWebPage(popup),
'"popup.created" event callback get a popup page instance');
});
casper.on('popup.loaded', function(popup) {
this.test.pass('"popup.loaded" event is fired');
this.test.assertEquals(popup.evaluate(function() {
return document.title;
}), 'CasperJS test index',
'"popup.loaded" is triggered when popup content is actually loaded');
});
casper.on('popup.closed', function(popup) {
this.test.assertEquals(this.popups.length, 0, '"popup.closed" event is fired');
});
casper.start('tests/site/popup.html');
casper.waitForPopup('index.html', function() {
this.test.pass('Casper.waitForPopup() waits for a popup being created');
this.test.assertEquals(this.popups.length, 1, 'A popup has been added');
this.test.assert(utils.isWebPage(this.popups[0]), 'A popup is a WebPage');
});
casper.withPopup('index.html', function() {
this.test.assertUrlMatches(/index\.html$/,
'Casper.withPopup() switched to popup as current active one');
this.test.assertEval(function() {
return '__utils__' in window;
}, 'Casper.withPopup() has client utils injected');
this.test.assertExists('h1',
'Casper.withPopup() can perform assertions on the DOM');
this.test.assertExists(x('//h1'),
'Casper.withPopup() can perform assertions on the DOM using XPath');
});
casper.then(function() {
this.test.assertUrlMatches(/popup\.html$/,
'Casper.withPopup() has reverted to main page after using the popup');
});
casper.thenClick('.close', function() {
this.test.assertEquals(this.popups.length, 0, 'Popup is removed when closed');
});
casper.thenOpen('tests/site/popup.html');
casper.waitForPopup(/index\.html$/, function() {
this.test.pass('Casper.waitForPopup() waits for a popup being created');
});
casper.withPopup(/index\.html$/, function() {
this.test.assertTitle('CasperJS test index',
'Casper.withPopup() can use a regexp to identify popup');
});
casper.thenClick('.close', function() {
this.test.assertUrlMatches(/popup\.html$/,
'Casper.withPopup() has reverted to main page after using the popup');
this.test.assertEquals(this.popups.length, 0, 'Popup is removed when closed');
this.removeAllListeners('popup.created');
this.removeAllListeners('popup.loaded');
this.removeAllListeners('popup.closed');
});
casper.thenClick('a[target="_blank"]');
casper.waitForPopup('form.html', function() {
this.test.pass('Casper.waitForPopup() waits when clicked on a link with target=_blank');
});
casper.withPopup('form.html', function() {
this.test.assertTitle('CasperJS test form');
});
casper.run(function() {
// removes event listeners as they've now been tested already
this.test.done(25);
});

View File

@@ -1,19 +1,13 @@
/*global casper*/ /*global casper*/
/*jshint strict:false*/ /*jshint strict:false*/
// skip this test for phantom versions < 1.5 casper.setFilter('page.prompt', function(message, value) {
if (phantom.version.major === 1 && phantom.version.minor < 6) { return 'Chuck ' + value;
casper.test.comment('Skipped tests, PhantomJS 1.6 required'); });
casper.test.done();
} else {
casper.setFilter('page.prompt', function(message, value) {
return 'Chuck ' + value;
});
casper.start('tests/site/prompt.html', function() { casper.start('tests/site/prompt.html', function() {
this.test.assertEquals(this.getGlobal('name'), 'Chuck Norris', 'prompted value has been received'); this.test.assertEquals(this.getGlobal('name'), 'Chuck Norris', 'prompted value has been received');
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(1);
}); });
}

View File

@@ -0,0 +1,36 @@
/*global casper*/
/*jshint strict:false*/
function testHeader(header) {
return header.name === 'Accept' && header.value === 'application/json';
}
var t = casper.test, current = 0, tests = [
function(request) {
t.assertNot(request.headers.some(testHeader), "Casper.open() sets no custom header by default");
},
function(request) {
t.assert(request.headers.some(testHeader), "Casper.open() can set a custom header");
},
function(request) {
t.assertNot(request.headers.some(testHeader), "Casper.open() custom headers option is not persistent");
}
];
casper.on('page.resource.requested', function(request) {
tests[current++](request);
});
casper.start();
casper.thenOpen('tests/site/index.html');
casper.thenOpen('tests/site/index.html', {
headers: {
Accept: 'application/json'
}
});
casper.thenOpen('tests/site/index.html');
casper.run(function() {
this.removeAllListeners('page.resource.requested');
t.done(3);
});

View File

@@ -21,4 +21,4 @@ casper.start "tests/site/resources.html", ->
onTimeout = -> @test.fail "waitForResource timeout occured" onTimeout = -> @test.fail "waitForResource timeout occured"
@waitForResource "dummy.js", onTime, onTimeout @waitForResource "dummy.js", onTime, onTimeout
casper.run(-> @test.done()) casper.run(-> @test.done(5))

View File

@@ -0,0 +1,32 @@
/*global casper*/
/*jshint strict:false*/
casper.options.remoteScripts = [
'includes/include1.js', // local includes are actually served
'includes/include2.js', // through the local test webserver
'http://code.jquery.com/jquery-1.8.3.min.js'
];
casper.start('tests/site/index.html', function() {
this.test.assertSelectorHasText('#include1', 'include1',
'Casper.includeRemoteScripts() includes a first remote script on start');
this.test.assertSelectorHasText('#include2', 'include2',
'Casper.includeRemoteScripts() includes a second remote script on start');
this.test.assertEval(function() {
return 'jQuery' in window;
}, 'Casper.includeRemoteScripts() includes a really remote file on first step');
});
casper.thenOpen('tests/site/form.html', function() {
this.test.assertSelectorHasText('#include1', 'include1',
'Casper.includeRemoteScripts() includes a first remote script on second step');
this.test.assertSelectorHasText('#include2', 'include2',
'Casper.includeRemoteScripts() includes a second remote script on second step');
this.test.assertEval(function() {
return 'jQuery' in window;
}, 'Casper.includeRemoteScripts() includes a really remote file on second step');
});
casper.run(function() {
this.options.remoteScripts = [];
this.test.done(6);
});

View File

@@ -13,5 +13,5 @@ casper.start('tests/site/index.html', function() {
casper.test.assert(casper.started, 'Casper.start() started'); casper.test.assert(casper.started, 'Casper.start() started');
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(4);
}); });

View File

@@ -30,5 +30,5 @@ casper.each([1, 2, 3], function(self, item, i) {
}); });
casper.run(function() { casper.run(function() {
this.test.done(); this.test.done(8);
}); });

Some files were not shown because too many files have changed in this diff Show More