Testen von JavaScript mit PhantomJS
German (Deutsch) translation by Alex Grigorovich (you can also view the original English article)
Ich glaube nicht, dass ich Sie davon überzeugen muss, dass das Testen Ihres JavaScript-Codes eine gute Idee ist. Es kann sich jedoch manchmal als mühsam erweisen, JavaScript-Code zu testen, für den ein DOM erforderlich ist. Dies bedeutet, dass Sie Ihren Code im Browser testen müssen und das Terminal nicht verwenden können, oder? Eigentlich falsch: Geben Sie PhantomJS ein.
Was genau ist PhantomJS? Hier ist ein Klappentext von der PhantomJS-Website:
PhantomJS ist ein kopfloses WebKit mit JavaScript-API.
Wie Sie wissen, ist Webkit die Layout-Engine, die Chrome, Safari und einige andere Nischenbrowser verwenden. PhantomJS ist also ein Browser, aber ein kopfloser Browser. Dies bedeutet, dass die gerenderten Webseiten niemals tatsächlich angezeigt werden. Das mag für dich komisch klingen; Sie können sich das also als programmierbaren Browser für das Terminal vorstellen. Wir werden uns in einer Minute ein einfaches Beispiel ansehen, aber wir müssen zuerst PhantomJS installieren.
PhantomJS installieren
Die Installation von PhantomJS ist eigentlich ziemlich einfach: Es ist nur eine einzelne Binärdatei, die Sie herunterladen und in Ihren Terminalpfad einfügen. Wählen Sie auf der PhantomJS-Downloadseite Ihr Betriebssystem aus und laden Sie das richtige Paket herunter. Verschieben Sie dann die Binärdatei aus dem heruntergeladenen Paket in ein Verzeichnis in Ihrem Terminalpfad (ich möchte so etwas in ~/bin einfügen).
Wenn Sie unter Mac OS X arbeiten, gibt es eine einfachere Möglichkeit, PhantomJS zu installieren (und dies ist tatsächlich die Methode, die ich verwendet habe). Verwenden Sie einfach Homebrew wie folgt:
1 |
brew update && brew install phantomjs |
Sie sollten jetzt PhantomJS installieren. Sie können Ihre Installation überprüfen, indem Sie Folgendes ausführen:
1 |
phantomjs --version
|
Ich sehe 1.7.0 und Sie?
Ein kleines Beispiel
Beginnen wir mit einem kleinen Beispiel.
simple.js
1 |
console.log("we can log stuff out."); |
2 |
|
3 |
function add(a, b) { |
4 |
return a + b; |
5 |
}
|
6 |
|
7 |
conslole.log("We can execute regular JS too:", add(1, 2)); |
8 |
|
9 |
phantom.exit(); |
Führen Sie den folgenden Befehl aus, und führen Sie diesen Code aus:
1 |
phantomjs simple.js |
Sie sollten die Ausgabe der beiden Zeilen console.log in Ihrem Terminalfenster sehen.
Sicher, das ist einfach, aber es macht einen guten Punkt: PhantomJS kann JavaScript genau wie einen Browser ausführen. Dieses Beispiel enthält jedoch keinen PhantomJS-spezifischen Code ... abgesehen von der letzten Zeile. Dies ist eine wichtige Zeile für jedes PhantomJS-Skript, da es das Skript beendet. Dies ist hier möglicherweise nicht sinnvoll, aber denken Sie daran, dass JavaScript nicht immer linear ausgeführt wird. Beispielsweise möchten Sie möglicherweise den Aufruf exit() in eine Rückruffunktion einfügen.
Schauen wir uns ein komplexeres Beispiel an.
Seiten laden
Mit der PhantomJS-API können wir tatsächlich jede URL laden und mit der Seite aus zwei Perspektiven arbeiten:
- als JavaScript auf der Seite.
- als Benutzer auf der Seite.
Beginnen wir mit dem Laden einer Seite. Erstellen Sie eine neue Skriptdatei und fügen Sie den folgenden Code hinzu:
script.js
1 |
var page = require('webpage').create(); |
2 |
|
3 |
page.open('http://net.tutsplus.com', function (s) { |
4 |
console.log(s); |
5 |
phantom.exit(); |
6 |
});
|
Wir beginnen mit dem Laden des webpage moduls von PhantomJS und dem Erstellen eines Webseitenobjekts. Wir rufen dann die open-Methode auf und übergeben ihr eine URL und eine Rückruffunktion. Innerhalb dieser Rückruffunktion können wir mit der eigentlichen Seite interagieren. Im obigen Beispiel protokollieren wir nur den Status der Anforderung, der durch den Parameter der Rückruffunktion bereitgestellt wird. Wenn Sie dieses Skript ausführen (mit phantomjs script.js), sollte im Terminal "Erfolg" gedruckt werden.
Aber machen wir das interessanter, indem wir eine Seite laden und etwas JavaScript darauf ausführen. Wir beginnen mit dem obigen Code, rufen dann aber page.evaluate auf:
1 |
page.open('http://net.tutsplus.com', function () { |
2 |
var title = page.evaluate(function () { |
3 |
var posts = document.getElementsByClassName("post"); |
4 |
posts[0].style.backgroundColor = "#000000"; |
5 |
return document.title; |
6 |
});
|
7 |
page.clipRect = { top: 0, left: 0, width: 600, height: 700 }; |
8 |
page.render(title + ".png"); |
9 |
phantom.exit(); |
10 |
});
|
PhantomJS ist ein Browser, aber ein kopfloser Browser.
Die Funktion, die wir an page.evaluate übergeben, wird auf der geladenen Webseite als JavaScript ausgeführt. In diesem Fall finden wir alle Elemente mit der Post-Klasse. Dann setzen wir den Hintergrund des ersten Beitrags auf Schwarz. Schließlich geben wir den document.title zurück. Dies ist eine nette Funktion, die einen Wert aus unserem evaluate zurückgibt und ihn einer Variablen (in diesem Fall title) zuweist.
Dann setzen wir den clipRect auf der Seite; Dies sind die Abmessungen für den Screenshot, den wir mit der render methode aufnehmen. Wie Sie sehen können, legen wir die top und left Werte fest, um den Startpunkt festzulegen, und wir legen auch eine width und height fest. Schließlich rufen wir page.render auf und übergeben ihm einen Namen für die Datei (die title variable). Zum Schluss rufen wir phantom.exit() auf.
Führen Sie dieses Skript aus, und Sie sollten ein Bild haben, das ungefähr so aussieht:


