Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Contributor Code of Conduct

As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.

We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information, such as physical or electronic addresses, without explicit permission
* Other unethical or unprofessional conduct.

Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.

This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.

This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0, available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
GREP ?=.

test: node_modules
@rm -rf /tmp/niffy
@node_modules/.bin/mocha --harmony --grep "$(GREP)"
@node_modules/.bin/mocha --harmony ./test/index.js

test/legacy: node_modules
@node_modules/.bin/mocha --harmony ./test/indexLegacy.js

node_modules: package.json
@npm install
Expand Down
84 changes: 79 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,70 @@ built on <a href="https://github.com/segmentio/nightmare">Nightmare</a> by <a hr
</p>

## Getting Started
You can look at [`test/index.js`](https://github.com/segmentio/niffy/blob/master/test/index.js) as an example for how to use Niffy. To run the example test just do `make test` after cloning this repo.
You can look at [`test/index.js`](https://github.com/segmentio/niffy/blob/master/test/index.js) as an example for how to use Niffy. To run the example test just run `make test` after cloning this repo.

## Reference
Niffy is built on [Nightmare](https://github.com/segmentio/nightmare) and used in combination with [Mocha](https://mochajs.org/). You'll also need to read and use both of those library's APIs to use niffy effectively.
Niffy is built on [Nightmare](https://github.com/segmentio/nightmare) and used in combination with [Mocha](https://mochajs.org/) (though other test frameworks can be used). You'll also need to read and use both of those library's APIs to use niffy effectively.

### Niffy(basehost, testhost[, options])
To create a new Niffy differ:

```js
let niffy = new Niffy(basehost, testhost, options);
let niffy = new Niffy(basehost, testhost, options, testsFunction);
```

* `basehost` is the url that is assumed "good"
* `testhost` is the url that you are comparing to the base
* `options` aside from the few specific niffy options, all nightmare options can be used. They can be seen [here in the Nightmare docs](https://github.com/segmentio/nightmare#nightmareoptions)
* `pngPath` is the folder the screenshots will be saved. It defaults to `./niffy`
* `threshold` is the maximum percentage difference for a passing test (default: 0.2%)
* `pngPath` is the folder the screenshots will be saved.
* `threshold` is the maximum percentage difference for a passing test
* `targets` a list of resolutions to test
* `testsFunction`

Niffy supplies these defaults:

```
{
pngPath : ./niffy,
threshold : 0.2,
targets : {
default : [ 1400, 1000 ]
}
}
```

## Usage

Whatever tests you want to run at each resolution must be wrapped in a function and passed as the `testsFunction` parameter of the Niffy constructor.

### Mocha example:

```
new Niffy(
'https://google.com',
'https://google.co.jp',
{
targets: {
small: [100,200],
big: [1000,2000]
}
}, ( niffy, size ) => {

const { label, width, height } = size;

describe(`${label} : ${width} x ${height}`, () => {
it('Homepage', function* () {
yield niffy.test('/');
})

after(function* () {
yield niffy.end();
});
}
);
```

(see [`test/index.js`](https://github.com/segmentio/niffy/blob/master/test/index.js) for a full example)

### .test(url[, fn])
This method instructs niffy to go to a `url` (and optionally take additional actions like clicking, typing or checkboxing via the `fn` argument), and test `basehost` vs. `testhost` screenshots for pixel differences, and output the diff-highlight image. Typically you'll use `.test(url, fn)` in the body of a mocha test, like this:
Expand Down Expand Up @@ -59,6 +106,33 @@ after(function* () {
});
```

Contributing
============

Development of Niffy requires node `7` or higher.

Niffy's **branch structure** goes as follows:

+ `master` - latest stable git repo.

+ `dev` - current development branch. This is where feature branches should branch from.

+ issue branches - these branches come from `dev` and are branched for a specific feature or bug, then get merged back into `dev`. The branch names should follow the structure `GH-(issue number)-(name)`

-----

We gladly accept and review any pull-requests into the current `dev` branch. Feel free! :heart:

Otherwise, if you just want to talk, we are very easy to get a hold of!

+ Email: [[email protected]](mailto:[email protected])
+ Git: <a href="https://github.com/mousemke/niffy/" target="_blank">https://github.com/mousemke/niffy/</a>


This project adheres to the [Contributor Covenant](http://contributor-covenant.org/). By participating, you are expected to honor this code.

[Niffy - Code of Conduct](https://github.com/mousemke/niffy/blob/master/CODE_OF_CONDUCT.md)


## License (MIT)

Expand Down
112 changes: 84 additions & 28 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
/*jshint esnext:true*/

var debug = require('debug')('niffy');
var Nightmare = require('nightmare');
var mkdirp = require('mkdirp');
var fs = require('fs');
var thunkify = require('thunkify');
var defaults = require('defaults');
var sprintf = require('sprintf-js').sprintf;
var diff = require('./lib/diff');
var path = require('path');
const debug = require('debug')('niffy');
const Nightmare = require('nightmare');
const mkdirp = require('mkdirp');
const fs = require('fs');
const thunkify = require('thunkify');
const defaults = require('defaults');
const sprintf = require('sprintf-js').sprintf;
const diff = require('./lib/diff');
const path = require('path');

function* timeout(ms) {
var to = function (ms, cb) {
const to = function (ms, cb) {
setTimeout(function () { cb(null); }, ms);
};
yield thunkify(to)(ms);
}

class Niffy
class NiffyTestRunner
{
/**
* capture a specific url after optionally taking some actions.
Expand All @@ -30,7 +30,6 @@ class Niffy
/**
* Capture the screenshots.
*/

yield this.captureHost('base', this.basehost, pngPath, fn);
yield this.captureHost('test', this.testhost, pngPath, fn);

Expand All @@ -39,10 +38,10 @@ class Niffy
*/

this.startProfile('diff');
var pathA = this.imgfilepath('base', pngPath);
var pathB = this.imgfilepath('test', pngPath);
var pathDiff = this.imgfilepath('diff', pngPath);
var result = yield diff(pathA, pathB, pathDiff);
const pathA = this.imgfilepath('base', pngPath);
const pathB = this.imgfilepath('test', pngPath);
const pathDiff = this.imgfilepath('diff', pngPath);
const result = yield diff(pathA, pathB, pathDiff);
this.stopProfile('diff');

/**
Expand Down Expand Up @@ -83,8 +82,16 @@ class Niffy
* @param {String} test
* @param {Object} options
*/
constructor(base, test, options) {
options = defaults(options, { show: false, width: 1400, height: 1000, threshold: .2, pngPath: path.resolve(process.cwd(), './niffy') });
constructor(base, test, options, size = 'default') {
options = defaults(options, {
show: false,
width: 1400,
height: 1000,
threshold: 0.2,
pngPath: path.resolve(process.cwd(), './niffy')
});

this.size = size;
this.nightmare = new Nightmare(options);
this.pngPath = options.pngPath;
this.basehost = base;
Expand Down Expand Up @@ -145,15 +152,15 @@ class Niffy
* Utils
*/
imgfilepath(name, pngPath) {
var filepath = this.pngPath + pngPath;
if (filepath.slice(-1) !== '/') filepath += '/';
if (pngPath.slice(-1) !== '/') pngPath += '/';
let filepath = `${this.pngPath}/${this.size}${pngPath}`;
if (filepath[0] === '.') {
filepath = path.join( __dirname, filepath.slice(1));
}
mkdirp(filepath, function (err) {
if (err) console.error(err.toString());
});
return (filepath + name + '.png');
return `${filepath}${name}.png`;
}


Expand All @@ -163,7 +170,7 @@ class Niffy
* @param {String} name
*/
startProfile(name) {
var start = new Date().getTime();
const start = new Date().getTime();
this.starts[name] = start;
}

Expand All @@ -174,7 +181,7 @@ class Niffy
* @param {String} name
*/
stopProfile(name) {
var end = new Date().getTime();
const end = new Date().getTime();
if (!this.starts[name]) return;
if (this.profiles[name]) this.profiles[name] += (end - this.starts[name]);
else this.profiles[name] = (end - this.starts[name]);
Expand All @@ -188,18 +195,67 @@ class Niffy
* @param {Function} fn
*/
* test(url, fn) {
console.log(url)
var diff = yield this.capture(url, fn);
var pct = '' + Math.floor(diff.percentage * 10000) / 10000 + '%';
var failMessage = sprintf('%s different, open %s', pct, diff.diffFilepath);
var absolutePct = Math.abs(diff.percentage);
const diff = yield this.capture(url, fn);
const pct = '' + Math.floor(diff.percentage * 10000) / 10000 + '%';
const failMessage = sprintf('%s different, open %s', pct, diff.diffFilepath);
const absolutePct = Math.abs(diff.percentage);
if (diff.percentage > this.errorThreshold) {
throw new Error(failMessage);
}
}
}


class Niffy {
/**
* Initialize Niffy cluster
*
* @param {String} base
* @param {String} test
* @param {Object} options
*/
constructor(base, test, options, fn) {
if (!options.targets)
{
console.warn('Testing from the niffy object is DEPRECATED and will be removed with version 1.0.0. Please define targets');

return new NiffyTestRunner(base, test, options);
}

const targets = Object.assign({}, options.targets);
delete options.targets;

this.testers = Object.keys(targets).map(label => {
const res = targets[label];
const width = res[0];
const height = res[1];

const niffy = new NiffyTestRunner(
base,
test,
Object.assign({
width,
height
}, options),
label
);

if ( !fn ) {
throw new Error('You must supply a test function');
}

fn( niffy, {
width,
height,
label
} );

return niffy;
});
}
}


/**
* Export `Niffy`
*/
Expand Down
Loading