Skip to content
This repository was archived by the owner on Oct 11, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 18 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
3 changes: 2 additions & 1 deletion commands/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"@after-work.js/node": "^5.0.0-beta.2",
"@after-work.js/protractor": "^5.0.0-beta.2",
"@after-work.js/puppeteer": "^5.0.0-beta.2",
"@after-work.js/serve": "^5.0.0-beta.2"
"@after-work.js/serve": "^5.0.0-beta.2",
"@after-work.js/chai-plugin-screenshot": "^5.0.0-beta.2"
},
"files": [
"/src"
Expand Down
2 changes: 2 additions & 0 deletions commands/cli/src/preset-env.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const screenshotPlugin = require('@after-work.js/chai-plugin-screenshot');
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
Expand All @@ -11,3 +12,4 @@ global.expect = chai.expect;
chai.use(sinonChai);
chai.use(chaiAsPromised);
chai.use(chaiSubset);
chai.Assertion.addMethod('matchImageOf', screenshotPlugin.matchImageOf);
2 changes: 0 additions & 2 deletions commands/protractor/src/plugins/screenshoter/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/* global browser */
const utils = require('./utils');

global.chai.Assertion.addMethod('matchImageOf', utils.matchImageOf);

const screenshoter = {
/**
* Sets up plugins before tests are run. This is called after the WebDriver
Expand Down
86 changes: 0 additions & 86 deletions commands/protractor/src/plugins/screenshoter/utils.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
/* globals document, Element, window */
const path = require('path');
const fs = require('fs');
const jimp = require('jimp');
const util = require('util');
const mkdirp = require('mkdirp');

function getBoundingClientRect(selector, cb) {
/* eslint-disable */
Expand All @@ -29,13 +26,6 @@ const utils = {
getBrowserName(browser) {
return browser.getCapabilities();
},
fileExists(filePath) {
return new Promise((resolve) => {
fs.lstat(filePath, (err) => {
resolve(!err);
});
});
},
removeFile(filePath) {
return new Promise((resolve, reject) => {
fs.unlink(filePath, (err) => {
Expand All @@ -50,28 +40,6 @@ const utils = {
removeFiles(...files) {
return Promise.all(files.map(file => this.removeFile(file)));
},
writeImage(img, filePath) {
return new Promise((resolve, reject) => {
img.write(filePath, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
},
compare(baseline, regressionImg, tolerance) {
return jimp.read(baseline).then((baselineImg) => {
const distance = jimp.distance(baselineImg, regressionImg);
const diff = jimp.diff(baselineImg, regressionImg);
return {
diffImg: diff.image,
isEqual: distance <= Math.max(0.02, tolerance) && diff.percent <= tolerance,
equality: `distance: ${distance}, percent: ${diff.percent}`,
};
});
},
takeImageOf(browser, {
selector = 'body', offsetX = 0, offsetY = 0, offsetWidth = 0, offsetHeight = 0,
} = {}) {
Expand All @@ -98,60 +66,6 @@ const utils = {
platform: caps.get('platform').replace(/ /g, '-').toLowerCase(),
})));
},
matchImageOf(id, folder = '', tolerance = 0.002) {
const promise = this._obj.then ? this._obj : Promise.resolve(this._obj); // eslint-disable-line
return promise.then((meta) => {
const imageName = util.format('%s-%s-%s.png', id, meta.platform, meta.browserName);

mkdirp.sync(path.resolve(meta.artifactsPath, 'baseline', folder));
mkdirp.sync(path.resolve(meta.artifactsPath, 'regression', folder));
mkdirp.sync(path.resolve(meta.artifactsPath, 'diff', folder));

const baseline = path.resolve(meta.artifactsPath, 'baseline', folder, imageName);
const regression = path.resolve(meta.artifactsPath, 'regression', folder, imageName);
const diff = path.resolve(meta.artifactsPath, 'diff', folder, imageName);

// Injecting images into assert
const expected = {
baseline: path.join('baseline', folder, imageName).replace(/\\/g, '/'),
diff: path.join('diff', folder, imageName).replace(/\\/g, '/'),
regression: path.join('regression', folder, imageName).replace(/\\/g, '/'),
};

const actual = {};

return utils.fileExists(baseline).then((exists) => {
if (!exists) {
return utils.writeImage(meta.img, baseline).then(() => {
this.assert(
false,
`No baseline found! New baseline generated at ${`${meta.artifactsPath}/${expected.baseline}`}`,
`No baseline found! New baseline generated at ${`${meta.artifactsPath}/${expected.baseline}`}`,
JSON.stringify(expected),
actual,
);
});
}
return utils.compare(baseline, meta.img, tolerance).then((comparison) => {
if (comparison.isEqual) {
return comparison;
}
return Promise.all([
utils.writeImage(meta.img, regression),
utils.writeImage(comparison.diffImg, diff)]).then(() => {
this.assert(
comparison.isEqual === true,
`expected ${id} equality to be less than ${tolerance}, but was ${comparison.equality}`,
`expected ${id} equality to be greater than ${tolerance}, but was ${comparison.equality}`,
JSON.stringify(expected),
actual,
);
return comparison;
});
});
});
});
},
};

module.exports = utils;
178 changes: 0 additions & 178 deletions commands/protractor/test/unit/plugins/screenshoter/utils.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
const path = require('path');
const fs = require('fs');
const mkdirp = require('mkdirp');
const jimp = require('jimp');
const utils = require('../../../../src/plugins/screenshoter/utils');

Expand All @@ -15,18 +13,6 @@ describe('Screenshoter Utils', () => {
sandbox.restore();
});

describe('fileExists', () => {
it('should return true if file exists', () => {
sandbox.stub(fs, 'lstat').callsArgWith(1, false);
return expect(utils.fileExists('foo')).to.eventually.equal(true);
});

it("should return false if file doesn't exist", () => {
sandbox.stub(fs, 'lstat').callsArgWith(1, true);
return expect(utils.fileExists('foo')).to.eventually.equal(false);
});
});

describe('removeFile', () => {
it('should resolve if the file could be removed', () => {
sandbox.stub(fs, 'unlink').callsArgWith(1, false);
Expand All @@ -52,86 +38,6 @@ describe('Screenshoter Utils', () => {
});
});

describe('writeImage', () => {
let img;

beforeEach(() => {
img = { write: sandbox.stub() };
});

it('should write an image to a file', () => {
img.write.callsArgWith(1);
return expect(utils.writeImage(img, 'foo')).to.eventually.be.fulfilled.and.be.an('undefined');
});

it("should reject if image file couldn't be created", () => {
img.write.callsArgWith(1, 'error');
return expect(utils.writeImage(img, 'foo')).to.eventually.be.rejectedWith('error');
});
});

describe('compare', () => {
let jimpRead;
let jimpDistance;
let jimpDiff;

beforeEach(() => {
jimpRead = sandbox.stub(jimp, 'read');
jimpDistance = sandbox.stub(jimp, 'distance');
jimpDiff = sandbox.stub(jimp, 'diff');
});

it('should be equal if the tolerance is met', () => {
const distance = 0;
const diffImg = {};
const percent = 0;
jimpRead.returns(Promise.resolve({}));
jimpDistance.returns(distance);
jimpDiff.returns({ image: diffImg, percent });

return expect(utils.compare('baseline', 'regression', 0)).to.eventually.be.fulfilled.and.deep.equal({
diffImg,
isEqual: true,
equality: `distance: ${distance}, percent: ${percent}`,
});
});

it("should not be equal if the tolerance isn't met", () => {
const distance = 0.1;
const diffImg = {};
const percent = 0;
jimpRead.returns(Promise.resolve({}));
jimpDistance.returns(distance);
jimpDiff.returns({ image: diffImg, percent });

return expect(utils.compare('baseline', 'regression', 0)).to.eventually.be.fulfilled.and.deep.equal({
diffImg,
isEqual: false,
equality: `distance: ${distance}, percent: ${percent}`,
});
});

it("should not be equal if the tolerance isn't met", () => {
const distance = 0.1;
const diffImg = {};
const percent = 0;
jimpRead.returns(Promise.resolve({}));
jimpDistance.returns(distance);
jimpDiff.returns({ image: diffImg, percent });

return expect(utils.compare('baseline', 'regression', 0)).to.eventually.be.fulfilled.and.deep.equal({
diffImg,
isEqual: false,
equality: `distance: ${distance}, percent: ${percent}`,
});
});

it('should reject with error', () => {
jimpRead.returns(Promise.reject(new Error('error')));
expect(utils.compare('baseline', 'regression', 0)).to.eventually.be.rejected.and.have.property('message', 'error');
});
});

describe('getBoundingClientRect', () => {
let querySelector;
let getBoundingClientRect;
Expand Down Expand Up @@ -221,88 +127,4 @@ describe('Screenshoter Utils', () => {
expect(result.browserName).to.equal('chrome');
}));
});

describe('matchImageOf', () => {
let chaiCtx;
let matchImageOf;
let fileExists;
let writeImage;
let compare;
let mkdir;
const baselinePath = 'artifacts/baseline/';
const baseline = `${baselinePath}id-windows-nt-chrome.png`;
const regressionPath = 'artifacts/regression/';
const regression = `${regressionPath}id-windows-nt-chrome.png`;
const diffPath = 'artifacts/diff/';
const diff = `${diffPath}id-windows-nt-chrome.png`;
const img = {};

beforeEach(() => {
sandbox.stub(path, 'resolve').callsFake((...args) => args.join('/').replace('//', '/'));
fileExists = sandbox.stub(utils, 'fileExists');
writeImage = sandbox.stub(utils, 'writeImage');
compare = sandbox.stub(utils, 'compare');
chaiCtx = {
_obj: Promise.resolve({
img, browserName: 'chrome', artifactsPath: 'artifacts', platform: 'windows-nt',
}),
assert: sinon.stub(),
};
matchImageOf = utils.matchImageOf.bind(chaiCtx);
sandbox.stub(process, 'cwd').returns('foo');
mkdir = sandbox.stub(mkdirp, 'sync');
});

it("should write baseline if it's not existing", () => {
fileExists.returns(Promise.resolve(false));
writeImage.returns(Promise.resolve());
return matchImageOf('id').then(() => {
expect(writeImage).to.have.been.calledWith({}, baseline);
});
});

it('should compare and resolve if considered equal to baseline', () => {
fileExists.returns(Promise.resolve(true));
writeImage.returns(Promise.resolve());
compare.returns(Promise.resolve({ equality: 0, isEqual: true }));
return matchImageOf('id').then((comparison) => {
expect(comparison).to.deep.equal({ equality: 0, isEqual: true });
});
});

it('should reject if not considered equal to baseline', () => {
const diffImg = {};
fileExists.returns(Promise.resolve(true));
writeImage.returns(Promise.resolve());
compare.returns(Promise.resolve({ isEqual: false, diffImg }));
return matchImageOf('id').then(() => {
expect(writeImage.callCount).to.equal(2);
expect(writeImage.firstCall).to.have.been.calledWithExactly(img, regression);
expect(writeImage.secondCall).to.have.been.calledWithExactly(diffImg, diff);
});
});

it('should create the default artifacts folders', () => {
fileExists.returns(Promise.resolve(false));
writeImage.returns(Promise.resolve());
return matchImageOf('id').then(() => {
expect(mkdir.callCount).to.equal(3);
expect(mkdir.firstCall).to.have.been.calledWithExactly(baselinePath);
expect(mkdir.secondCall).to.have.been.calledWithExactly(regressionPath);
expect(mkdir.thirdCall).to.have.been.calledWithExactly(diffPath);
});
});

it('should create the artifacts folders with subfolders', () => {
const folders = 'foo/bar';
fileExists.returns(Promise.resolve(false));
writeImage.returns(Promise.resolve());
return matchImageOf('id', folders).then(() => {
expect(mkdir.callCount).to.equal(3);
expect(mkdir.firstCall).to.have.been.calledWithExactly(baselinePath + folders);
expect(mkdir.secondCall).to.have.been.calledWithExactly(regressionPath + folders);
expect(mkdir.thirdCall).to.have.been.calledWithExactly(diffPath + folders);
});
});
});
});
17 changes: 17 additions & 0 deletions docs/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ describe('button', () => {
</p>
</details>

## Screenshot testing

When using the preset-env option. A screenshot assertion plugin is added to Chai. This allows comperisons of images.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo comperisons


```javascript
describe('screenshot', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixup the example

it('image should be equal', () => {
const img = Promise.resolve('<base64-encoded-image>'); // Promise that resolves to Buffer or a base64 encoded image
expect(img).to.equal('<name-of-my-img-on-disk>', {
artifactsPath: 'tests/__artifacts__',
tolerance: 0.002
});
});
});
```

## Options

Expand All @@ -57,6 +72,7 @@ describe('button', () => {
<p>

```javascript
const screenshotPlugin = require('@after-work.js/chai-plugin-screenshot');
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
Expand All @@ -70,6 +86,7 @@ global.expect = chai.expect;
chai.use(sinonChai);
chai.use(chaiAsPromised);
chai.use(chaiSubset);
chai.Assertion.addMethod('matchImageOf', screenshotPlugin.matchImageOf);
```

This enables writing your tests like this:
Expand Down
3 changes: 2 additions & 1 deletion lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"packages": [
"commands/*",
"commands-common/*",
"examples/*"
"examples/*",
"plugins/*"
],
"npmClient": "npm",
"npmClientArgs": [
Expand Down
Loading