From b629b3c67c20e84ba3909887a3d339361cdddad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Thu, 22 Nov 2018 20:09:06 +0100 Subject: [PATCH 1/2] initial modes implementation --- addon-test-support/helpers.js | 13 ++-- ember-cli-build.js | 7 +- index.js | 101 +++++++++++++++++---------- tests/acceptance/visual-test-test.js | 2 + 4 files changed, 78 insertions(+), 45 deletions(-) diff --git a/addon-test-support/helpers.js b/addon-test-support/helpers.js index 1f0ef31..4ad0c11 100644 --- a/addon-test-support/helpers.js +++ b/addon-test-support/helpers.js @@ -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); @@ -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; @@ -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); @@ -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'); diff --git a/ember-cli-build.js b/ember-cli-build.js index 06635d7..b7a089e 100644 --- a/ember-cli-build.js +++ b/ember-cli-build.js @@ -10,7 +10,12 @@ module.exports = function(defaults) { visualTest: { imageLogging: true, debugLogging: true, - imgurClientId: '6331cc0a93af83c' + imgurClientId: '6331cc0a93af83c', + modes: { + mobile: { + windowWidth: 412 + } + } }, fingerprint: { exclude: [ diff --git a/index.js b/index.js index 91431cc..03985ae 100644 --- a/index.js +++ b/index.js @@ -31,7 +31,8 @@ module.exports = { chromePort: 0, windowWidth: 1024, windowHeight: 768, - noSandbox: false + noSandbox: false, + modes: {} }, included(app) { @@ -39,6 +40,9 @@ module.exports = { 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; @@ -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' @@ -76,7 +93,7 @@ module.exports = { noSandbox = true; } - this.browser = new HeadlessChrome({ + this.browsers[mode] = new HeadlessChrome({ headless: true, chrome: { flags, @@ -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); @@ -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 { @@ -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')) { @@ -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, @@ -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); @@ -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; @@ -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); @@ -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; }, diff --git a/tests/acceptance/visual-test-test.js b/tests/acceptance/visual-test-test.js index 8da81e4..e651e83 100644 --- a/tests/acceptance/visual-test-test.js +++ b/tests/acceptance/visual-test-test.js @@ -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 From 52f14a3f9f8d648aa6647ecfbc266dd926f7fb51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82?= Date: Sun, 25 Nov 2018 15:26:45 +0100 Subject: [PATCH 2/2] modes docs --- tests/dummy/app/router.js | 1 + tests/dummy/app/templates/docs.hbs | 1 + tests/dummy/app/templates/docs/build.md | 3 +- tests/dummy/app/templates/docs/modes.md | 47 +++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tests/dummy/app/templates/docs/modes.md diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js index ebdc1f6..2f43ed8 100644 --- a/tests/dummy/app/router.js +++ b/tests/dummy/app/router.js @@ -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'); diff --git a/tests/dummy/app/templates/docs.hbs b/tests/dummy/app/templates/docs.hbs index 896b46a..e8136b8 100644 --- a/tests/dummy/app/templates/docs.hbs +++ b/tests/dummy/app/templates/docs.hbs @@ -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}} diff --git a/tests/dummy/app/templates/docs/build.md b/tests/dummy/app/templates/docs/build.md index 4097297..9808d47 100644 --- a/tests/dummy/app/templates/docs/build.md +++ b/tests/dummy/app/templates/docs/build.md @@ -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 } }); ``` diff --git a/tests/dummy/app/templates/docs/modes.md b/tests/dummy/app/templates/docs/modes.md new file mode 100644 index 0000000..ea30a49 --- /dev/null +++ b/tests/dummy/app/templates/docs/modes.md @@ -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' }); +```