Skip to content
Open
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
13 changes: 7 additions & 6 deletions addon-test-support/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import RSVP from 'rsvp';
* @param {integer} [options.delayMs] Delay (in milliseconds) before taking the screenshot. Useful when you need to wait for CSS transitions, etc. Defaults to `100`.
* @return {Promise}
*/
export async function capture(assert, fileName, { selector = null, fullPage = true, delayMs = 100 } = {}) {
export async function capture(assert, fileName, { selector = null, fullPage = true, delayMs = 100, mode = null } = {}) {
let testId = assert.test.testId;

let queryParamString = window.location.search.substr(1);
Expand Down Expand Up @@ -53,12 +53,12 @@ export async function capture(assert, fileName, { selector = null, fullPage = tr
];

let url = `${window.location.protocol}//${window.location.host}${window.location.pathname}?${urlQueryParams.join('&')}`;
let response = await requestCapture(url, fileName, { selector, fullPage, delayMs });
let response = await requestCapture(url, fileName, { selector, fullPage, delayMs, mode });

if (response.status === 'SUCCESS') {
assert.ok(true, `visual-test: ${fileName} has not changed`);
assert.ok(true, `visual-test: ${fileName} (${mode || 'default'}) has not changed`);
} else {
assert.ok(false, `visual-test: ${fileName} has changed: ${response.error}`);
assert.ok(false, `visual-test: ${fileName} (${mode || 'default'}) has changed: ${response.error}`);
}

return response;
Expand All @@ -79,7 +79,7 @@ export function prepareCaptureMode() {
}
}

export async function requestCapture(url, fileName, { selector, fullPage, delayMs }) {
export async function requestCapture(url, fileName, { selector, fullPage, delayMs, mode }) {
// If not in capture mode, make a request to the middleware to capture a screenshot in node
fileName = dasherize(fileName);

Expand All @@ -88,7 +88,8 @@ export async function requestCapture(url, fileName, { selector, fullPage, delayM
name: fileName,
selector,
fullPage,
delayMs
delayMs,
mode
};

return await ajaxPost('/visual-test/make-screenshot', data, 'application/json');
Expand Down
7 changes: 6 additions & 1 deletion ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ module.exports = function(defaults) {
visualTest: {
imageLogging: true,
debugLogging: true,
imgurClientId: '6331cc0a93af83c'
imgurClientId: '6331cc0a93af83c',
modes: {
mobile: {
windowWidth: 412
}
}
},
fingerprint: {
exclude: [
Expand Down
101 changes: 63 additions & 38 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@ module.exports = {
chromePort: 0,
windowWidth: 1024,
windowHeight: 768,
noSandbox: false
noSandbox: false,
modes: {}
},

included(app) {
this._super.included(app);
this._ensureThisImport();

let options = Object.assign({}, this.visualTest, app.options.visualTest);
Object.entries(options.modes).forEach(([name, modeOptions]) => {
options.modes[name] = Object.assign({}, options, { modes: undefined }, modeOptions);
});
this._debugLog('Setting up ember-visual-test...');

options.forceBuildVisualTestImages = !!process.env.FORCE_BUILD_VISUAL_TEST_IMAGES;
Expand All @@ -60,12 +64,25 @@ module.exports = {
});
},

async _getBrowser() {
if (this.browser) {
return this.browser;
_getOptions(mode) {
if (mode) {
if (this.visualTest.modes[mode]) {
return this.visualTest.modes[mode];
} else {
console.error(`Mode '${mode}' not found. Please define it in your ember-cli-build.js`);
}
} else {
return this.visualTest;
}
},

async _getBrowser(mode) {
this.browsers = this.browsers || {};
if (this.browsers[mode]) {
return this.browsers[mode];
}

let options = this.visualTest;
let options = this._getOptions(mode);

let flags = [
'--enable-logging'
Expand All @@ -76,7 +93,7 @@ module.exports = {
noSandbox = true;
}

this.browser = new HeadlessChrome({
this.browsers[mode] = new HeadlessChrome({
headless: true,
chrome: {
flags,
Expand All @@ -94,43 +111,43 @@ module.exports = {
});

// This is started while the app is building, so we can assume this will be ready
this._debugLog('Starting chrome instance...');
await this.browser.init();
this._debugLog(`Chrome instance initialized with port=${this.browser.port}`);
this._debugLog('Starting chrome instance...', mode);
await this.browsers[mode].init();
this._debugLog(`Chrome instance initialized with port=${this.browsers[mode].port}`, mode);

return this.browser;
return this.browsers[mode];
},

async _getBrowserTab() {
const browser = await this._getBrowser();
async _getBrowserTab(mode) {
const browser = await this._getBrowser(mode);
const tab = await browser.newTab({ privateTab: false });

tab.onConsole((options) => {
let logValue = options.map((item) => item.value).join(' ');
this._debugLog(`Browser log: ${logValue}`);
this._debugLog(`Browser log: ${logValue}`, mode);
});

return tab;
},

_imageLog(str) {
if (this.visualTest.imageLogging) {
_imageLog(str, mode) {
if (this._getOptions(mode).imageLogging) {
console.log(str);
}
},

_debugLog(str) {
if (this.visualTest.debugLogging) {
_debugLog(str, mode) {
if (this._getOptions(mode).debugLogging) {
console.log(str);
}
},

_makeScreenshots: async function(url, fileName, { selector, fullPage, delayMs }) {
let options = this.visualTest;
_makeScreenshots: async function(url, fileName, { selector, fullPage, delayMs, mode }) {
let options = this._getOptions(mode);
let tab;

try {
tab = await this._getBrowserTab();
tab = await this._getBrowserTab(mode);
} catch (e) {
console.error('Error when launching browser!');
console.error(e);
Expand All @@ -153,19 +170,19 @@ module.exports = {
let newScreenshotUrl = null;
let newBaseline = options.forceBuildVisualTestImages || !fs.existsSync(fullPath);
if (newBaseline) {
this._imageLog(`Making base screenshot ${fileName}`);
this._imageLog(`Making base screenshot ${fileName}`, mode);

await fs.outputFile(fullPath, await tab.getScreenshot(screenshotOptions, true));

newScreenshotUrl = await this._tryUploadToImgur(fullPath);
newScreenshotUrl = await this._tryUploadToImgur(fullPath, mode);
if (newScreenshotUrl) {
this._imageLog(`New screenshot can be found under ${newScreenshotUrl}`);
this._imageLog(`New screenshot can be found under ${newScreenshotUrl}`, mode);
}
}

// Always make the tmp screenshot
let fullTmpPath = `${path.join(options.imageTmpDirectory, fileName)}.png`;
this._imageLog(`Making comparison screenshot ${fileName}`);
this._imageLog(`Making comparison screenshot ${fileName}`, mode);
await fs.outputFile(fullTmpPath, await tab.getScreenshot(screenshotOptions, true));

try {
Expand All @@ -178,8 +195,8 @@ module.exports = {
return { newBaseline, newScreenshotUrl };
},

_compareImages(fileName) {
let options = this.visualTest;
_compareImages(fileName, mode) {
let options = this._getOptions(mode);
let _this = this;

if (!fileName.includes('.png')) {
Expand Down Expand Up @@ -214,8 +231,8 @@ module.exports = {
await fs.outputFile(diffPath, PNG.sync.write(diff));

RSVP.all([
_this._tryUploadToImgur(imgPath),
_this._tryUploadToImgur(diffPath)
_this._tryUploadToImgur(imgPath, mode),
_this._tryUploadToImgur(diffPath, mode)
]).then(([urlTmp, urlDiff]) => {
reject({
errorPixelCount,
Expand All @@ -228,8 +245,8 @@ module.exports = {
});
},

_tryUploadToImgur: async function(imagePath) {
let imgurClientID = this.visualTest.imgurClientId;
_tryUploadToImgur: async function(imagePath, mode) {
let imgurClientID = this._getOptions(mode).imgurClientId;

if (!imgurClientID) {
return RSVP.resolve(null);
Expand Down Expand Up @@ -265,7 +282,8 @@ module.exports = {

app.post('/visual-test/make-screenshot', (req, res) => {
let url = req.body.url;
let fileName = this._getFileName(req.body.name);
let mode = req.body.mode;
let fileName = this._getFileName(req.body.name, mode);
let selector = req.body.selector;
let fullPage = req.body.fullPage || false;
let delayMs = req.body.delayMs ? parseInt(req.body.delayMs) : 100;
Expand All @@ -278,11 +296,11 @@ module.exports = {
}

let data = {};
this._makeScreenshots(url, fileName, { selector, fullPage, delayMs }).then(({ newBaseline, newScreenshotUrl }) => {
this._makeScreenshots(url, fileName, { selector, fullPage, delayMs, mode }).then(({ newBaseline, newScreenshotUrl }) => {
data.newScreenshotUrl = newScreenshotUrl;
data.newBaseline = newBaseline;

return this._compareImages(fileName);
return this._compareImages(fileName, mode);
}).then(() => {
data.status = 'SUCCESS';
res.send(data);
Expand Down Expand Up @@ -331,19 +349,26 @@ module.exports = {
}
},

_getFileName(fileName) {
let options = this.visualTest;
_getFileName(fileName, mode) {
let options = this._getOptions(mode);

if (options.groupByOs) {
let os = options.os;
if (options.groupByOs || mode) {
let os = options.groupByOs ? this.visualTest.os : null;

const filePath = path.parse(fileName);

filePath.name = `${os}-${filePath.name}`;
if (os) {
filePath.name = `${os}-${filePath.name}`;
}
if (mode) {
filePath.name = `${filePath.name}-${mode}`;
}

delete filePath.base;

return path.format(filePath);
}

return fileName;
},

Expand Down
2 changes: 2 additions & 0 deletions tests/acceptance/visual-test-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ module('Acceptance | visual test', function(hooks) {
assert.equal(currentURL(), '/visual-test-route');

await capture(assert, 'visual-test');

await capture(assert, 'visual-test', { mode: 'mobile' });
});
});
// END-SNIPPET
Expand Down
1 change: 1 addition & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Router.map(function() {
docsRoute(this, function () {
this.route('how');
this.route('mirage');
this.route('modes');
this.route('platforms');
this.route('styles');
this.route('tech');
Expand Down
1 change: 1 addition & 0 deletions tests/dummy/app/templates/docs.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

{{nav.section 'Advanced'}}
{{nav.item 'Styles' 'docs.styles'}}
{{nav.item 'Modes' 'docs.modes'}}
{{nav.item 'ember-cli-mirage' 'docs.mirage'}}
{{nav.item 'Technical explanation' 'docs.tech'}}
{{/viewer.nav}}
Expand Down
3 changes: 2 additions & 1 deletion tests/dummy/app/templates/docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ let app = new EmberAddon(defaults, {
debugLogging: false, // If console messages from headless chrome should be printed in the console
imgurClientId: null, // If set to a client ID of imgur, images will be uploaded there as well, to debug images e.g. on CI
groupByOs: true, // If one set of images should be created/compared by OS
noSandbox: false // This may need to be set to true depending on your environment e.g. in CI
noSandbox: false // This may need to be set to true depending on your environment e.g. in CI
modes: {} // Modes configuration - learn more in the modes section
}
});
```
Expand Down
47 changes: 47 additions & 0 deletions tests/dummy/app/templates/docs/modes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Modes

If you need to test your app in multiple setups (let's say different screen sizes),
`modes` come in handy.

## Defining modes

Define your modes in the `ember-cli-build.js`:

```js
let app = new EmberAddon(defaults, {
visualTest: {
// some configuration params ommited for brevity
windowWidth: 1024,
modes: {
tablet: {
windowWidth: 768
},
mobile: {
windowWidth: 412
}
}
}
});
```
You can name your modes as you like - as long as they are a valid JavaScript object keys.

Modes support same options as the main configuration. Learn more about configuration
in the [build section](build).

Each mode configuration will be merged with the main configuration. You can skip
options in mode config if they stay the same as in the main config.


## Using modes

To use a mode just pass its name to the `capture()` helper like this:

```js
// this will use default config
// and will create visual-test.png file:
await capture(assert, 'visual-test');

// this will use mobile mode config
// and will create visual-test-mobile.png file:
await capture(assert, 'visual-test', { mode: 'mobile' });
```