Sie können beide Seiten der PhantomJS-Münze hier sehen: Wir können JavaScript von innerhalb der Seite und auch von außen auf der Seiteninstanz selbst ausführen.
Das hat Spaß gemacht, ist aber nicht unglaublich nützlich. Konzentrieren wir uns beim Testen unseres DOM-bezogenen JavaScript auf die Verwendung von PhantomJS.
Testen mit PhantomJS
Yeoman verwendet PhantomJS in seinem Testverfahren und es ist praktisch nahtlos.
Für viele JavaScript-Codes können Sie ohne DOM testen, aber manchmal müssen Ihre Tests mit HTML-Elementen arbeiten. Wenn Sie wie ich sind und lieber Tests über die Befehlszeile ausführen möchten, kommt PhantomJS hier ins Spiel.
Natürlich ist PhantomJS keine Testbibliothek, aber viele der anderen gängigen Testbibliotheken können auf PhantomJS ausgeführt werden. Wie Sie auf der PhantomJS-Wiki-Seite zum Thema Headless-Tests sehen können, sind PhantomJS-Testläufer für nahezu jede Testbibliothek verfügbar, die Sie möglicherweise verwenden möchten. Schauen wir uns an, wie PhantomJS mit Jasmine und Mocha verwendet wird.
Zunächst Jasmine und ein Haftungsausschluss: Derzeit gibt es keinen guten PhantomJS-Läufer für Jasmine. Wenn Sie Windows und Visual Studio verwenden, sollten Sie sich Chutzpah ansehen, und Rails-Entwickler sollten Guard-Jasmin ausprobieren. Abgesehen davon ist die Unterstützung von Jasmine + PhantomJS spärlich.
Aus diesem Grund empfehle ich Ihnen, Mocha für DOM-bezogene Tests zu verwenden.
ALLERDINGS.
Möglicherweise haben Sie bereits ein Projekt mit Jasmine und möchten es mit PhantomJS verwenden. Ein Projekt, Phantom-Jasmin, erfordert ein wenig Arbeit, aber es sollte den Trick machen.
Beginnen wir mit einer Reihe von JasmineJS-Tests. Laden Sie den Code für dieses Tutorial herunter (Link oben) und sehen Sie sich den jasmin-starter-Ordner an. Sie werden sehen, dass wir eine einzige tests.js-Datei haben, die ein DOM-Element erstellt, einige Eigenschaften festlegt und es an den Body anfügt. Anschließend führen wir einige Jasmin-Tests durch, um sicherzustellen, dass der Prozess tatsächlich ordnungsgemäß funktioniert hat. Hier ist der Inhalt dieser Datei:
tests.js
1 |
describe("DOM Tests", function () { |
2 |
var el = document.createElement("div"); |
3 |
el.id = "myDiv"; |
4 |
el.innerHTML = "Hi there!"; |
5 |
el.style.background = "#ccc"; |
6 |
document.body.appendChild(el); |
7 |
|
8 |
var myEl = document.getElementById('myDiv'); |
9 |
it("is in the DOM", function () { |
10 |
expect(myEl).not.toBeNull(); |
11 |
});
|
12 |
|
13 |
it("is a child of the body", function () { |
14 |
expect(myEl.parentElement).toBe(document.body); |
15 |
});
|
16 |
|
17 |
it("has the right text", function () { |
18 |
expect(myEl.innerHTML).toEqual("Hi there!"); |
19 |
});
|
20 |
|
21 |
it("has the right background", function () { |
22 |
expect(myEl.style.background).toEqual("rgb(204, 204, 204)"); |
23 |
});
|
24 |
});
|
Die SpecRunner.html-Datei ist ziemlich umfangreich. Der einzige Unterschied besteht darin, dass ich die Skript-Tags in den Body verschoben habe, um sicherzustellen, dass das DOM vollständig geladen wird, bevor unsere Tests ausgeführt werden. Sie können die Datei in einem Browser öffnen und sehen, dass alle Tests einwandfrei bestanden haben.
Lassen Sie uns dieses Projekt auf PhantomJS umstellen. Klonen Sie zunächst das Phantom-Jasmin-Projekt:
1 |
git clone git://github.com/jcarver989/phantom-jasmine.git |
Dieses Projekt ist nicht so gut organisiert, wie es sein könnte, aber es gibt zwei wichtige Teile, die Sie benötigen:
- der PhantomJS-Läufer (wodurch Jasmine ein PhantomJS-DOM verwendet).
- der Jasmine-Konsolenreporter (der die Konsolenausgabe liefert).
Beide Dateien befinden sich im Ordner lib. Kopieren Sie sie in jasmin-starter/lib. Wir müssen jetzt unsere SpecRunner.html-Datei öffnen und die <script/> -Elemente anpassen. So sollten sie aussehen:
1 |
<script src="lib/jasmine-1.2.0/jasmine.js"></script> |
2 |
<script src="lib/jasmine-1.2.0/jasmine-html.js"></script> |
3 |
<script src="lib/console-runner.js"></script> |
4 |
<script src="tests.js"></script> |
5 |
|
6 |
<script>
|
7 |
var console_reporter = new jasmine.ConsoleReporter() |
8 |
jasmine.getEnv().addReporter(new jasmine.HtmlReporter()); |
9 |
jasmine.getEnv().addReporter(console_reporter); |
10 |
jasmine.getEnv().execute(); |
11 |
</script>
|
Beachten Sie, dass wir zwei Reporter für unsere Tests haben: einen HTML-Reporter und einen Konsolenreporter. Dies bedeutet, dass SpecRunner.html und seine Tests sowohl im Browser als auch in der Konsole ausgeführt werden können. Das ist praktisch. Leider benötigen wir diese Variable console_reporter, da sie in der CoffeeScript-Datei verwendet wird, die wir ausführen möchten.
Wie führen wir diese Tests tatsächlich auf der Konsole aus? Angenommen, Sie befinden sich im jasmin-starter-Ordner des Terminals. Hier ist der Befehl:
1 |
phantomjs lib/run\_jasmine\_test.coffee ./SpecRunner.html |
Wir führen das Skript run\_jasmine\_test.coffee mit PhantomJS aus und übergeben unsere Datei SpecRunner.html als Parameter. Sie sollten so etwas sehen:


