Recherche…
Remarques
Nightwatch fournit des tests d’acceptation et de bout en bout pour les applications Meteor depuis la version v0.5, et a géré les migrations de PHP vers Spark vers Blaze et React; et toutes les principales plates-formes d'intégration continue. Pour une aide supplémentaire, veuillez consulter:
Documentation de l'API Nightwatch
Groupe Google Nightwatch.js
Surface de l'application
Au niveau le plus élémentaire, les tests d'acceptation sont essentiellement des tests en boîte noire, qui concernent essentiellement le test des entrées et des sorties d'un système fermé. En tant que tel, il y a trois caractéristiques essentielles aux tests d'acceptation: localiser une ressource, lire des données et écrire des données. En ce qui concerne les navigateurs et les applications Web, ces trois caractéristiques se résument essentiellement aux suivantes:
- Charger une page Web ou une vue d'application
- Inspecter les éléments de l'interface utilisateur (c.-à-d. DOM)
- Déclencher un événement / simuler une interaction utilisateur
Nous appelons cela la surface de l'application. La surface est tout ce qu'un utilisateur voit ou vit. C'est l'extérieur d'un système de boîte noire. Et comme les utilisateurs interagissent avec les applications Web modernes sur les écrans vidéo à l'aide de navigateurs Web, notre couverture de surface est définie par des URL et des fenêtres de visualisation universelles. Et donc notre toute première visite commence comme suit:
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();
}
};
Commandes personnalisées
Nightwatch prend en charge la création de commandes personnalisées pouvant simuler des frappes de touches, des clics de souris et d'autres entrées. Une commande personnalisée peut être chaînée avec d'autres commandes Nightwatch, comme ceci:
module.exports = {
"Login App" : function (client) {
client
.url("http://localhost:3000")
.login("[email protected]", "janedoe123")
.end();
}
};
Pour activer cela, définissez une commande dans ./tests/nightwatch/commands/login
ainsi:
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.
};
Pour que tout fonctionne, vous devrez ajouter des attributs d’ id
à votre page de connexion. À un certain niveau, il devra ressembler en gros à ce qui suit:
<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>
Inspection des objets Meteor sur le client
Comme Nightwatch a accès à la console du navigateur, il est possible d'inspecter les objets côté client à l'aide de l'API .execute()
. Dans l'exemple suivant, nous vérifions l'objet Session pour une variable de session particulière. Premièrement, nous commençons par créer le fichier ./tests/nightwatch/api/meteor/checkSession
, où nous conserverons la commande suivante:
// 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;
};
On peut alors l'enchaîner comme ça:
module.exports = {
"Check Client Session" : function (client) {
client
.url("http://localhost:3000")
.checkSession("currentUser", "Jane Doe")
.end();
}
};
Formulaires et types d'entrées
Pour télécharger un fichier, vous devez d'abord créer un répertoire / data et ajouter le fichier à télécharger.
tests/nightwatch/data/IM-0001-1001.dcm
Votre formulaire aura besoin d'une entrée avec le type de fichier. (Certaines personnes n'aiment pas les options de style que cette entrée fournit; un modèle courant consiste à masquer cette entrée et à cliquer sur un autre bouton de la part de l'utilisateur.)
<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>
Vos tests devront alors utiliser setValue () et résoudre le chemin d'accès à la ressource de fichier local.
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();
}
};
Nous remercions Daniel Rinehart pour avoir inspiré cet exemple.
Composants et objets de page
Les objets de page sont similaires aux commandes personnalisées. sauf qu'ils sont des collections de commandes personnalisées associées à un composant d'interface utilisateur spécifique. Cela fonctionne extrêmement bien avec la conception à base de composants moderne, comme dans 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]'
}
}
};
La seule mise en garde concernant l'utilisation du modèle PageObject dans les composants de test est que l'implémentation rompt le flux de chaînage de méthode fourni par verify.elementPresent
natif. Au lieu de cela, vous devrez affecter l'objet page à une variable et instancier une nouvelle chaîne de méthode pour chaque page. Un prix raisonnable à payer pour un modèle cohérent et fiable pour tester la réutilisation du 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');
},
}