Suche…
Bemerkungen
Nightwatch bietet seit v0.5 Tagen Akzeptanz- und End-to-End-Tests für Meteor-Apps an und hat Migrationen von PHP nach Spark nach Blaze und React verwaltet. und alle wichtigen Continuous Integration-Plattformen. Weitere Hilfe finden Sie unter:
App-Oberfläche
Auf der einfachsten Ebene sind Abnahmetests im Wesentlichen Black-Box-Tests, bei denen es im Wesentlichen darum geht, die Eingänge und Ausgänge eines geschlossenen Systems zu testen. Daher gibt es drei wesentliche Funktionen für das Akzeptieren von Tests: Lokalisieren einer Ressource, Lesen von Daten und Schreiben von Daten. Bei Browsern und Webapps lassen sich diese drei Funktionen im Wesentlichen auf Folgendes reduzieren:
- Laden Sie eine Webseiten- oder Anwendungsansicht
- Benutzeroberflächenelemente (z. B. DOM) prüfen
- Ereignis auslösen / Benutzerinteraktion simulieren
Wir nennen dies die Oberfläche der Anwendung. Oberfläche ist alles, was ein Benutzer sieht oder erlebt. Es ist die Außenseite eines Blackbox-Systems. Und da Benutzer mit Web-Browsern mit modernen Webanwendungen interagieren, wird die Flächendeckung durch universelle Ressourcen-Locators (URLs) und Viewports definiert. Und so sieht unser allererster Walkthrough so aus:
module.exports = {
"Hello World" : function (client) {
client
// the location of our Meteor app
.url("http://localhost:3000")
// the size of the viewport
.resizeWindow(1024, 768)
// test app output
.verify.elementPresent('h1')
.verify.containsText('h1', "Welcome to Meteor!")
.verify.containsText('p', "You've pressed the button 0 times")
.verify.elementPresent('button')
// simulate user input
.click('button').pause(500)
// test app output again, to make sure input worked
.verify.containsText('p', "button 1 times")
// saving a copy of our viewport pixel grid
.saveScreenshot('tests/nightwatch/screenshots/homepage.png')
.end();
}
};
Benutzerdefinierte Befehle
Nightwatch unterstützt das Erstellen benutzerdefinierter Befehle, mit denen Tastatureingaben, Mausklicks und andere Eingaben simuliert werden können. Ein benutzerdefinierter Befehl kann wie folgt mit anderen Nightwatch-Befehlen verkettet werden:
module.exports = {
"Login App" : function (client) {
client
.url("http://localhost:3000")
.login("[email protected]", "janedoe123")
.end();
}
};
Um dies zu aktivieren, definieren Sie einen Befehl in ./tests/nightwatch/commands/login
wie ./tests/nightwatch/commands/login
:
exports.command = function(username, password) {
this
.verify.elementPresent('#login')
// we clear the input in case there's any data remaining from previous visits
.clearValue("#emailInput")
.clearValue("#passwordInput")
// we simulate key presses
.setValue("#emailInput", username)
.setValue("#passwordInput", password)
// and we simulate a mouse click
.click("#signInToAppButton").pause(1000)
return this; // allows the command to be chained.
};
Damit dies alles funktioniert, müssen Sie Ihrer Anmeldeseite id
Attribute hinzufügen. Auf einer gewissen Ebene muss es ungefähr so aussehen:
<template name="login">
<div id="login">
<input id="emailInput" name="email" type="email" />
<input id="passwordInput" name="password" type="password" />
<button id="#signInToAppButton">Sign In</button>
</div>
</template>
Meteorobjekte auf dem Client prüfen
Da Nightwatch Zugriff auf die Browserkonsole hat, ist es möglich, clientseitige Objekte mithilfe der .execute()
API zu überprüfen. Im folgenden Beispiel überprüfen wir das Session-Objekt auf eine bestimmte Session-Variable. Zuerst erstellen wir die Datei ./tests/nightwatch/api/meteor/checkSession
, in der wir den folgenden Befehl behalten:
// syncrhonous version; only works for checking javascript objects on client
exports.command = function(sessionVarName, expectedValue) {
var client = this;
this
.execute(function(data){
return Session.get(data);
}, [sessionVarName], function(result){
client.assert.ok(result.value);
if(expectedValue){
client.assert.equal(result.value, expectedValue);
}
})
return this;
};
Wir können es dann so ketten:
module.exports = {
"Check Client Session" : function (client) {
client
.url("http://localhost:3000")
.checkSession("currentUser", "Jane Doe")
.end();
}
};
Formulare und Eingabetypen
Um eine Datei hochzuladen, müssen Sie zuerst ein / data-Verzeichnis erstellen und die Datei hinzufügen, die Sie hochladen möchten.
tests/nightwatch/data/IM-0001-1001.dcm
Ihr Formular benötigt eine Eingabe mit Dateityp. (Manche Leute mögen die Stiloptionen, die diese Eingabe bietet, nicht; und es ist ein übliches Muster, diese Eingabe auszublenden; und eine weitere Schaltfläche auf der Seite für den Benutzer anklicken.)
<form id="myform">
<input type="file" id="fileUpload">
<input type="text" name="first_name">
<input type="text" name="last_name">
<input type="date" name="dob_month">
<input type="date" name="dob_day">
<input type="date" name="dob_year">
<input type="radio" name="gender" value="M">
<input type="radio" name="gender" value="F">
<input type="radio" name="gender" value="O">
<input type="select" name="hs_graduation_year">
<input type="text" name="city">
<input type="select" name="state">
<input type="submit" name="submit" value="Submit">
</form>
Ihre Tests müssen dann setValue () verwenden und den Pfad zum lokalen Dateisystem auflösen.
module.exports = {
"Upload Study" : function (client) {
console.log(require('path').resolve(__dirname + '/../data' ));
var stringArray = "Chicago";
client
.url(client.globals.url)
.verify.elementPresent("form#myform")
// input[type="file"]
.verify.elementPresent("input#fileUpload")
.setValue('input#fileUpload', require('path').resolve(__dirname + '/../data/IM-0001-1001.dcm'))
// input[type="text"]
.setValue('input[name="first_name"]', 'First')
.setValue('input[name="last_name"]', 'Last')
// input[type="date"]
.click('select[name="dob_month"] option[value="3"]')
.click('select[name="dob_day"] option[value="18"]')
.click('select[name="dob_year"] option[value="1987"]')
// input[type="radio"]
.click('input[name="gender"][value="M"]')
// input[type="number"]
.click('select[name="hs_graduation_year"] option[value="2002"]')
// input[type="text"]
// sometimes Nightwatch will send text faster than the browser can handle
// which will cause skipping of letters. In such cases, we need to slow
// Nightwatch down; which we do by splitting our input into an array
// and adding short 50ms pauses between each letter
for(var i=0; i < userIdArray.length; i++) {
client.setValue('input[name="city"]', stringArray[i]).pause(50)
}
// input[type="select"]
// after an array input above, we need to resume our method chain...
client.click('select[name="state"] option[value="CA"]')
// input[type="number"]
.setValue('input[name="zip"]', '01234')
//input [ type="submit" ]
.click('button[type="submit"]')
.end();
}
};
Dank an Daniel Rinehart für die Inspiration dieses Beispiels.
Komponenten & Seitenobjekte
Seitenobjekte ähneln benutzerdefinierten Befehlen. Sie sind jedoch Sammlungen von benutzerdefinierten Befehlen, die einer bestimmten UI-Komponente zugeordnet sind. Dies funktioniert sehr gut mit modernem komponentenbasiertem Design wie in React.
module.exports = {
url: 'http://localhost:3000/login',
commands: [{
login: function(email, password) {
return this
.clearValue('input[name="emailAddress"]')
.clearValue('input[name="password"]')
.setValue('input[name="emailAddress"]', email)
.setValue('input[name="password"]', password)
.verify.elementPresent('#loginButton')
.click("#loginButton");
},
clear: function() {
return this
.waitForElementVisible('@emailInput')
.clearValue('@emailInput')
.clearValue('@passInput')
.waitForElementVisible('@loginButton')
.click('@loginButton')
},
checkElementsRendered: function(){
return this
.verify.elementPresent("#loginPage")
.verify.elementPresent('input[name="emailAddress"]')
.verify.elementPresent('input[name="password"]')
},
pause: function(time, client) {
client.pause(time);
return this;
},
saveScreenshot: function(path, client){
client.saveScreenshot(path);
return this;
}
}],
elements: {
emailInput: {
selector: 'input[name=email]'
},
passInput: {
selector: 'input[name=password]'
},
loginButton: {
selector: 'button[type=submit]'
}
}
};
Die einzige Einschränkung bei der Verwendung des PageObject-Musters beim Testen von Komponenten besteht darin, dass die Implementierung den Methodenverkettungsfluss verify.elementPresent
, den das native Nightwatch verify.elementPresent
bereitstellt. Stattdessen müssen Sie das Seitenobjekt einer Variablen zuweisen und für jede Seite eine neue Methodenkette instanziieren. Ein angemessener Preis für ein konsistentes und zuverlässiges Muster für die Wiederverwendung von Code.
module.exports = {
tags: ['accounts', 'passwords', 'users', 'entry'],
'User can sign up.': function (client) {
const signupPage = client.page.signupPage();
const indexPage = client.page.indexPage();
client.page.signupPage()
.navigate()
.checkElementsRendered()
.signup('Alice', 'Doe', '[email protected]', 'alicedoe')
.pause(1500, client);
indexPage.expect.element('#indexPage').to.be.present;
indexPage.expect.element('#authenticatedUsername').text.to.contain('Alice Doe');
},
}