Im vorherigen Artikel haben wir uns den Aufbau einer grundlegenden Struktur für Backstop angeschaut.
In diesem Artikel wird aufgezeigt wie man Backstop noch besser für große Projekte einrichtet und verwenden kann.
Automatisches öffnen des Browsers für den Report
GitHub Commit in dem das automatisch Öffnen des Browser abgestellt wird
So nützlich das automatische Starten des Standardbrowsers für das Ausgeben des Reports ist, so nervig wird es, wenn man produktiv damit arbeiten möchte. Zum Glück kann man dieses Verhalten in backstop.json
leicht verändern.
Dazu muss man lediglich unter report
ein leeres Array anstatt ["browser"]
übergeben.
"report": []
Möchte man sich nun den Report anzeigen lassen, führt man folgenden Befehl aus:
backstop openReport
Backstop im Projekt verwalten
GitHub Commit, der die Backstop Befehle im Projekt verwaltet
Damit alle Entwickler die gleiche Backstop Version verwenden und auch die gleichen (ggf. vereinfachten) Befehle ausführen (dazu später), kann man sich die NPM package.json
zu Nutze machen.
Zunächst führt man den folgenden Befehl aus und beantwortet die Fragen, sofern das Projekt noch keine package.json
Datei besitzt:
npm init
Anschließend sieht die package.json
etwa wie folgt aus:
{
"name": "backstop-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "backstop test"
},
"author": "wesselbaum",
"license": "MIT"
}
Der spannende Punkt ist unter scripts.test
. Dort wurde der bekannte Befehl backstop test
eingetragen.
Nun kann man anstelle von backstop test
den Befehl npm test
eingeben und dasselbe Ergebnis erhalten. Noch ist das keine große Zeitersparnis, wird jedoch später umso wichtiger, wenn weitere Parameter an den Befehl angehängt werden.
Um das Arbeiten dann konsistent zu gestalten werde ich die weiteren Standardbefehle im scripts
Block vermerken.
"scripts": {
"test": "backstop test",
"approve": "backstop approve",
"reference": "backstop reference",
"report": "backstop openReport"
}
Nun können die entsprechenden Befehle mittels z.B. npm run approve
ausgeführt werden. Hierbei ist zu beachten, dass zusätzlich run
angegeben werden muss. Das liegt daran, dass test
ein reservierter Befehl im Gegensatz zu anderen Befehlen ist. Für mehr Konsistenz im Arbeitsalltag kann man natürlich auch Test mit npm run test
ausführen.
Aufteilung von Verantwortlichkeiten
In der Standardkonfiguration sind alle Einstellungen in einer Datei hinterlegt. Das ist für kleine Projekte noch wartbar, für große Projekte ist aber eine Aufteilung der Verantwortlichkeiten besser und fördert die Übersicht. Dazu wird die Kernkonfiguration von den Szenarios getrennt.
Konfiguration in ein Node Modul auslagern
Damit man die gewünschten Optimierungen durchführen kann muss man im ersten Schritt die Konfiguration von einem JSON Dokument zu einem Node Modul portieren.
Etwas versteckt in dem Dokumentationsbereich generating test bitmaps gibt es folgenden Tipp:
Tip To use a js-module as a config file, just explicitly specify your config filepath and point to a
BackstopJS Readme.js
file. Just be sure to export your config object as a node module.
Mit dieser Information ergeben sich weitere Möglichkeiten.
Man legt eine neue Datei unter backstop_data/config/config.js
an. Anschließend kopiert man den folgenden Rumpf hinein und fügen den Inhalt der backstop.json
in die Variable backstopConfiguration
ein.
let backstopConfiguration = {
// backstop.json
};
// Ausgabe der Konfiguration in der Konsole für bessere Übersicht dessen, was am Ende als Ergebnis herauskommt
console.log('\x1b[32m', JSON.stringify(backstopConfiguration, null, 2), '\x1b[0m');
module.exports = backstopConfiguration;
Am Ende sollte das Ergebnis ungefähr so aussehen:
let backstopConfiguration = {
"id": "Foundation Template",
"viewports": [
{
"label": "phone",
"width": 320,
"height": 480
},
{
"label": "tablet",
"width": 1024,
"height": 768
},
{
"label": "desktop",
"width": 1440,
"height": 900
}
],
"onBeforeScript": "puppet/onBefore.js",
"onReadyScript": "puppet/onReady.js",
"scenarios": [
{
"label": "index",
"url": "http://localhost:3000/index.html",
"selectors": [
".top-bar",
".callout",
".blog-post",
".sticky-container",
".pagination"
],
"selectorExpansion": true
},
{
"label": "agency",
"url": "http://localhost:3000/agency.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "news-magazine",
"url": "http://localhost:3000/news-magazine.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "ecommerce",
"url": "http://localhost:3000/ecommerce.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "blog",
"url": "http://localhost:3000/blog.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "blog-simple",
"url": "http://localhost:3000/blog-simple.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "portfolio",
"url": "http://localhost:3000/portfolio.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "product-page",
"url": "http://localhost:3000/product-page.html",
"selectors": [
".row:not(.medium-up-3)"
],
"selectorExpansion": true
},
{
"label": "real-estate",
"url": "http://localhost:3000/real-estate.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "marketing-site",
"url": "http://localhost:3000/marketing-site.html",
"selectors": [
".row"
],
"selectorExpansion": true
}
],
"paths": {
"bitmaps_reference": "backstop_data/bitmaps_reference",
"bitmaps_test": "backstop_data/bitmaps_test",
"engine_scripts": "backstop_data/engine_scripts",
"html_report": "backstop_data/html_report",
"ci_report": "backstop_data/ci_report"
},
"report": [],
"engine": "chromy",
"engineOptions": {
"args": ["--no-sandbox"]
},
"asyncCaptureLimit": 5,
"asyncCompareLimit": 50,
"debug": false,
"debugWindow": false
};
console.log('\x1b[32m', JSON.stringify(backstopConfiguration, null, 2), '\x1b[0m');
module.exports = backstopConfiguration;
Damit die Konfiguration auch verwendet werden kann muss man nun die einzelnen Skripts test
, approve
, reference
und report
um die Angabe der Konfiguration ergänzen.
"scripts": {
"test": "backstop test --config=\"backstop_data/config/config.js\"",
"approve": "backstop approve --config=\"backstop_data/config/config.js\"",
"reference": "backstop reference --config=\"backstop_data/config/config.js\"",
"report": "backstop openReport --config=\"backstop_data/config/config.js\"",
"start": "backstop remote"
},
Anschließend kann man npm run test
ausführen und sollte die gleichen Ergebnisse wie zuvor erhalten.
Wenn das erfolgreich war, kann die backstop.json
gelöscht werden, da diese nicht mehr verwendet wird. Außerdem werden so falsch ausgeführte Tests vermieden.
Szenarios auslagern
Im ersten Schritt sollte man die Szenarios in eine eigene Datei auslagern. Dies hat den Hintergrund, dass die Szenarios der Bereich sind, der am häufigsten während eines Projekts angepasst wird. Die anderen Bereiche der Konfiguration hingegen sollten im Regelfall für die Weile des Projektes unverändert bestehen bleiben, um die Ergebnisse nicht zu verfälschen.
Dazu legt man eine neue Datei backstop_data/config/scenarios.js
an. In dieser legt man den folgenden Rumpf an:
const baseUrl = "http://localhost:3000";
let scenarios = [
// Szenarios aus ./config.js hier einfügen
];
module.exports = scenarios;
Als nächstes kopiert man das Szenarios Array aus der config.json
in die neu angelegte Datei.
Um Redundanzen noch mehr zu minimieren sollte die Variable baaeUrl
anstatt des Pfades verwendet werden, sodass diese wie folgt aussieht:
const baseUrl = "http://localhost:3000";
let scenarios = [
{
"label": "index",
"url": baseUrl + "/index.html",
"selectors": [
".top-bar",
".callout",
".blog-post",
".sticky-container",
".pagination"
],
"selectorExpansion": true
},
{
"label": "agency",
"url": baseUrl + "/agency.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "news-magazine",
"url": baseUrl + "/news-magazine.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "ecommerce",
"url": baseUrl + "/ecommerce.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "blog",
"url": baseUrl + "/blog.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "blog-simple",
"url": baseUrl + "/blog-simple.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "portfolio",
"url": baseUrl + "/portfolio.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "product-page",
"url": baseUrl + "/product-page.html",
"selectors": [
".row:not(.medium-up-3)"
],
"selectorExpansion": true
},
{
"label": "real-estate",
"url": baseUrl + "/real-estate.html",
"selectors": [
".row"
],
"selectorExpansion": true
},
{
"label": "marketing-site",
"url": baseUrl + "/marketing-site.html",
"selectors": [
".row"
],
"selectorExpansion": true
}
];
module.exports = scenarios;
Nun muss man dafür sorgen, dass die Szenarios wieder unter config.js
eingelesen werden. Dazu fügt man die folgende Zeile an den Anfang von config.js
ein und ersetzt das scenarios
Array mit der Variable scenarios
.
let scenarios = require('./scenarios.js');
Zuallerletzt sollte man noch einmal npm run test
durchführen um sicherzustellen, das sich nichts an der Ausgabe verändert hat.
Standardwerte festlegen
Der nächste Punkt, der die Lebensqualität deutlich erhöht, ist Standardwerte für die Szenarios an seine eigenen Bedürfnisse anzupassen. Aktuell sieht man noch, dass in der Mehrzahl der Tests im selectors
Array nur .row
angibt. Außerdem steht in jedem Szenario "selectorExpansion": true
. Diese Redundanzen gilt es jetzt zu beseitigen.
Als erstes legt man eine neue Datei backstop_data/config/defaultScenarioValues.js
an. In dieser Datei sollen alle Standardwerte für Szenarios verzeichnet werden. Wird in der scenarios.js
einer der gesetzten Werte erneut gesetzt, so sollen die Standardwerte nicht verwendet werden.
Für die oben beschriebenen Anforderungen ergibt sich folgender Inhalt für die defaultScenarioValues.js
let defaultScenarioValues =
{
"selectors": [
".row"
],
"selectorExpansion": true,
};
module.exports = defaultScenarioValues;
Nun muss auch diese Datei mit dem folgendem Befehl in config.js
eingebunden werden
let defaultScenarioValues = require('./default_scenario_values');
Anschließend müssen wie oben beschrieben Standard- und Szenario-Werte zusammengeführt werden. Dafür werden folgende Zeilen verwendet:
const mergedScenarios = scenarios.map(scenario => {
return Object.assign(JSON.parse(JSON.stringify(defaultScenarioValues)), scenario);
});
Anschließend muss bei scenarios
anstatt der Variable scenarios
die Variable mergedScenarios
verwendet werden. Letztendlich sollte die config.js
wie folgt aussehen:
let scenarios = require('./scenarios.js');
let defaultScenarioValues = require('./default_scenario_values');
const mergedScenarios = scenarios.map(scenario => {
return Object.assign(JSON.parse(JSON.stringify(defaultScenarizeilen oValues)), scenario);
});
let backstopConfiguration = {
"id": "Foundation Template",
"viewports": [
{
"label": "phone",
"width": 320,
"height": 480
},
{
"label": "tablet",
"width": 1024,
"height": 768
},
{
"label": "desktop",
"width": 1440,zeilen
"height": 900
}
],
"onBeforeScript": "puppet/onBefore.js",
"onReadyScript": "puppet/onReady.js",
"scenarios": mergedScenarios,
"paths": {
"bitmaps_reference": "backstop_data/bitmaps_reference",
"bitmaps_test": "backstop_data/bitmaps_test",
"engine_scripts": "backstop_data/engine_scripts",
"html_report": "backstop_data/html_report",
"ci_report": "backstop_data/ci_report"
},
"report": [],
"engine": "chromy",
"engineOptions": {
"args": ["--no-sandbox"]
},
"asyncCaptureLimit": 5,
"asyncCompareLimit": 50,
"debug": false,
"debugWindow": false
};
console.log('\x1b[32m', JSON.stringify(backstopConfiguration, null, 2), '\x1b[0m');
module.exports = backstopConfiguration;
Zuletzt muss die scenarios.js
von den Werten bereinigt werden, die ohnehin in der defaultScenarioValues.js
gesetzt werden. Aufgeräumt sieht es dann in etwa so aus:
const baseUrl = "http://localhost:3000";
let scenarios = [
{
"label": "index",
"url": baseUrl + "/index.html",
"selectors": [
".top-bar",
".callout",
".blog-post",
".sticky-container",
".pagination"
]
},
{
"label": "agency",
"url": baseUrl + "/agency.html"
},
{
"label": "news-magazine",
"url": baseUrl + "/news-magazine.html"
},
{
"label": "ecommerce",
"url": baseUrl + "/ecommerce.html"
},
{
"label": "blog",
"url": baseUrl + "/blog.html"
},
{
"label": "blog-simple",
"url": baseUrl + "/blog-simple.html"
},
{
"label": "portfolio",
"url": baseUrl + "/portfolio.html"
},
{
"label": "product-page",
"url": baseUrl + "/product-page.html",
"selectors": [
".row:not(.medium-up-3)"
]
},
{
"label": "real-estate",
"url": baseUrl + "/real-estate.html"
},
{
"label": "marketing-site",
"url": baseUrl + "/marketing-site.html"
}
];
module.exports = scenarios;
In diesem Zustand ist für die meisten Entwickler in dem Projekt nur noch die scenarios.js
relevant. Das beste daran ist, dass man nicht aus Versehen Konfigurationen anpasst, die sich auf alle auswirken würden. Das anzupassende Dokument ist sehr übersichtlich und neue Testfälle sind in einfachen Fällen in 4 Zeilen Code fertig erstellt.