Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
  • 2 commits
  • 11 files changed
  • 0 commit comments
  • 1 contributor
View
1  .gitignore
@@ -0,0 +1 @@
+*.diff.png
View
3  .gitmodules
@@ -0,0 +1,3 @@
+[submodule "lib/PhantomCSS"]
+ path = lib/PhantomCSS
+ url = https://github.com/thingsinjars/PhantomCSS.git
View
21 README.md
@@ -1,11 +1,28 @@
GhostStory
-====
+===
+
+A collection of cucumber steps for automated CSS testing with [SpookyJS](https://github.com/WaterfallEngineering/SpookyJS), [CasperJS](http://casperjs.org/), [PhantomJS](http://phantomjs.org/), [PhantomCSS](http://github.com/Huddle/PhantomCSS) and [Node.JS](http://nodejs.org/).
+
+Steps
+===
+
+The steps implemented so far cover basic calculated styles and image diffs.
+
+ Then the "Element descriptor" should have "property" of "value"
+ Then the "Element descriptor" should look the same as before
-A collection of cucumber steps for automated CSS testing with [SpookyJS](https://github.com/WaterfallEngineering/SpookyJS), [CasperJS](http://casperjs.org/), [PhantomJS](http://phantomjs.org/) and [Node.JS](http://nodejs.org/).
+Here, "Element descriptor" is the human-readable name of the element you are testing. These are mapped to CSS selectors in the 'selectors.json' file
+
+Read more about CSS testing at http://csste.st/ or read the slides introducing GhostStory at http://csste.st/slides/
Installation
====
+ This includes a fork of [PhantomCSS](http://github.com/thingsinjars/PhantomCSS) as a submodule. To pull it in, check this project out using
+
+ git clone --recursive git://github.com/thingsinjars/GhostStory.git
+
+
1. Download SpookyJS
1. `git clone https://github.com/WaterfallEngineering/SpookyJS.git`
View
8 css.feature
@@ -3,12 +3,12 @@ Feature: Navigation
I would like to test my CSS
Scenario: Frozen DOM test
- Given I go to "/empty.html"
- Then the "Main title" should have "font size" of "150px"
- And the "Main title" should have "color" of "#FF6600"
+ Given I go to "/index.html"
+ Then the "Main title" should have "font size" of "32px"
+ And the "Main title" should have "color" of "rgb(0, 0, 0)"
And run
Scenario: Image Diff Test
- Given I go to "/empty.html"
+ Given I go to "/1.html"
Then the "Page body" should look the same
And run
1  lib/PhantomCSS
@@ -0,0 +1 @@
+Subproject commit e7b061ceffca18aea3144aab9229c4df21970b33
View
BIN  screenshots/screenshot_0.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
4 selectors.json
@@ -0,0 +1,4 @@
+{
+ "Main title": "h1",
+ "Page body": "body"
+}
View
33 steps/css.js
@@ -1,10 +1,7 @@
module.exports = function () {
this.World = require('../support/world.js').World;
- selectors = {
- "Main title": "h1",
- "Page body": "body"
- };
+ var selectors = require('../selectors.json');
var camelize = function (str) {
return str.replace(/[\s\-](\w)/g, function(str, letter){
@@ -42,27 +39,33 @@ module.exports = function () {
// js-imagediff to calculate diffs
this.Then(/^the "([^"]*)" should look the same$/, compareImages);
function compareImages(elementName, callback) {
- var elementSelector = selectors[elementName];
+ var elementSelector = selectors[elementName] || elementName;
this.spooky.then([{
elementSelector: elementSelector
}, function () {
var casper = this,
testUrl = this.getCurrentUrl(),
- css = require('./examples/cucumber/features/support/phantomcss.js');
+ css = require('./GhostStory/lib/PhantomCSS/phantomcss.js');
css.init({
casper: casper,
- screenshotRoot: './screenshots',
- libraryRoot: './examples/cucumber/features/support',
- testRunnerUrl: './examples/cucumber/features/support/testRunner.html'
+ screenshotRoot: './GhostStory/screenshots',
+ libraryRoot: './GhostStory/lib/PhantomCSS',
+ testRunnerUrl: './GhostStory/support/testRunner.html'
});
- css.screenshot('body');
- css.compareAll();
- this.test.assertEquals(css.getExitStatus(), 0, testUrl + " does not match the previous test run");
+ casper.
+ then(function(){
+ css.screenshot(elementSelector);
+ }).
+ then(function(){
+ css.compareAll();
+ }).
+ then(function () {
+ this.test.assertEquals(css.getExitStatus(), 0, testUrl + " matches the previous test run");
+ });
}]);
-
- callback();
+ callback();
}
/* To save future confusion:
@@ -92,4 +95,4 @@ module.exports = function () {
*
*/
-};
+};
View
376 support/imagediff.js
@@ -1,376 +0,0 @@
-(function (name, definition) {
- var root = this;
- if (typeof module !== 'undefined') {
- var Canvas = require('canvas');
- module.exports = definition(root, name, Canvas);
- } else if (typeof define === 'function' && typeof define.amd === 'object') {
- define(definition);
- } else {
- root[name] = definition(root, name);
- }
-})('imagediff', function (root, name, Canvas) {
-
- var
- TYPE_ARRAY = /\[object Array\]/i,
- TYPE_CANVAS = /\[object (Canvas|HTMLCanvasElement)\]/i,
- TYPE_CONTEXT = /\[object CanvasRenderingContext2D\]/i,
- TYPE_IMAGE = /\[object (Image|HTMLImageElement)\]/i,
- TYPE_IMAGE_DATA = /\[object ImageData\]/i,
-
- UNDEFINED = 'undefined',
-
- canvas = getCanvas(),
- context = canvas.getContext('2d'),
- previous = root[name],
- imagediff, jasmine;
-
- // Creation
- function getCanvas (width, height) {
- var
- canvas = Canvas ?
- new Canvas() :
- document.createElement('canvas');
- if (width) canvas.width = width;
- if (height) canvas.height = height;
- return canvas;
- }
- function getImageData (width, height) {
- canvas.width = width;
- canvas.height = height;
- context.clearRect(0, 0, width, height);
- return context.createImageData(width, height);
- }
-
-
- // Type Checking
- function isImage (object) {
- return isType(object, TYPE_IMAGE);
- }
- function isCanvas (object) {
- return isType(object, TYPE_CANVAS);
- }
- function isContext (object) {
- return isType(object, TYPE_CONTEXT);
- }
- function isImageData (object) {
- return !!(object &&
- isType(object, TYPE_IMAGE_DATA) &&
- typeof(object.width) !== UNDEFINED &&
- typeof(object.height) !== UNDEFINED &&
- typeof(object.data) !== UNDEFINED);
- }
- function isImageType (object) {
- return (
- isImage(object) ||
- isCanvas(object) ||
- isContext(object) ||
- isImageData(object)
- );
- }
- function isType (object, type) {
- return typeof (object) === 'object' && !!Object.prototype.toString.apply(object).match(type);
- }
-
-
- // Type Conversion
- function copyImageData (imageData) {
- var
- height = imageData.height,
- width = imageData.width,
- data = imageData.data,
- newImageData, newData, i;
-
- canvas.width = width;
- canvas.height = height;
- newImageData = context.getImageData(0, 0, width, height);
- newData = newImageData.data;
-
- for (i = imageData.data.length; i--;) {
- newData[i] = data[i];
- }
-
- return newImageData;
- }
- function toImageData (object) {
- if (isImage(object)) { return toImageDataFromImage(object); }
- if (isCanvas(object)) { return toImageDataFromCanvas(object); }
- if (isContext(object)) { return toImageDataFromContext(object); }
- if (isImageData(object)) { return object; }
- }
- function toImageDataFromImage (image) {
- var
- height = image.height,
- width = image.width;
- canvas.width = width;
- canvas.height = height;
- context.clearRect(0, 0, width, height);
- context.drawImage(image, 0, 0);
- return context.getImageData(0, 0, width, height);
- }
- function toImageDataFromCanvas (canvas) {
- var
- height = canvas.height,
- width = canvas.width,
- context = canvas.getContext('2d');
- return context.getImageData(0, 0, width, height);
- }
- function toImageDataFromContext (context) {
- var
- canvas = context.canvas,
- height = canvas.height,
- width = canvas.width;
- return context.getImageData(0, 0, width, height);
- }
- function toCanvas (object) {
- var
- data = toImageData(object),
- canvas = getCanvas(data.width, data.height),
- context = canvas.getContext('2d');
-
- context.putImageData(data, 0, 0);
- return canvas;
- }
-
-
- // ImageData Equality Operators
- function equalWidth (a, b) {
- return a.width === b.width;
- }
- function equalHeight (a, b) {
- return a.height === b.height;
- }
- function equalDimensions (a, b) {
- return equalHeight(a, b) && equalWidth(a, b);
- }
- function equal (a, b, tolerance) {
-
- var
- aData = a.data,
- bData = b.data,
- length = aData.length,
- i;
-
- tolerance = tolerance || 0;
-
- if (!equalDimensions(a, b)) return false;
- for (i = length; i--;) if (aData[i] !== bData[i] && Math.abs(aData[i] - bData[i]) > tolerance) return false;
-
- return true;
- }
-
-
- // Diff
- function diff (a, b) {
- return (equalDimensions(a, b) ? diffEqual : diffUnequal)(a, b);
- }
- function diffEqual (a, b) {
-
- var
- height = a.height,
- width = a.width,
- c = getImageData(width, height), // c = a - b
- aData = a.data,
- bData = b.data,
- cData = c.data,
- length = cData.length,
- row, column,
- i, j, k, v;
-
- for (i = 0; i < length; i += 4) {
- cData[i] = Math.abs(aData[i] - bData[i]);
- cData[i+1] = Math.abs(aData[i+1] - bData[i+1]);
- cData[i+2] = Math.abs(aData[i+2] - bData[i+2]);
- cData[i+3] = Math.abs(255 - aData[i+3] - bData[i+3]);
- }
-
- return c;
- }
- function diffUnequal (a, b) {
-
- var
- height = Math.max(a.height, b.height),
- width = Math.max(a.width, b.width),
- c = getImageData(width, height), // c = a - b
- aData = a.data,
- bData = b.data,
- cData = c.data,
- rowOffset,
- columnOffset,
- row, column,
- i, j, k, v;
-
-
- for (i = cData.length - 1; i > 0; i = i - 4) {
- cData[i] = 255;
- }
-
- // Add First Image
- offsets(a);
- for (row = a.height; row--;){
- for (column = a.width; column--;) {
- i = 4 * ((row + rowOffset) * width + (column + columnOffset));
- j = 4 * (row * a.width + column);
- cData[i+0] = aData[j+0]; // r
- cData[i+1] = aData[j+1]; // g
- cData[i+2] = aData[j+2]; // b
- // cData[i+3] = aData[j+3]; // a
- }
- }
-
- // Subtract Second Image
- offsets(b);
- for (row = b.height; row--;){
- for (column = b.width; column--;) {
- i = 4 * ((row + rowOffset) * width + (column + columnOffset));
- j = 4 * (row * b.width + column);
- cData[i+0] = Math.abs(cData[i+0] - bData[j+0]); // r
- cData[i+1] = Math.abs(cData[i+1] - bData[j+1]); // g
- cData[i+2] = Math.abs(cData[i+2] - bData[j+2]); // b
- }
- }
-
- // Helpers
- function offsets (imageData) {
- rowOffset = Math.floor((height - imageData.height) / 2);
- columnOffset = Math.floor((width - imageData.width) / 2);
- }
-
- return c;
- }
-
-
- // Validation
- function checkType () {
- var i;
- for (i = 0; i < arguments.length; i++) {
- if (!isImageType(arguments[i])) {
- throw {
- name : 'ImageTypeError',
- message : 'Submitted object was not an image.'
- };
- }
- }
- }
-
-
- // Jasmine Matchers
- function get (element, content) {
- element = document.createElement(element);
- if (element && content) {
- element.innerHTML = content;
- }
- return element;
- }
-
- jasmine = {
-
- toBeImageData : function () {
- return imagediff.isImageData(this.actual);
- },
-
- toImageDiffEqual : function (expected, tolerance) {
-
- if (typeof (document) !== UNDEFINED) {
- this.message = function () {
- var
- div = get('div'),
- a = get('div', '<div>Actual:</div>'),
- b = get('div', '<div>Expected:</div>'),
- c = get('div', '<div>Diff:</div>'),
- diff = imagediff.diff(this.actual, expected),
- canvas = getCanvas(),
- context;
-
- canvas.height = diff.height;
- canvas.width = diff.width;
-
- div.style.overflow = 'hidden';
- a.style.float = 'left';
- b.style.float = 'left';
- c.style.float = 'left';
-
- context = canvas.getContext('2d');
- context.putImageData(diff, 0, 0);
-
- a.appendChild(toCanvas(this.actual));
- b.appendChild(toCanvas(expected));
- c.appendChild(canvas);
-
- div.appendChild(a);
- div.appendChild(b);
- div.appendChild(c);
-
- return [
- div,
- "Expected not to be equal."
- ];
- };
- }
-
- return imagediff.equal(this.actual, expected, tolerance);
- }
- };
-
-
- // Image Output
- function imageDataToPNG (imageData, outputFile, callback) {
-
- var
- canvas = toCanvas(imageData),
- base64Data,
- decodedImage;
-
- callback = callback || Function;
-
- base64Data = canvas.toDataURL().replace(/^data:image\/\w+;base64,/,"");
- decodedImage = new Buffer(base64Data, 'base64');
- require('fs').writeFile(outputFile, decodedImage, callback);
- }
-
-
- // Definition
- imagediff = {
-
- createCanvas : getCanvas,
- createImageData : getImageData,
-
- isImage : isImage,
- isCanvas : isCanvas,
- isContext : isContext,
- isImageData : isImageData,
- isImageType : isImageType,
-
- toImageData : function (object) {
- checkType(object);
- if (isImageData(object)) { return copyImageData(object); }
- return toImageData(object);
- },
-
- equal : function (a, b, tolerance) {
- checkType(a, b);
- a = toImageData(a);
- b = toImageData(b);
- return equal(a, b, tolerance);
- },
- diff : function (a, b) {
- checkType(a, b);
- a = toImageData(a);
- b = toImageData(b);
- return diff(a, b);
- },
-
- jasmine : jasmine,
-
- // Compatibility
- noConflict : function () {
- root[name] = previous;
- return imagediff;
- }
- };
-
- if (typeof module !== 'undefined') {
- imagediff.imageDataToPNG = imageDataToPNG;
- }
-
- return imagediff;
-});
View
239 support/phantomcss.js
@@ -1,239 +0,0 @@
-var fs = require('fs');
-var _tolerance = 64;
-var _root = '.';
-var _count = 0;
-var _realPath;
-var _diffsToProcess = [];
-var _emptyPageToRunTestsOn;
-var _libraryRoot = '.';
-var exitStatus;
-
-exports.screenshot = screenshot;
-exports.compareAll = compareAll;
-exports.init = init;
-exports.getExitStatus = getExitStatus;
-
-function init(options){
- if(typeof casper === "undefined") {
- casper = options.casper || {};
- }
- _emptyPageToRunTestsOn = options.testRunnerUrl;
- _libraryRoot = options.libraryRoot || _libraryRoot;
- _root = options.screenshotRoot || _root;
- _fileNameGetter = options.fileNameGetter || _fileNameGetter;
- _report = options.report || _report;
-}
-
-function _fileNameGetter(){
- var name = _root + "/screenshot_" + _count++;
-
- if(fs.isFile(name+'.png')){
- return name+'.diff.png';
- } else {
- return name+'.png';
- }
-
-}
-
-function screenshot(selector, timeToWait, hideSelector){
- casper.wait(timeToWait || 250, function(){
-
- if(hideSelector){
- casper.evaluate(function(s){
- $(s).css('visibility', 'hidden');
- }, {
- s: selector + ' ' + hideSelector
- });
- }
-
- casper.captureSelector( _fileNameGetter(_root) , selector);
-
- }); // give a bit of time for all the images appear
-}
-
-function asyncCompare(one, two, func){
-
- if(!casper.evaluate(function(){ return window._imagediff_;})){
- initClient();
- }
-
- casper.fill('form#image-diff', {
- 'one': one,
- 'two': two
- });
-
- casper.evaluate(function(){window._imagediff_.run();});
-
- casper.waitFor(
- function check() {
- return this.evaluate(function(){
- return window._imagediff_.hasResult;
- });
- },
- function () {
- var isSame = casper.evaluate(function(){
- return window._imagediff_.getResult();
- });
-
- func(isSame);
- }
- );
-}
-
-function getDiffs (path){
-
- var filePath;
-
- if(({'..':1,'.':1})[path]){ return true; }
-
- if(_realPath){
- _realPath += fs.separator + path;
- } else {
- _realPath = path;
- }
-
- filePath = _realPath;
-
- if(fs.isDirectory(_realPath) ){
- fs.list(_realPath).forEach(getDiffs);
- } else {
- if( /\.diff\./.test(path.toLowerCase()) ){
- _diffsToProcess.push(filePath);
- }
- }
-
- _realPath = _realPath.replace(fs.separator + path, '');
-}
-
-function compareAll(){
- var tests = [];
- var fails = 0;
- var errors = 0;
-
- getDiffs(_root);
-
- _diffsToProcess.forEach(function(file){
- var baseFile = file.replace('.diff', '');
- var test = {
- filename: baseFile
- };
-
- if(!fs.isFile(baseFile)) {
- test.error = true;
- errors++;
- tests.push(test);
- } else {
- casper.
- thenOpen (_emptyPageToRunTestsOn, function (){
- asyncCompare(baseFile, file, function(isSame){
- if(!isSame){
- test.fail = true;
- fails++;
- }
- tests.push(test);
- });
- });
- }
- });
-
- casper.then(function(){
- casper.waitFor(function(){
- return _diffsToProcess.length === tests.length;
- }, function(){
- _report(tests, fails, errors);
- });
- });
-}
-
-function initClient(){
- casper.log("*******************")
- casper.page.injectJs(_libraryRoot+'/imagediff.js');
- casper.log("8888888888888888888")
- casper.log(casper.evaluate(function(){ return window._imagediff_;}))
-
- casper.evaluate(function(_tolerance){
-
- var result;
-
- var compare = function firstCompare(one){
- compare = function secondCompare(two){
- result = imagediff.equal(one, two, _tolerance); // 0 = no tolerance 100 is too much.
- window._imagediff_.hasResult = true;
- compare = firstCompare;
- };
- };
-
- var div = document.createElement('div');
-
- function getImageData(e) {
- var image = new Image();
- image.onload = function(){
- compare( image );
- };
- image.src = e.target.result;
- }
-
- // this is a bit of hack, need to get images into browser for analysis
- div.style = "display:block;position:absolute;border:0;top:-1px;left:-1px;height:1px;width:1px;overflow:hidden;";
- div.innerHTML = '<form id="image-diff">'+
- '<input type="file" id="image-diff-one" name="one"/>'+
- '<input type="file" id="image-diff-two" name="two"/>'+
- '</form>';
- document.body.appendChild(div);
-
- window._imagediff_ = {
- hasResult: false,
- run: run,
- getResult: function(){
- window._imagediff_.hasResult = false;
- console.log('THE RESULT IS ', result);
- return result;
- }
- };
-
- function run(){
- var reader1 = new FileReader();
- var reader2 = new FileReader();
-
- reader1.onload = getImageData;
- reader2.onload = getImageData;
-
- reader1.readAsDataURL(document.getElementById('image-diff-one').files[0]);
- reader2.readAsDataURL(document.getElementById('image-diff-two').files[0]);
- }
- }, {
- _tolerance: _tolerance
- });
-}
-
-
-function _report(tests, noOfFails, noOfErrors){
-
- if( tests.length === 0){
- console.log("\nMust be your first time?");
- console.log("Some screenshots have been generated in the directory " + _root);
- console.log("This is your 'baseline', check the images manually. If they're wrong, delete the images.");
- console.log("The next time you run these tests, new screenshots will be taken. These screenshots will be compared to the original.");
- console.log('If they are different, PhantomCSS will report a failure.');
- } else {
-
- console.log("\nPhantomCSS found: " + tests.length + " tests.");
-
- if(noOfFails === 0){
- console.log("None of them failed. Which is good right?");
- console.log("If you want to make them fail, go change some CSS - weirdo.");
- } else {
- console.log(noOfFails + ' of them failed.');
- }
-
- if(noOfErrors !== 0){
- console.log("There were " + noOfErrors + "errors. Is it possible that a baseline image was deleted but not the diff?");
- }
-
- exitStatus = noOfErrors+noOfFails;
- }
-}
-
-function getExitStatus() {
- return exitStatus;
-}
View
4 support/world.js
@@ -1,5 +1,5 @@
var util = require('util');
-var Spooky = require('../../../../lib/spooky');
+var Spooky = require('../../lib/spooky');
var World = function World(callback) {
var spooky;
@@ -24,7 +24,7 @@ var World = function World(callback) {
console.trace('Spooky.listen failed');
}
- spooky.debug = false;
+ //spooky.debug = true;
// track errors
spooky.errors = [];

No commit comments for this range

Something went wrong with that request. Please try again.