Wenn ein Test fehlschlägt, sehen Sie natürlich Folgendes:


Wenn Sie dies häufig verwenden möchten, ist es möglicherweise eine gute Idee, run\_jasmine\_test.coffee an einen anderen Speicherort (z. B. ~/bin/run\_jasmine\_test.coffee) zu verschieben und einen Terminalalias für den gesamten Befehl zu erstellen. So würden Sie das in einer Bash-Shell machen:
1 |
alias phantom-jasmine='phantomjs /path/to/run\_jasmine\_test.coffee' |
Wirf das einfach in deine .bashrc- oder .bash_profile-Datei. Jetzt können Sie einfach ausführen:
1 |
phantom-jasmine SpecRunner.html |
Jetzt funktionieren Ihre Jasmin-Tests auf dem Terminal über PhantomJS einwandfrei. Sie können den endgültigen Code im Ordner jasmine-total im Download sehen.
PhantomJS und Mocha
Zum Glück ist es viel einfacher, Mocha und PhantomJS in Mocha-Phantomjs zu integrieren. Es ist super einfach zu installieren, wenn Sie NPM installiert haben (was Sie sollten):
1 |
npm install -g mocha-phantomjs |
Dieser Befehl installiert eine mocha-phantomjs-Binärdatei, mit der wir unsere Tests ausführen.
In einem früheren Tutorial habe ich Ihnen gezeigt, wie Sie Mocha im Terminal verwenden, aber Sie werden die Dinge anders machen, wenn Sie es zum Testen von DOM-Code verwenden. Wie bei Jasmine beginnen wir mit einem HTML-Testreporter, der im Browser ausgeführt werden kann. Das Schöne daran ist, dass wir dieselbe Datei auf dem Terminal für Konsolentestergebnisse mit PhantomJS ausführen können. Genau wie wir es mit Jasmine konnten.
Lassen Sie uns also ein einfaches Projekt erstellen. Erstellen Sie ein Projektverzeichnis und verschieben Sie es. Wir beginnen mit einer package.json-Datei:
1 |
{
|
2 |
"name": "project", |
3 |
"version": "0.0.1", |
4 |
"devDependencies": { |
5 |
"mocha": "*", |
6 |
"chai" : "*" |
7 |
}
|
8 |
}
|
Mocha ist das Test-Framework, und wir werden Chai als unsere Assertionsbibliothek verwenden. Wir installieren diese, indem wir NPM ausführen.
Wir werden unsere Testdatei test/tests.js aufrufen, und hier sind ihre Tests:
1 |
describe("DOM Tests", function () { |
2 |
var el = document.createElement("div"); |
3 |
el.id = "myDiv"; |
4 |
el.innerHTML = "Hi there!"; |
5 |
el.style.background = "#ccc"; |
6 |
document.body.appendChild(el); |
7 |
|
8 |
var myEl = document.getElementById('myDiv'); |
9 |
it("is in the DOM", function () { |
10 |
expect(myEl).to.not.equal(null); |
11 |
});
|
12 |
|
13 |
it("is a child of the body", function () { |
14 |
expect(myEl.parentElement).to.equal(document.body); |
15 |
});
|
16 |
|
17 |
it("has the right text", function () { |
18 |
expect(myEl.innerHTML).to.equal("Hi there!"); |
19 |
});
|
20 |
|
21 |
it("has the right background", function () { |
22 |
expect(myEl.style.background).to.equal("rgb(204, 204, 204)"); |
23 |
});
|
24 |
});
|
Sie sind den Jasmine-Tests sehr ähnlich, aber die Syntax der Chai-Assertion ist etwas anders (kopieren Sie also nicht nur Ihre Jasmine-Tests).
Das letzte Puzzleteil ist die Datei TestRunner.html:
1 |
<html>
|
2 |
<head>
|
3 |
<title> Tests </title> |
4 |
<link rel="stylesheet" href="./node_modules/mocha/mocha.css" /> |
5 |
</head>
|
6 |
<body>
|
7 |
<div id="mocha"></div> |
8 |
<script src="./node_modules/mocha/mocha.js"></script> |
9 |
<script src="./node_modules/chai/chai.js"></script> |
10 |
<script>
|
11 |
mocha.ui('bdd'); |
12 |
mocha.reporter('html'); |
13 |
var expect = chai.expect; |
14 |
</script>
|
15 |
<script src="test/test.js"></script> |
16 |
<script>
|
17 |
if (window.mochaPhantomJS) { mochaPhantomJS.run(); } |
18 |
else { mocha.run(); } |
19 |
</script>
|
20 |
</body>
|
21 |
</html>
|
Hier gibt es mehrere wichtige Faktoren. Beachten Sie zunächst, dass dies vollständig genug ist, um in einem Browser ausgeführt zu werden. Wir haben das CSS und JavaScript von den Knotenmodulen, die wir installiert haben. Beachten Sie dann das Inline-Skript-Tag. Dies bestimmt, ob PhantomJS geladen ist, und führt in diesem Fall die PhantomJS-Funktionalität aus. Ansonsten bleibt es bei der rohen Mocha-Funktionalität. Sie können dies im Browser ausprobieren und sehen, wie es funktioniert.
Um es in der Konsole auszuführen, führen Sie einfach Folgendes aus:
1 |
mocha-phantomjs TestRunner.html |
Voila! Jetzt werden Ihre Tests in der Konsole ausgeführt, und das alles dank PhantomJS.
PhantomJS und Yeoman
Ich wette, Sie wussten nicht, dass der beliebte Yeoman PhantomJS in seinem Testverfahren verwendet, und es ist praktisch scheinbar. Schauen wir uns ein kurzes Beispiel an. Ich gehe davon aus, dass Sie Yeoman eingerichtet haben.
Erstellen Sie ein neues Projektverzeichnis, führen Sie yeoman init darin aus und beantworten Sie alle Optionen mit "Nein". Öffnen Sie die Datei test/index.html, und unten finden Sie ein Skript-Tag mit einem Kommentar, der Sie auffordert, es durch Ihre eigenen Spezifikationen zu ersetzen. Ignorieren Sie diesen guten Rat vollständig und fügen Sie ihn in den it-Block ein:
1 |
var el = document.createElement("div"); |
2 |
expect(el.tagName).to.equal("DIV"); |
Führen Sie nun den yeoman test aus, und Sie werden sehen, dass der Test einwandfrei ausgeführt wird. Öffnen Sie nun die Datei test/index.html im Browser. Es klappt! Perfekt!
Natürlich können Sie mit Yeoman noch viel mehr tun. Weitere Informationen finden Sie in der Dokumentation.
Abschluss
Verwenden Sie die Bibliotheken, die PhantomJS erweitern, um das Testen zu vereinfachen.
Wenn Sie PhantomJS alleine verwenden, gibt es keinen Grund, sich mit PhantomJS selbst vertraut zu machen. Sie können einfach wissen, dass es existiert, und die Bibliotheken verwenden, die PhantomJS erweitern, um Ihre Tests zu vereinfachen.
Ich hoffe, dieses Tutorial hat Sie ermutigt, sich mit PhantomJS zu beschäftigen. Ich empfehle, mit den Beispieldateien und der Dokumentation zu beginnen, die PhantomJS anbietet. Sie öffnen Ihnen wirklich die Augen für das, was Sie mit PhantomJS tun können - von der Seitenautomatisierung bis zum Netzwerk-Sniffing.
Können Sie sich ein Projekt vorstellen, das PhantomJS verbessern würde? Lass es uns in den Kommentaren hören!



