diff --git a/zephyr/tests/frontend/casperjs/.jshintconfig b/zephyr/tests/frontend/casperjs/.jshintconfig
index 87f894276c..7c4d5e4b91 100644
--- a/zephyr/tests/frontend/casperjs/.jshintconfig
+++ b/zephyr/tests/frontend/casperjs/.jshintconfig
@@ -9,6 +9,7 @@
"maxdepth": 3,
"maxstatements": 15,
"maxcomplexity": 7,
+ "proto": true,
"regexdash": true,
"strict": true,
"sub": true,
diff --git a/zephyr/tests/frontend/casperjs/.travis.yml b/zephyr/tests/frontend/casperjs/.travis.yml
index 375ec62857..8a9e7e086a 100644
--- a/zephyr/tests/frontend/casperjs/.travis.yml
+++ b/zephyr/tests/frontend/casperjs/.travis.yml
@@ -1,13 +1,16 @@
branches:
only:
- - master
+ - "master"
+ - "1.0"
before_script:
+ - "npm install -g jshint"
- "phantomjs --version"
- "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:
- - "DISPLAY=:99.0 ./bin/casperjs selftest"
+ - "./bin/casperjs selftest"
+ - "./bin/casperjs __selfcommandtest"
+after_script:
+ - "jshint --config=.jshintconfig ."
notifications:
irc:
channels:
diff --git a/zephyr/tests/frontend/casperjs/CHANGELOG.md b/zephyr/tests/frontend/casperjs/CHANGELOG.md
index 107a4f8c30..05cea5b9b3 100644
--- a/zephyr/tests/frontend/casperjs/CHANGELOG.md
+++ b/zephyr/tests/frontend/casperjs/CHANGELOG.md
@@ -1,6 +1,186 @@
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
----------------------
diff --git a/zephyr/tests/frontend/casperjs/CONTRIBUTORS.md b/zephyr/tests/frontend/casperjs/CONTRIBUTORS.md
index f17b71190c..6ff2e55f4e 100644
--- a/zephyr/tests/frontend/casperjs/CONTRIBUTORS.md
+++ b/zephyr/tests/frontend/casperjs/CONTRIBUTORS.md
@@ -3,44 +3,51 @@
You can check out the [contribution graphs on github](https://github.com/n1k0/casperjs/graphs/contributors).
```
-$ git shortlog -s -n
- 689 Nicolas Perriault
- 14 oncletom
- 14 Brikou CARRE
- 8 hannyu
- 6 Chris Lorenzo
- 4 pborreli
- 4 nrabinowitz
- 3 Andrew Childs
- 3 Solomon White
- 3 reina.sweet
- 2 Reina Sweet
- 2 Jason Funk
- 2 Michael Geers
- 2 Julien Moulin
- 2 Donovan Hutchinson
- 2 Clochix
- 1 Marcel Duran
- 1 Mathieu Agopian
- 1 Mehdi Kabab
- 1 Mikko Peltonen
- 1 Pascal Borreli
- 1 Rafael
- 1 Rafael Garcia
- 1 Raphaël Benitte
- 1 Andrew de Andrade
- 1 Tim Bunce
- 1 Victor Yap
- 1 alfetopito
- 1 Christophe Benz
- 1 jean-philippe serafin
- 1 Chris Winters
- 1 Ben Lowery
- 1 Jan Pochyla
- 1 Harrison Reiser
- 1 Julian Gruber
- 1 Justine Tunney
- 1 KaroDidi
- 1 Leandro Boscariol
- 1 Maisons du monde
+$ git shortlog -s -n | cut -c8-
+Nicolas Perriault
+Brikou CARRE
+oncletom
+hannyu
+Chris Lorenzo
+Victor Yap
+nrabinowitz
+pborreli
+Rob Barreca
+Andrew Childs
+Solomon White
+reina.sweet
+Dave Lee
+Reina Sweet
+Elmar Langholz
+Jason Funk
+Donovan Hutchinson
+Julien Moulin
+Michael Geers
+Jan Schaumann
+Clochix
+Raphaël Benitte
+Tim Bunce
+alfetopito
+jean-philippe serafin
+snkashis
+Andrew de Andrade
+Ben Lowery
+Chris Winters
+Christophe Benz
+Harrison Reiser
+Jan Pochyla
+Jan-Martin Fruehwacht
+Julian Gruber
+Justin Slattery
+Justine Tunney
+KaroDidi
+Leandro Boscariol
+Maisons du monde
+Marcel Duran
+Mathieu Agopian
+Mehdi Kabab
+Mikko Peltonen
+Pascal Borreli
+Rafael
+Rafael Garcia
```
diff --git a/zephyr/tests/frontend/casperjs/batchbin/casperjs.bat b/zephyr/tests/frontend/casperjs/batchbin/casperjs.bat
index 6f47552d18..dd86a98f26 100644
--- a/zephyr/tests/frontend/casperjs/batchbin/casperjs.bat
+++ b/zephyr/tests/frontend/casperjs/batchbin/casperjs.bat
@@ -1,22 +1,5 @@
@ECHO OFF
-set CASPER_PATH=%~dp0..\
-set CASPER_BIN=%CASPER_PATH%bin\
-
-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)
-
-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%
\ No newline at end of file
+set CASPER_PATH=%~dp0..
+set CASPER_BIN=%CASPER_PATH%\bin\
+set ARGV=%*
+call phantomjs "%CASPER_BIN%bootstrap.js" --casper-path="%CASPER_PATH%" --cli %ARGV%
\ No newline at end of file
diff --git a/zephyr/tests/frontend/casperjs/bin/bootstrap.js b/zephyr/tests/frontend/casperjs/bin/bootstrap.js
index d2816942a6..2e40c6109b 100755
--- a/zephyr/tests/frontend/casperjs/bin/bootstrap.js
+++ b/zephyr/tests/frontend/casperjs/bin/bootstrap.js
@@ -33,14 +33,21 @@
if (!phantom) {
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) {
- console.error('CasperJS needs at least PhantomJS v1.6.0 or later.');
+if (phantom.version.major === 1 && phantom.version.minor < 7) {
+ console.error('CasperJS needs at least PhantomJS v1.7 or later.');
phantom.exit(1);
} 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
@@ -106,10 +113,13 @@ function patchRequire(require, requireDirs) {
fileGuesses.push.apply(fileGuesses, [
testPath,
testPath + '.js',
+ testPath + '.json',
testPath + '.coffee',
fs.pathJoin(testPath, 'index.js'),
+ fs.pathJoin(testPath, 'index.json'),
fs.pathJoin(testPath, 'index.coffee'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.js'),
+ fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.json'),
fs.pathJoin(testPath, 'lib', fs.basename(testPath) + '.coffee')
]);
});
@@ -120,16 +130,25 @@ function patchRequire(require, requireDirs) {
}
}
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) {
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 = fs.read(file);
if (/\.coffee$/i.test(file)) {
/*global CoffeeScript*/
- scriptCode = CoffeeScript.compile(scriptCode);
+ try {
+ scriptCode = CoffeeScript.compile(scriptCode);
+ } catch (e) {
+ throw new Error('Unable to compile coffeescript:' + e);
+ }
}
return scriptCode;
})(file);
@@ -137,8 +156,14 @@ function patchRequire(require, requireDirs) {
try {
fn(file, _require, module, module.exports);
} 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.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;
}
requireCache[file] = module;
@@ -150,9 +175,20 @@ function patchRequire(require, requireDirs) {
function bootstrap(global) {
"use strict";
-
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.
*/
@@ -230,7 +266,7 @@ function bootstrap(global) {
throw new global.CasperError('Cannot read package file contents: ' + e);
}
parts = pkg.version.trim().split(".");
- if (parts < 3) {
+ if (parts.length < 3) {
throw new global.CasperError("Invalid version number");
}
patchPart = parts[2].split('-');
@@ -264,20 +300,21 @@ function bootstrap(global) {
*/
phantom.initCasperCli = function initCasperCli() {
var fs = require("fs");
+ var baseTestsPath = fs.pathJoin(phantom.casperPath, 'tests');
if (!!phantom.casperArgs.options.version) {
console.log(phantom.casperVersion.toString());
- phantom.exit(0);
+ return phantom.exit();
} 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.casperArgs.drop("test");
} else if (phantom.casperArgs.get(0) === "selftest") {
- phantom.casperScript = fs.absolute(fs.pathJoin(phantom.casperPath, 'tests', 'run.js'));
- phantom.casperSelfTest = true;
- phantom.casperArgs.options.includes = fs.pathJoin(phantom.casperPath, 'tests', 'selftest.js');
+ phantom.casperScript = fs.absolute(fs.pathJoin(baseTestsPath, 'run.js'));
+ phantom.casperSelfTest = phantom.casperTest = true;
+ phantom.casperArgs.options.includes = fs.pathJoin(baseTestsPath, 'selftest.js');
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");
} else if (phantom.casperArgs.args.length === 0 || !!phantom.casperArgs.options.help) {
@@ -287,25 +324,30 @@ function bootstrap(global) {
phantom.casperVersion.toString(),
phantom.casperPath, phantomVersion));
console.log(fs.read(fs.pathJoin(phantom.casperPath, 'bin', 'usage.txt')));
- phantom.exit(0);
+ return phantom.exit(0);
}
-
if (!phantom.casperScript) {
phantom.casperScript = phantom.casperArgs.get(0);
}
- if (phantom.casperScript) {
- if (!fs.isFile(phantom.casperScript)) {
- console.error('Unable to open file: ' + phantom.casperScript);
- phantom.exit(1);
- } else {
- // filter out the called script name from casper args
- phantom.casperArgs.drop(phantom.casperScript);
+ if (!fs.isFile(phantom.casperScript)) {
+ console.error('Unable to open file: ' + phantom.casperScript);
+ return phantom.exit(1);
+ }
- // passed casperjs script execution
- phantom.injectJs(phantom.casperScript);
- }
+ // filter out the called script name from casper args
+ 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');
}
};
diff --git a/zephyr/tests/frontend/casperjs/bin/casperjs b/zephyr/tests/frontend/casperjs/bin/casperjs
index c22baf0f38..16c1be57ef 100755
--- a/zephyr/tests/frontend/casperjs/bin/casperjs
+++ b/zephyr/tests/frontend/casperjs/bin/casperjs
@@ -1,8 +1,17 @@
#!/usr/bin/env python
+import json
import os
+import subprocess
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):
if os.path.islink(path):
@@ -32,9 +41,25 @@ PHANTOMJS_NATIVE_ARGS = [
'web-security',
]
CASPER_ARGS = []
+CASPER_PATH = os.path.abspath(os.path.join(os.path.dirname(resolve(__file__)), '..'))
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
for native in PHANTOMJS_NATIVE_ARGS:
if arg.startswith('--%s' % native):
@@ -43,7 +68,6 @@ for arg in sys.argv[1:]:
if not found:
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.extend(PHANTOMJS_ARGS)
CASPER_COMMAND.extend([
diff --git a/zephyr/tests/frontend/casperjs/bin/usage.txt b/zephyr/tests/frontend/casperjs/bin/usage.txt
index cc94438ae7..3b8595d22f 100644
--- a/zephyr/tests/frontend/casperjs/bin/usage.txt
+++ b/zephyr/tests/frontend/casperjs/bin/usage.txt
@@ -1,10 +1,11 @@
Usage: casperjs [options] script.[js|coffee] [script argument [script argument ...]]
casperjs [options] test [test path [test path ...]]
+ casperjs [options] selftest
Options:
--help Prints this help
--version Prints out CasperJS version
-Read the docs http://casperjs.org/
\ No newline at end of file
+Read the docs http://casperjs.org/
diff --git a/zephyr/tests/frontend/casperjs/casperjs.gemspec b/zephyr/tests/frontend/casperjs/casperjs.gemspec
index 301488431c..abe051e662 100644
--- a/zephyr/tests/frontend/casperjs/casperjs.gemspec
+++ b/zephyr/tests/frontend/casperjs/casperjs.gemspec
@@ -19,5 +19,5 @@ high-level functions, methods & syntaxic sugar for doing common tasks."
s.bindir = "rubybin"
s.executables = [ "casperjs" ]
s.license = "MIT"
- s.requirements = [ "PhantomJS v1.6" ]
+ s.requirements = [ "PhantomJS v1.7" ]
end
diff --git a/zephyr/tests/frontend/casperjs/modules/casper.js b/zephyr/tests/frontend/casperjs/modules/casper.js
index 1855d08876..8555f024fa 100644
--- a/zephyr/tests/frontend/casperjs/modules/casper.js
+++ b/zephyr/tests/frontend/casperjs/modules/casper.js
@@ -35,6 +35,7 @@ var events = require('events');
var fs = require('fs');
var http = require('http');
var mouse = require('mouse');
+var pagestack = require('pagestack');
var qs = require('querystring');
var tester = require('tester');
var utils = require('utils');
@@ -75,7 +76,7 @@ exports.selectXPath = selectXPath;
*/
var Casper = function Casper(options) {
"use strict";
- /*jshint maxstatements:30*/
+ /*jshint maxstatements:40*/
// init & checks
if (!(this instanceof Casper)) {
return new Casper(options);
@@ -110,6 +111,7 @@ var Casper = function Casper(options) {
localToRemoteUrlAccessEnabled: true,
userAgent: defaultUserAgent
},
+ remoteScripts: [],
stepTimeout: null,
timeout: null,
verbose: false,
@@ -138,6 +140,7 @@ var Casper = function Casper(options) {
this.mouse = mouse.create(this);
this.page = null;
this.pendingWait = false;
+ this.popups = pagestack.create();
this.requestUrl = 'about:blank';
this.resources = [];
this.result = {
@@ -154,12 +157,14 @@ var Casper = function Casper(options) {
this.initErrorHandler();
this.on('error', function(msg, backtrace) {
+ if (msg === this.test.SKIP_MESSAGE) {
+ return;
+ }
var c = this.getColorizer();
var match = /^(.*): __mod_error(.*):: (.*)/.exec(msg);
var notices = [];
if (match && match.length === 4) {
notices.push(' in module ' + match[2]);
- notices.push(' NOTICE: errors within modules cannot be backtraced yet.');
msg = match[3];
}
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) {
"use strict";
return this.evaluate(function _evaluate(url, method, data) {
- return window.__utils__.getBase64(url, method, data);
- }, { url: url, method: method, data: data });
+ return __utils__.getBase64(url, method, data);
+ }, url, method, data);
};
/**
@@ -382,7 +387,14 @@ Casper.prototype.clear = function clear() {
Casper.prototype.click = function click(selector) {
"use strict";
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);
};
+/**
+ * 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.
*
@@ -455,13 +493,13 @@ Casper.prototype.debugPage = function debugPage() {
*/
Casper.prototype.die = function die(message, status) {
"use strict";
- this.checkStarted();
this.result.status = "error";
this.result.time = new Date().getTime() - this.startTime;
if (!utils.isString(message) || !message.length) {
message = "Suite explicitely interrupted without any message given.";
}
this.log(message, "error");
+ this.echo(message, "ERROR");
this.emit('die', message, status);
if (utils.isFunction(this.options.onDie)) {
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('#password').value = password;
* document.querySelector('#submit').click();
- * }, {
- * username: 'Bazoonga',
- * password: 'baz00nga'
- * })
- *
- * FIXME: waiting for a patch of PhantomJS to allow direct passing of
- * arguments to the function.
+ * }, 'Bazoonga', 'baz00nga');
*
* @param Function fn The function to be evaluated within current page DOM
* @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) {
"use strict";
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
this.injectClientUtils();
// function context
- context = utils.isObject(context) ? context : {};
- // the way this works is kept for BC with older casperjs versions
- var args = Object.keys(context).map(function(arg) {
- return context[arg];
- });
- return this.page.evaluate.apply(this.page, [fn].concat(args));
+ if (arguments.length === 1) {
+ return this.page.evaluate(fn);
+ } else if (arguments.length === 2) {
+ // check for closure signature if it matches context
+ if (utils.isObject(context) && eval(fn).length === Object.keys(context).length) {
+ 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";
this.checkStarted();
return this.evaluate(function _evaluate(selector) {
- return window.__utils__.exists(selector);
- }, { selector: selector });
+ return __utils__.exists(selector);
+ }, selector);
};
/**
@@ -633,8 +677,8 @@ Casper.prototype.fetchText = function fetchText(selector) {
"use strict";
this.checkStarted();
return this.evaluate(function _evaluate(selector) {
- return window.__utils__.fetchText(selector);
- }, { selector: selector });
+ return __utils__.fetchText(selector);
+ }, selector);
};
/**
@@ -653,11 +697,8 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
}
this.emit('fill', selector, vals, submit);
var fillResults = this.evaluate(function _evaluate(selector, values) {
- return window.__utils__.fill(selector, values);
- }, {
- selector: selector,
- values: vals
- });
+ return __utils__.fill(selector, values);
+ }, selector, vals);
if (!fillResults) {
throw new CasperError("Unable to fill form");
} else if (fillResults.errors.length > 0) {
@@ -681,17 +722,17 @@ Casper.prototype.fill = function fill(selector, vals, submit) {
// Form submission?
if (submit) {
this.evaluate(function _evaluate(selector) {
- var form = window.__utils__.findOne(selector);
+ var form = __utils__.findOne(selector);
var method = (form.getAttribute('method') || "GET").toUpperCase();
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") {
form.submit();
} else {
// http://www.spiration.co.uk/post/1232/Submit-is-not-a-function
form.submit.click();
}
- }, { selector: selector });
+ }, selector);
}
};
@@ -732,7 +773,7 @@ Casper.prototype.getPageContent = function getPageContent() {
this.checkStarted();
var contentType = utils.getPropertyPath(this, 'currentResponse.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
var sanitizedHtml = this.evaluate(function checkHtml() {
@@ -742,7 +783,7 @@ Casper.prototype.getPageContent = function getPageContent() {
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
* @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";
this.checkStarted();
return this.evaluate(function _evaluate(selector, 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);
}
var clipRect = this.evaluate(function _evaluate(selector) {
- return window.__utils__.getElementBounds(selector);
- }, { selector: selector });
+ return __utils__.getElementBounds(selector);
+ }, selector);
if (!utils.isClipRect(clipRect)) {
throw new CasperError('Could not fetch boundaries for element matching selector: ' + selector);
}
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.
*
@@ -814,8 +873,25 @@ Casper.prototype.getElementsBounds = function getElementBounds(selector) {
throw new CasperError("No element matching selector found: " + selector);
}
return this.evaluate(function _evaluate(selector) {
- return window.__utils__.getElementsBounds(selector);
- }, { selector: selector });
+ return __utils__.getElementsBounds(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]);
} catch (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;
}
return result;
- }, {'name': name});
- if (typeof result !== "object") {
+ }, name);
+ if (!utils.isObject(result)) {
throw new CasperError(f('Could not retrieve global value for "%s"', name));
} else if ('error' in result) {
throw new CasperError(result.error);
} else if (utils.isString(result.value)) {
return JSON.parse(result.value);
- } else {
- return undefined;
}
};
@@ -861,7 +935,7 @@ Casper.prototype.getHTML = function getHTML(selector, outer) {
"use strict";
this.checkStarted();
if (!selector) {
- return this.page.content;
+ return this.page.frameContent;
}
if (!this.exists(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) {
var element = __utils__.findOne(selector);
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) {
"use strict";
+ /*jshint maxstatements:20*/
if (resource.stage !== "end") {
return;
}
@@ -902,6 +977,7 @@ Casper.prototype.handleReceivedResource = function(resource) {
this.currentHTTPStatus = null;
this.currentResponse = undefined;
if (utils.isHTTPResource(resource)) {
+ this.emit('page.resource.received', resource);
this.currentResponse = resource;
this.currentHTTPStatus = resource.status;
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
*/
@@ -965,7 +1041,7 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
"use strict";
this.checkStarted();
var clientUtilsInjected = this.page.evaluate(function() {
- return typeof window.__utils__ === "object";
+ return typeof __utils__ === "object";
});
if (true === clientUtilsInjected) {
return;
@@ -984,6 +1060,32 @@ Casper.prototype.injectClientUtils = function injectClientUtils() {
}.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.
*
@@ -1041,22 +1143,18 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
if (!this.exists(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);
- }, {
- type: type,
- 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;
- }
+ }, type, selector)) {
+ return true;
}
- 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
* - 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 Object settings The request settings (optional)
@@ -1074,16 +1172,12 @@ Casper.prototype.mouseEvent = function mouseEvent(type, selector) {
*/
Casper.prototype.open = function open(location, settings) {
"use strict";
+ /*jshint maxstatements:30*/
+ var baseCustomHeaders = this.page.customHeaders,
+ customHeaders = settings && settings.headers || {};
this.checkStarted();
- // settings validation
- if (!settings) {
- settings = {
- method: "get"
- };
- }
- if (!utils.isObject(settings)) {
- throw new CasperError("open(): request settings must be an Object");
- }
+ settings = utils.isObject(settings) ? settings : {};
+ settings.method = settings.method || "get";
// http method
// taken from https://github.com/ariya/phantomjs/blob/master/src/webpage.cpp#L302
var methods = ["get", "head", "put", "post", "delete"];
@@ -1101,28 +1195,21 @@ Casper.prototype.open = function open(location, settings) {
// clean location
location = utils.cleanUrl(location);
// current request url
+ this.configureHttpAuth(location, settings);
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.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, {
operation: settings.method,
- data: settings.data,
- headers: settings.headers
+ data: settings.data
}, this.page.settings);
- this.resources = [];
+ // revert base custom headers
+ this.page.customHeaders = baseCustomHeaders;
return this;
};
@@ -1203,8 +1290,7 @@ Casper.prototype.run = function run(onComplete, time) {
"use strict";
this.checkStarted();
if (!this.steps || this.steps.length < 1) {
- this.log("No steps defined, aborting", "error");
- return this;
+ throw new CasperError('No steps defined, aborting');
}
this.log(f("Running suite: %d step%s", this.steps.length, this.steps.length > 1 ? "s" : ""), "info");
this.emit('run.start');
@@ -1232,7 +1318,7 @@ Casper.prototype.runStep = function runStep(step) {
if ((self.test.currentSuiteNum + "-" + self.step) === stepNum) {
self.emit('step.timeout');
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);
@@ -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 password The HTTP_AUTH_PW value
+ * @param String selector A DOM CSS3 compatible selector
+ * @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
*/
Casper.prototype.setHttpAuth = function setHttpAuth(username, password) {
"use strict";
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.password = password;
- this.emit('http.auth', username, password);
- this.log("Setting HTTP authentication for user " + username, "info");
return this;
};
@@ -1279,10 +1396,12 @@ Casper.prototype.setHttpAuth = function setHttpAuth(username, password) {
*/
Casper.prototype.start = function start(location, then) {
"use strict";
+ /*jshint maxstatements:30*/
this.emit('starting');
this.log('Starting...', "info");
this.startTime = new Date().getTime();
this.history = [];
+ this.popups = pagestack.create();
this.steps = [];
this.step = 0;
// 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.options.logLevel = "warning";
}
- // WebPage
if (!utils.isWebPage(this.page)) {
- if (utils.isWebPage(this.options.page)) {
- this.page = this.options.page;
- } else {
- this.page = createPage(this);
- }
+ this.page = this.mainPage = utils.isWebPage(this.options.page) ? this.options.page : createPage(this);
}
this.page.settings = utils.mergeObjects(this.page.settings, this.options.pageSettings);
if (utils.isClipRect(this.options.clipRect)) {
@@ -1305,8 +1419,7 @@ Casper.prototype.start = function start(location, then) {
if (utils.isObject(this.options.viewportSize)) {
this.page.viewportSize = this.options.viewportSize;
}
- this.started = true;
- this.emit('started');
+ // timeout handling
if (utils.isNumber(this.options.timeout) && this.options.timeout > 0) {
this.log(f("Execution timeout set to %dms", this.options.timeout), "info");
setTimeout(function _check(self) {
@@ -1316,10 +1429,12 @@ Casper.prototype.start = function start(location, then) {
}
}, this.options.timeout, this);
}
+ this.started = true;
+ this.emit('started');
if (utils.isString(location) && location.length > 0) {
return this.thenOpen(location, utils.isFunction(then) ? then : this.createStep(function _step() {
this.log("start page is loaded", "debug");
- }));
+ }, {skipLog: true}));
}
return this;
};
@@ -1409,8 +1524,9 @@ Casper.prototype.thenClick = function thenClick(selector, then, fallbackToHref)
Casper.prototype.thenEvaluate = function thenEvaluate(fn, context) {
"use strict";
this.checkStarted();
+ var args = [fn].concat([].slice.call(arguments, 1));
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) {
"use strict";
- this.checkStarted();
- this.options.pageSettings.userAgent = this.page.settings.userAgent = agent;
+ this.options.pageSettings.userAgent = agent;
+ if (this.started && this.page) {
+ this.page.settings.userAgent = agent;
+ }
return this;
};
@@ -1510,8 +1628,8 @@ Casper.prototype.visible = function visible(selector) {
"use strict";
this.checkStarted();
return this.evaluate(function _evaluate(selector) {
- return window.__utils__.visible(selector);
- }, { selector: selector });
+ return __utils__.visible(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
*
@@ -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 Function then The next step to perform (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 String|RegExp pattern Text or RegExp to wait for
+ * @param Function then The next step to perform (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)
* @return Casper
*/
-Casper.prototype.waitForText = function(text, then, onTimeout, timeout) {
+Casper.prototype.waitForText = function(pattern, then, onTimeout, timeout) {
"use strict";
this.checkStarted();
timeout = timeout ? timeout : this.options.waitTimeout;
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);
};
@@ -1730,6 +1874,73 @@ Casper.prototype.waitWhileVisible = function waitWhileVisible(selector, then, on
}, 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.
*
@@ -1771,6 +1982,7 @@ exports.Casper = Casper;
* @return WebPage
*/
function createPage(casper) {
+ /*jshint maxstatements:20*/
"use strict";
var page = require('webpage').create();
page.onAlert = function onAlert(message) {
@@ -1781,17 +1993,21 @@ function createPage(casper) {
}
};
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) {
// 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) {
casper.echo(consoleTest[1]);
return; // don't trigger remote.message event for these
}
// 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) {
logLevel = logTest[1];
msg = logTest[2];
@@ -1803,9 +2019,9 @@ function createPage(casper) {
casper.emit('page.error', msg, trace);
};
page.onInitialized = function onInitialized() {
- casper.emit('page.initialized', this);
+ casper.emit('page.initialized', page);
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);
}
};
@@ -1814,6 +2030,7 @@ function createPage(casper) {
casper.emit('load.started');
};
page.onLoadFinished = function onLoadFinished(status) {
+ /*jshint maxstatements:20*/
if (status !== "success") {
casper.emit('load.failed', {
status: status,
@@ -1831,7 +2048,10 @@ function createPage(casper) {
casper.options.onLoadError.call(casper, casper, casper.requestUrl, status);
}
}
+ // local client scripts
casper.injectClientScripts();
+ // remote client scripts
+ casper.includeRemoteScripts();
// Client-side utils injection
casper.injectClientUtils();
// history
@@ -1847,6 +2067,17 @@ function createPage(casper) {
}
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) {
return casper.filter('page.prompt', message, value);
};
@@ -1860,6 +2091,9 @@ function createPage(casper) {
};
page.onResourceRequested = function onResourceRequested(request) {
casper.emit('resource.requested', request);
+ if (request.url === casper.requestUrl) {
+ casper.emit('page.resource.requested', request);
+ }
if (utils.isFunction(casper.options.onResourceRequested)) {
casper.options.onResourceRequested.call(casper, casper, request);
}
diff --git a/zephyr/tests/frontend/casperjs/modules/clientutils.js b/zephyr/tests/frontend/casperjs/modules/clientutils.js
index 2582186373..45cf3df81c 100644
--- a/zephyr/tests/frontend/casperjs/modules/clientutils.js
+++ b/zephyr/tests/frontend/casperjs/modules/clientutils.js
@@ -41,6 +41,7 @@
* Casper client-side helpers.
*/
exports.ClientUtils = function ClientUtils(options) {
+ /*jshint maxstatements:40*/
// private members
var BASE64_ENCODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
var BASE64_DECODE_CHARS = new Array(
@@ -200,6 +201,7 @@
* @return Object An object containing setting result for each field, including file uploads
*/
this.fill = function fill(form, vals) {
+ /*jshint maxcomplexity:8*/
var out = {
errors: [],
fields: [],
@@ -260,7 +262,7 @@
try {
var pSelector = this.processSelector(selector);
if (pSelector.type === 'xpath') {
- return this.getElementsByXPath(pSelector.path);
+ return this.getElementsByXPath(pSelector.path, scope);
} else {
return scope.querySelectorAll(pSelector.path);
}
@@ -281,7 +283,7 @@
try {
var pSelector = this.processSelector(selector);
if (pSelector.type === 'xpath') {
- return this.getElementByXPath(pSelector.path);
+ return this.getElementByXPath(pSelector.path, scope);
} else {
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.
*
- * @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
*/
- this.getElementByXPath = function getElementByXPath(expression) {
- var a = document.evaluate(expression, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ this.getElementByXPath = function getElementByXPath(expression, scope) {
+ scope = scope || this.options.scope;
+ var a = document.evaluate(expression, scope, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (a.snapshotLength > 0) {
return a.snapshotItem(0);
}
@@ -406,12 +437,14 @@
/**
* 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
*/
- this.getElementsByXPath = function getElementsByXPath(expression) {
+ this.getElementsByXPath = function getElementsByXPath(expression, scope) {
+ scope = scope || this.options.scope;
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++) {
nodes.push(a.snapshotItem(i));
}
@@ -445,7 +478,7 @@
if (type === 'radio') {
var value;
[].forEach.call(inputs, function(radio) {
- value = radio.checked ? radio.value : undefined;
+ value = radio.checked ? radio.value : value;
});
return value;
} 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
* to log phantomjs side.
@@ -569,24 +621,21 @@
* Performs an AJAX request.
*
* @param String url Url.
- * @param String method HTTP method.
+ * @param String method HTTP method (default: GET).
* @param Object data Request parameters.
* @param Boolean async Asynchroneous request? (default: false)
* @return String Response text.
*/
this.sendAJAX = function sendAJAX(url, method, data, async) {
- var xhr = new XMLHttpRequest(), dataString = "";
- if (typeof method !== "string" || ["GET", "POST"].indexOf(method.toUpperCase()) === -1) {
- method = "GET";
- } else {
- method = method.toUpperCase();
- }
+ var xhr = new XMLHttpRequest(),
+ dataString = "",
+ dataList = [];
+ method = method && method.toUpperCase() || "GET";
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");
if (method === "POST") {
if (typeof data === "object") {
- var dataList = [];
for (var k in data) {
dataList.push(encodeURIComponent(k) + "=" + encodeURIComponent(data[k].toString()));
}
@@ -595,7 +644,7 @@
} else if (typeof data === "string") {
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);
return xhr.responseText;
@@ -609,6 +658,7 @@
* @param mixed value The field value to set
*/
this.setField = function setField(field, value) {
+ /*jshint maxcomplexity:99 */
var logValue, fields, out;
value = logValue = (value || "");
if (field instanceof NodeList) {
@@ -694,10 +744,14 @@
out = 'Unsupported field type: ' + nodeName;
break;
}
- // firing the `change` event
- var changeEvent = document.createEvent("HTMLEvents");
- changeEvent.initEvent('change', true, true);
- field.dispatchEvent(changeEvent);
+
+ // firing the `change` and `input` events
+ ['change', 'input'].forEach(function(name) {
+ var event = document.createEvent("HTMLEvents");
+ event.initEvent(name, true, true);
+ field.dispatchEvent(event);
+ });
+
// blur the field
try {
field.blur();
diff --git a/zephyr/tests/frontend/casperjs/modules/colorizer.js b/zephyr/tests/frontend/casperjs/modules/colorizer.js
index 99d587dcc6..8a7babd25a 100644
--- a/zephyr/tests/frontend/casperjs/modules/colorizer.js
+++ b/zephyr/tests/frontend/casperjs/modules/colorizer.js
@@ -64,7 +64,8 @@ var Colorizer = function Colorizer() {
'WARNING': { fg: 'red', bold: true },
'GREEN_BAR': { fg: 'white', bg: 'green', 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 }
};
/**
diff --git a/zephyr/tests/frontend/casperjs/modules/events.js b/zephyr/tests/frontend/casperjs/modules/events.js
index 8840957bc6..f0db39e4c1 100644
--- a/zephyr/tests/frontend/casperjs/modules/events.js
+++ b/zephyr/tests/frontend/casperjs/modules/events.js
@@ -19,6 +19,8 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
+/*global CasperError*/
+
var isArray = Array.isArray;
function EventEmitter() {
@@ -230,6 +232,17 @@ EventEmitter.prototype.filter = function() {
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) {
if (!this._filters) {
this._filters = {};
diff --git a/zephyr/tests/frontend/casperjs/modules/http.js b/zephyr/tests/frontend/casperjs/modules/http.js
index 33030da321..1b072fe46f 100644
--- a/zephyr/tests/frontend/casperjs/modules/http.js
+++ b/zephyr/tests/frontend/casperjs/modules/http.js
@@ -62,6 +62,7 @@ responseHeaders.prototype.get = function get(name){
*/
exports.augmentResponse = function(response) {
"use strict";
+ /*jshint proto:true*/
if (!utils.isHTTPResource(response)) {
return;
}
diff --git a/zephyr/tests/frontend/casperjs/modules/injector.js b/zephyr/tests/frontend/casperjs/modules/injector.js
deleted file mode 100644
index 43c004952c..0000000000
--- a/zephyr/tests/frontend/casperjs/modules/injector.js
+++ /dev/null
@@ -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;
diff --git a/zephyr/tests/frontend/casperjs/modules/mouse.js b/zephyr/tests/frontend/casperjs/modules/mouse.js
index 93ee2aeae4..8a011a86b7 100644
--- a/zephyr/tests/frontend/casperjs/modules/mouse.js
+++ b/zephyr/tests/frontend/casperjs/modules/mouse.js
@@ -43,11 +43,13 @@ var Mouse = function Mouse(casper) {
throw new CasperError('Mouse() needs a Casper instance');
}
- var slice = Array.prototype.slice;
-
- var nativeEvents = ['mouseup', 'mousedown', 'click', 'mousemove'];
- var emulatedEvents = ['mouseover', 'mouseout'];
- var supportedEvents = nativeEvents.concat(emulatedEvents);
+ var slice = Array.prototype.slice,
+ nativeEvents = ['mouseup', 'mousedown', 'click', 'mousemove'];
+ if (utils.gteVersion(phantom.version, '1.8.0')) {
+ nativeEvents.push('doubleclick');
+ }
+ var emulatedEvents = ['mouseover', 'mouseout'],
+ supportedEvents = nativeEvents.concat(emulatedEvents);
function computeCenter(selector) {
var bounds = casper.getElementBounds(selector);
@@ -72,8 +74,7 @@ var Mouse = function Mouse(casper) {
throw new CasperError('Mouse.processEvent(): Too few arguments');
case 1:
// selector
- var selector = args[0];
- casper.page.sendEvent.apply(casper.page, [type].concat(computeCenter(selector)));
+ casper.page.sendEvent.apply(casper.page, [type].concat(computeCenter(args[0])));
break;
case 2:
// coordinates
@@ -95,6 +96,10 @@ var Mouse = function Mouse(casper) {
processEvent('click', arguments);
};
+ this.doubleclick = function doubleclick() {
+ processEvent('doubleclick', arguments);
+ };
+
this.down = function down() {
processEvent('mousedown', arguments);
};
diff --git a/zephyr/tests/frontend/casperjs/modules/pagestack.js b/zephyr/tests/frontend/casperjs/modules/pagestack.js
new file mode 100644
index 0000000000..3c7f7a381d
--- /dev/null
+++ b/zephyr/tests/frontend/casperjs/modules/pagestack.js
@@ -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 '
diff --git a/zephyr/tests/frontend/casperjs/tests/site/field-array.html b/zephyr/tests/frontend/casperjs/tests/site/field-array.html new file mode 100644 index 0000000000..0d64d96186 --- /dev/null +++ b/zephyr/tests/frontend/casperjs/tests/site/field-array.html @@ -0,0 +1,14 @@ + + +
+ +
+ +
+