+++
title = "phantomjs"
draft = false
tags = [
"code",
"javascript",
"testing",
"phantomjs"
]
date = "2013-03-12"
+++
# Test automatisatie: PhantomJS
Een experiment om mijn `JasmineTestRunner` uit te breiden (of volledig op te splitsen) van **Rhino + Envjs 1.2** naar **PhantomJS 1.4** (liefst 1.5 maar dit moet ik nog compilerend krijgen onder Windows).
### Phantom.js test code
#### Start script
```javascript
console.log("loading");
var page = new WebPage();
// Is nodig omdat anders in uw testpagina console.log() niets doet.
page.onConsoleMessage = function(msg) {
console.log("debug from page: " + msg);
};
/**
* Phantom JS 1.4 tests voor EnvJS "fixes" die ik heb moeten erin hacken.
* Alles werkt blijkbaar native, hoera!
*
* TODO:
* -----
* # Waarom werken relatieve paden niet met openen van de pagina (crash Phantom)
* # Waarom werken script includes niet, lokaal op relatief pad? Absoluut = access denied
* -> Een HTTP pad opgeven werkt wel, zoals jQuery.com include.
* -> Zolang dit niet goed werkt kunnen we niet naar de jasmine HTML suite gaan!
* # testen van set/clearTimeout en set/clearInterval
* # Heb ik Rhino dan nog wel nodig? Hoe kan ik phantomjs output evalueren en dan in een TestRunner knallen?
*
* Jasmine integratie
* ------------------
* Custom XML reporters, zie
* - http://code.google.com/p/phantomjs/wiki/ExternalArticles
* - http://code.google.com/p/phantomjs/wiki/TestFrameworkIntegration
* - https://github.com/detro/phantomjs-jasminexml-example
**/
page.open('D:
Profiles
BVERBEKE
Desktop
phantomjs-1.4.1-win32-dynamic
test.html', function(status) {
console.log("should have printed 'yoo' AND 'sup?', include from test.js??");
console.log("");
var testHTML = page.evaluate(function() {
return document.getElementById('test').innerHTML;
});
var isKnopHidden = page.evaluate(function() {
return $("#hiddenKnop").is(":hidden");
});
var whichIsFocussed = page.evaluate(function() {
return document.activeElement.id;
});
var divHeightCSSGetterTest = page.evaluate(function() {
return $("#test").css("height");
});
var jQueryCheckedSelectorTest = page.evaluate(function() {
$("#check").click();
return $("#check").is(":checked");
});
console.log("got testHTML " + testHTML);
console.log("is knop hidden? (should be) --> " + isKnopHidden);
console.log("which is focussed? (should be textarea) --> #" + whichIsFocussed);
console.log("div height? (should be 100px) --> " + divHeightCSSGetterTest);
console.log("checkbox checked? (should be true) --> " + jQueryCheckedSelectorTest);
phantom.exit();
});
```
#### Test.js script
```javascript
console.log("supp??");
```
#### Test.html resources
```html
bla
```
#### Console Output bovenstaande
```
D:
Profiles
BVERBEKE
Desktop
phantomjs-1.4.1-win32-dynamic>phantomjs start.js
loading
debug from page: yoo
debug from page: is knop hidden? (should be) --> true
should have printed 'yoo' AND 'sup?', include from test.js??
got testHTML jquery nieuwe blabla
is knop hidden? (should be) --> true
which is focussed? (should be textarea) --> #text
div height? (should be 100px) --> 100px
checkbox checked? (should be true) --> true
```
### Phantom.js uitvoeren
Downloaden (1.4 voor windows...) op http://code.google.com/p/phantomjs/downloads/list, zit Qt4 libs in. Dan met:
-> `phantomjs start.js`
Die dan met `page.open()` een webpagina inlaadt. Blijkbaar werkt `` tags evaluaten nog niet voor bepaalde paden?
#### Relatieve paden probleem
Mogelijke oorzaak: `Qt 4.8.0` die URLs anders behandelt. Wordt gerevert vanaf `4.8.1` en zou ook moeten werken met `4.7.0`. Vereist zelf compileren van PhantomJS...
Zie ook
1. http://code.google.com/p/phantomjs/issues/detail?id=231 en
2. http://stackoverflow.com/questions/9261803/phantomjs-is-not-loading-scripts-correctly-from-html-page-with-tests
Mottige tijdelijke oplossing:
```javascript
page.open("url", function() {
page.injectJs("filename"); // rel.path is OK here.
});
```
##### Oplossing in Windows: werk met absoluut path dat de base HTML include
:exclamation: Bovenstaande `.page.open(absoluteUrl)` vervangen door:
```javascript
page.open(require('fs').absolute('test.html'), function() {});
```
Vanaf dan werken ook relatieve `` tag includes!
### Evalueren in page context en in Phantom context
`page.evaluate()` wordt in een sandbox uitgevoerd, geen mogelijkheid tot closure scope én geen mogelijkheid tot toegang van het `phantom` object. Hoe kunnen we dan argumenten meegeven, of objecten of functies?
Hier is een "hackje" voor:
```javascript
function evaluate(page, func) {
var args = [].slice.call(arguments, 2);
var str = 'function() { return (' + func.toString() + ')(';
for (var i ###### 0, l args.length; i < l; i++) {
var arg = args[i];
if (/object|string/.test(typeof arg)) {
str += 'JSON.parse(' + JSON.stringify(JSON.stringify(arg)) + '),';
} else {
str += arg + ',';
}
}
str = str.replace(/,$/, '); }');
return page.evaluate(str);
}
var page = require('webpage').create();
var func = function() {
console.log('hello, ' + document.title + '
n');
for (var i ###### 0, l arguments.length; i < l; i++) {
var arg = arguments[i];
console.log(typeof arg + ':
t' + arg);
}
};
page.onLoadFinished = function() {
evaluate(page, func, true, 0, function() {
return require('fs').read
});
phantom.exit(0);
};
page.open('http://www.google.com/');
```
Wat helaas NIET werkt is het volgende:
```javascript
var func = function() {
arguments[0]('bla.html');
}
page.onLoadFinished = function() {
var read = require('fs').read;
evaluate(page, func, read);
};
```
Waarom niet? `ReferenceError: Can't find variable: require`.
Hoe kan ik dan `Phantom`/`commonJs` variabelen gebruiken? Voorlopig niet? Feature request voor 1.6 - zie http://code.google.com/p/phantomjs/issues/detail?id=132
### PhantomJS en jQuery Ajax
Werkt maar niet in `async: false` mode. Zie http://code.google.com/p/phantomjs/issues/detail?id######463&thanks463&ts=1332850556
```javascript
page.evaluate(function() {
console.log("ajaxying");
jQuery.ajax({
async: false,
dataType: 'html',
url: 'test.html',
success: function(data) {
console.log("--success!!");
console.log(data);
console.log("--");
}
});
});
```
Print:
```
--success!!
--
```
?? Async mode moet uit staan voor jasmine fixtures te kunnen laden...
### Phantomjs en Jasmine integratie
Werkend met **jasmine 1.1.0**: zie uitgewerkt voorbeeld op https://github.com/detro/phantomjs-jasminexml-example/
Het komt basically hierop neer:
* een js file die de suite.html inleest en wacht tot jasmine klaar is met runnen
* een custom reporter van jasmine die JUnit XML genereert. Wanneer dit klaar is schrijft de suite.html opener met phantom code de file weg.
* een maven plugin om de executable te draaien en de XML naar de juiste `surefire-reports` directory weg te schrijven.
#### in pom.xml
```xml
org.codehaus.mojo
exec-maven-plugin
1.1
jsunit
test
exec
phantomjs.exe
${project.js.test.directory}/lib
run-jasmine.js
./../suite.html
${project.build.directory}/surefire-reports
```
### Phantomjs en QUnit integratie
Geïntegreerd met maven:
1. http://kennychua.net/blog/running-qunit-tests-in-a-maven-continuous-integration-build-with-phantomjs
1. http://code.google.com/p/phantomjs-qunit-runner/
#### Fixtures inladen
Voor zover de QUnit documentatie aanwezig is, zijn fixtures child DOM elementen van `#fixtures`, een div die buiten het scherm float:
> The #qunit-fixture element can be used to provide and manipulate test markup, and it's content will be automatically reset after each test (see QUnit.reset). The element is styled with position:absolute; top:-10000px; left:-10000; - with these, it won't be obstructing the result, without affecting code the relies on the affected elements to be visible (instead of display:none).
Wordt dus automatisch gereset, maar we hebben dan ook `jQuery.ajax` nodig om externe files in te laden -> zelfde probleem!