From 34392dc1e995a0a076b7d866dd999b39fed5c549 Mon Sep 17 00:00:00 2001 From: Lukas Rejman <43343000+lukasr22@users.noreply.github.com> Date: Mon, 29 Apr 2019 12:56:52 +0100 Subject: [PATCH] fix: Snapshot makes shot even when its failed on specific functionality (#177) * fix: Snapshot makes shots even if selector in function not found * fix: Comments update * fix: Update height on firefox for tests * fix: E2E Cleaning state remove and setup pipeline to save records for debug * fix: Update docker composer with mapping volume * fix: update reference for firefox * fix: Snapshot added E2E tests --- .gitignore | 15 ++- .../chrome/hideElements/hideElements.test.js | 19 ---- .../removeElements/removeElements.test.js | 19 ---- e2eTests/chrome/snap/snapLocal.test.js | 6 -- e2eTests/docker-compose.yml | 8 +- .../baseline/hide-element-large.png | Bin 16382 -> 26779 bytes .../firefox/hideElements/hideElements.test.js | 19 ---- .../baseline/remove-element-large.png | Bin 13504 -> 26777 bytes .../removeElements/removeElements.test.js | 19 ---- e2eTests/firefox/snap/snapLocal.test.js | 6 -- .../gridError/gridErrorSuiteScript.test.js | 36 ++++++++ .../gridError/gridErrorSuiteScriptConfig.json | 15 +++ .../nonExistScriptSuiteScript.test.js | 36 ++++++++ .../nonExistScriptSuiteScriptConfig.json | 16 ++++ src/scenarioValidator.test.js | 59 ++++++++++++ src/snapshotter.js | 86 ++++++++++-------- src/snapshotter.test.js | 18 +++- 17 files changed, 243 insertions(+), 134 deletions(-) create mode 100644 e2eTests/generic/gridError/gridErrorSuiteScript.test.js create mode 100644 e2eTests/generic/gridError/gridErrorSuiteScriptConfig.json create mode 100644 e2eTests/generic/nonExistScript/nonExistScriptSuiteScript.test.js create mode 100644 e2eTests/generic/nonExistScript/nonExistScriptSuiteScriptConfig.json create mode 100644 src/scenarioValidator.test.js diff --git a/.gitignore b/.gitignore index d2fdbb4..97e72d0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,16 +20,27 @@ coverage/* # e2eTest folders e2eTests/generic/updateBaseline/baseline/* +e2eTests/generic/gridError/baseline/* +e2eTests/generic/nonExistScript/baseline/* e2eTests/chrome/snap/latest/* e2eTests/firefox/snap/latest/* e2eTests/generic/compare/generatedDiffs/* e2eTests/generic/compare/reports/* - +e2eTests/generic/onBeforeSuiteScript/latest/* +e2eTests/generic/gridError/latest/* +e2eTests/generic/nonExistScript/latest/* e2eTests/chrome/removeElements/latest/* e2eTests/firefox/removeElements/latest/* - +e2eTests/chrome/removeElements/generatedDiffs/* +e2eTests/firefox/removeElements/generatedDiffs/* +e2eTests/chrome/removeElements/reports/* +e2eTests/firefox/removeElements/reports/* e2eTests/chrome/hideElements/latest/* e2eTests/firefox/hideElements/latest/* +e2eTests/chrome/hideElements/generatedDiffs/* +e2eTests/firefox/hideElements/generatedDiffs/* +e2eTests/chrome/hideElements/reports/* +e2eTests/firefox/hideElements/reports/* diff --git a/e2eTests/chrome/hideElements/hideElements.test.js b/e2eTests/chrome/hideElements/hideElements.test.js index 6a0277d..64f5ecd 100644 --- a/e2eTests/chrome/hideElements/hideElements.test.js +++ b/e2eTests/chrome/hideElements/hideElements.test.js @@ -1,27 +1,8 @@ /* globals expect */ import { execSync } from 'child_process'; -import path from 'path'; -import fs from 'fs'; -import config from './hideElementsConfig'; - -function cleanState(dir) { - if (fs.existsSync(dir)) { - const files = fs.readdirSync(dir); - files.forEach(file => fs.unlinkSync(`${dir}/${file}`)); - fs.rmdirSync(dir); - } -} describe('e2e Tests hide elements', () => { - let latestMockImagesPath; - - beforeEach(() => { - latestMockImagesPath = path.resolve(config.latest); - - cleanState(latestMockImagesPath); - }); - it('hides elements using zero opacity', () => { let exitCode = 0; diff --git a/e2eTests/chrome/removeElements/removeElements.test.js b/e2eTests/chrome/removeElements/removeElements.test.js index 71b7e2c..5d41ff2 100644 --- a/e2eTests/chrome/removeElements/removeElements.test.js +++ b/e2eTests/chrome/removeElements/removeElements.test.js @@ -1,27 +1,8 @@ /* globals expect */ import { execSync } from 'child_process'; -import path from 'path'; -import fs from 'fs'; -import config from './removeElementsConfig'; - -function cleanState(dir) { - if (fs.existsSync(dir)) { - const files = fs.readdirSync(dir); - files.forEach(file => fs.unlinkSync(`${dir}/${file}`)); - fs.rmdirSync(dir); - } -} describe('e2e Tests remove elements', () => { - let latestMockImagesPath; - - beforeEach(() => { - latestMockImagesPath = path.resolve(config.latest); - - cleanState(latestMockImagesPath); - }); - it('remove elements from webpage and compares', () => { let exitCode = 0; diff --git a/e2eTests/chrome/snap/snapLocal.test.js b/e2eTests/chrome/snap/snapLocal.test.js index 96e1428..d67e46e 100644 --- a/e2eTests/chrome/snap/snapLocal.test.js +++ b/e2eTests/chrome/snap/snapLocal.test.js @@ -10,12 +10,6 @@ describe('e2e Tests taking snaps locally', () => { beforeEach(() => { dirPath = path.resolve(config.latest); - - if (fs.existsSync(dirPath)) { - const files = fs.readdirSync(dirPath); - files.forEach(file => fs.unlinkSync(`${dirPath}/${file}`)); - fs.rmdirSync(dirPath); - } }); it('should successfully take a snapshot', async () => { diff --git a/e2eTests/docker-compose.yml b/e2eTests/docker-compose.yml index a26ae36..aadb86c 100644 --- a/e2eTests/docker-compose.yml +++ b/e2eTests/docker-compose.yml @@ -2,13 +2,13 @@ version: '3' services: hub: - image: selenium/hub:3.141 + image: selenium/hub ports: - "4444:4444" networks: - selenium-grid firefox: - image: selenium/node-firefox:3.141 + image: selenium/node-firefox environment: - NODE_MAX_INSTANCES=2 - NODE_MAX_SESSION=2 @@ -21,7 +21,7 @@ services: depends_on: - hub chrome: - image: selenium/node-chrome:3.141 + image: selenium/node-chrome environment: - NODE_MAX_INSTANCES=2 - NODE_MAX_SESSION=2 @@ -48,6 +48,8 @@ services: - chrome networks: - selenium-grid + volumes: + - ./:/home/node/app/e2eTests/ ayespy_report: image: nginx diff --git a/e2eTests/firefox/hideElements/baseline/hide-element-large.png b/e2eTests/firefox/hideElements/baseline/hide-element-large.png index f63dd79b5f22416b63638bfd9931f651f7f989d5..0b564baf2edc5983fe0e6270aa1f685dd5eca098 100644 GIT binary patch delta 4996 zcmexYKl>u%1Tk(-F%C8c28PqFtB-C}!9R_0%uY zNOBV>{OM54;&f9z)k<=lR^YXvo~E@6VK)K8A8-1gb@BkkKh-N$!rq4ax_;n_{sy4e z8R`eF=m+OJVE7Mc3IxUfK&HTY2ap2>E(O*j8XxTDyV4jKIP5@8U7(xj6D`2-AHdp= zjRhS4qwOy`#WrSSgTjBb{e{Scbjx_qs2FX30YiKMQ{ZU(YanYs2A+}L{wkqyMbB~H WT1EJCK!_YDn|ZqWxvX fs.unlinkSync(`${dir}/${file}`)); - fs.rmdirSync(dir); - } -} describe('e2e Tests hide elements', () => { - let latestMockImagesPath; - - beforeEach(() => { - latestMockImagesPath = path.resolve(config.latest); - - cleanState(latestMockImagesPath); - }); - it('hides elements using zero opacity', () => { let exitCode = 0; diff --git a/e2eTests/firefox/removeElements/baseline/remove-element-large.png b/e2eTests/firefox/removeElements/baseline/remove-element-large.png index 06c096245d98f7cd7e6d9d4c561fba586e0531bd..46305b6188b0d1fa0f33514adadb9ef404c5bee1 100644 GIT binary patch literal 26777 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 IoBQwo18aIUj{pDw literal 13504 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 JvfB@y{0|KK&AR{q diff --git a/e2eTests/firefox/removeElements/removeElements.test.js b/e2eTests/firefox/removeElements/removeElements.test.js index d1b5e13..09e22c5 100644 --- a/e2eTests/firefox/removeElements/removeElements.test.js +++ b/e2eTests/firefox/removeElements/removeElements.test.js @@ -1,27 +1,8 @@ /* globals expect */ import { execSync } from 'child_process'; -import path from 'path'; -import fs from 'fs'; -import config from './removeElementsConfig'; - -function cleanState(dir) { - if (fs.existsSync(dir)) { - const files = fs.readdirSync(dir); - files.forEach(file => fs.unlinkSync(`${dir}/${file}`)); - fs.rmdirSync(dir); - } -} describe('e2e Tests remove elements', () => { - let latestMockImagesPath; - - beforeEach(() => { - latestMockImagesPath = path.resolve(config.latest); - - cleanState(latestMockImagesPath); - }); - it('remove elements from webpage and compares', () => { let exitCode = 0; diff --git a/e2eTests/firefox/snap/snapLocal.test.js b/e2eTests/firefox/snap/snapLocal.test.js index c220b07..81fc374 100644 --- a/e2eTests/firefox/snap/snapLocal.test.js +++ b/e2eTests/firefox/snap/snapLocal.test.js @@ -10,12 +10,6 @@ describe('e2e Tests taking snaps locally', () => { beforeEach(() => { dirPath = path.resolve(config.latest); - - if (fs.existsSync(dirPath)) { - const files = fs.readdirSync(dirPath); - files.forEach(file => fs.unlinkSync(`${dirPath}/${file}`)); - fs.rmdirSync(dirPath); - } }); it('should successfully take a snapshot', async () => { diff --git a/e2eTests/generic/gridError/gridErrorSuiteScript.test.js b/e2eTests/generic/gridError/gridErrorSuiteScript.test.js new file mode 100644 index 0000000..e6b2e34 --- /dev/null +++ b/e2eTests/generic/gridError/gridErrorSuiteScript.test.js @@ -0,0 +1,36 @@ +/* globals expect */ + +import path from 'path'; +import fs from 'fs'; +import { execSync } from 'child_process'; +import config from './gridErrorSuiteScriptConfig'; + +describe('e2e Tests running gridErrorSuiteScript', () => { + let dirPath; + + beforeEach(() => { + dirPath = path.resolve(config.latest); + + if (fs.existsSync(dirPath)) { + const files = fs.readdirSync(dirPath); + files.forEach(file => fs.unlinkSync(`${dirPath}/${file}`)); + fs.rmdirSync(dirPath); + } + }); + + it('throw exception and no snapshot is taken', async () => { + let exitCode = 0; + let stdout; + try { + stdout = execSync( + 'node ./lib/bin/run.js snap --browser chrome --config e2eTests/generic/gridError/gridErrorSuiteScriptConfig.json' + ).toString(); + //pipe stdout to Jest console + console.log(stdout); + } catch (error) { + exitCode = error.status; + } + + expect(exitCode).toEqual(1); + }); +}); diff --git a/e2eTests/generic/gridError/gridErrorSuiteScriptConfig.json b/e2eTests/generic/gridError/gridErrorSuiteScriptConfig.json new file mode 100644 index 0000000..9ce481d --- /dev/null +++ b/e2eTests/generic/gridError/gridErrorSuiteScriptConfig.json @@ -0,0 +1,15 @@ +{ + "gridUrl": "", + "baseline": "./e2eTests/generic/gridError/baseline", + "latest": "./e2eTests/generic/gridError/latest", + "generatedDiffs": "./e2eTests/generic/gridError/generatedDiffs", + "report": "./e2eTests/generic/gridError/reports", + "remoteBucketName": "aye-spy", + "remoteRegion": "eu-west-1", + "scenarios": [ + { + "url": "http://ayespy_report:4000", + "label": "image", + "viewports": [{"height": 2400, "width": 1024, "label": "large"}] + }] +} diff --git a/e2eTests/generic/nonExistScript/nonExistScriptSuiteScript.test.js b/e2eTests/generic/nonExistScript/nonExistScriptSuiteScript.test.js new file mode 100644 index 0000000..7b7a68d --- /dev/null +++ b/e2eTests/generic/nonExistScript/nonExistScriptSuiteScript.test.js @@ -0,0 +1,36 @@ +/* globals expect */ + +import path from 'path'; +import fs from 'fs'; +import { execSync } from 'child_process'; +import config from './nonExistScriptSuiteScriptConfig'; + +describe('e2e Tests running nonExistScriptSuiteScript', () => { + let dirPath; + + beforeEach(() => { + dirPath = path.resolve(config.latest); + + if (fs.existsSync(dirPath)) { + const files = fs.readdirSync(dirPath); + files.forEach(file => fs.unlinkSync(`${dirPath}/${file}`)); + fs.rmdirSync(dirPath); + } + }); + + it('should successfully run the snap with with non exist script', async () => { + let exitCode = 0; + let stdout; + try { + stdout = execSync( + 'node ./lib/bin/run.js snap --browser chrome --config e2eTests/generic/nonExistScript/nonExistScriptSuiteScriptConfig.json' + ).toString(); + //pipe stdout to Jest console + console.log(stdout); + } catch (error) { + exitCode = error.status; + } + + expect(exitCode).toEqual(0); + }); +}); diff --git a/e2eTests/generic/nonExistScript/nonExistScriptSuiteScriptConfig.json b/e2eTests/generic/nonExistScript/nonExistScriptSuiteScriptConfig.json new file mode 100644 index 0000000..1fe6097 --- /dev/null +++ b/e2eTests/generic/nonExistScript/nonExistScriptSuiteScriptConfig.json @@ -0,0 +1,16 @@ +{ + "gridUrl": "http://hub:4444/wd/hub", + "baseline": "./e2eTests/generic/nonExistScript/baseline", + "latest": "./e2eTests/generic/nonExistScript/latest", + "generatedDiffs": "./e2eTests/generic/nonExistScript/generatedDiffs", + "report": "./e2eTests/generic/nonExistScript/reports", + "remoteBucketName": "aye-spy", + "remoteRegion": "eu-west-1", + "scenarios": [ + { + "url": "http://ayespy_report:4000", + "label": "image", + "onReadyScript": "./nonExistScript.js", + "viewports": [{"height": 2400, "width": 1024, "label": "large"}] + }] +} diff --git a/src/scenarioValidator.test.js b/src/scenarioValidator.test.js new file mode 100644 index 0000000..ff3831e --- /dev/null +++ b/src/scenarioValidator.test.js @@ -0,0 +1,59 @@ +/* globals expect */ +import scenarioValidator from './scenarioValidator'; + +describe('Validate Scenario', () => { + it('Valid Scenario', () => { + const scenario = { + url: 'http://lol.co.uk/', + label: 'scenario-valid-viewport', + viewports: [{ width: 1024, height: 800, label: 'large' }] + }; + + expect(() => scenarioValidator(scenario)).not.toThrow(); + }); + it('Scenario missing height', () => { + const scenario = { + url: 'http://lol.co.uk/', + label: 'scenario-height-missing', + viewports: [{ width: 1024, label: 'large' }] + }; + expect(() => scenarioValidator(scenario)).toThrow( + 'scenario-height-missing has no height set' + ); + }); + + it('Scenario missing width', () => { + const scenario = { + url: 'http://lol.co.uk/', + label: 'scenario-width-missing', + viewports: [{ height: 1024, label: 'large' }] + }; + + expect(() => scenarioValidator(scenario)).toThrow( + 'scenario-width-missing has no width set' + ); + }); + + it('Scenario missing label', () => { + const scenario = { + url: 'http://lol.co.uk/', + label: 'scenario-label-missing', + viewports: [{ height: 1024, width: 800 }] + }; + + expect(() => scenarioValidator(scenario)).toThrow( + 'scenario-label-missing has no label set' + ); + }); + + it('Scenario missing viewport', () => { + const scenario = { + url: 'http://lol.co.uk/', + label: 'scenario-viewport-missing' + }; + + expect(() => scenarioValidator(scenario)).toThrow( + 'scenario-viewport-missing has no viewports array defined' + ); + }); +}); diff --git a/src/snapshotter.js b/src/snapshotter.js index 72bb9b6..328309a 100644 --- a/src/snapshotter.js +++ b/src/snapshotter.js @@ -29,6 +29,7 @@ export default class SnapShotter { }, selenium, onComplete, + onInfo, onError ) { this._label = label; @@ -53,6 +54,7 @@ export default class SnapShotter { this._until = selenium.until; this._webdriver = selenium.webdriver; this._onComplete = onComplete; + this._onInfo = onInfo; this._onError = onError; const browserCapability = this._browser.includes('chrome') @@ -204,38 +206,28 @@ export default class SnapShotter { ); } - async takeSnap() { - try { - this._driver = await new this._webdriver.Builder() - .usingServer(this._gridUrl) - .withCapabilities(this._capability) - .build(); - } catch (err) { - this._onError(); - logger.error( - 'snapshotter', - `❌ Unable to connect to the grid at ${this._gridUrl}` - ); - process.exitCode = 1; - return; - } - - try { - logger.verbose( - 'Snapshotting', - `${this._label}-${this._viewportLabel} : Url: ${this._url}` - ); - - await this.driver.get(this._url); + async driverSetup() { + this._driver = await new this._webdriver.Builder() + .usingServer(this._gridUrl) + .withCapabilities(this._capability) + .build(); + logger.verbose( + 'Snapshotting', + `${this._label}-${this._viewportLabel} : Url: ${this._url}` + ); + await this.driver.get(this._url); - await this._driver - .manage() - .window() - .setRect({ - width: this._width, - height: this._height - }); + await this._driver + .manage() + .window() + .setRect({ + width: this._width, + height: this._height + }); + } + async preSnapshootSetup() { + try { if (this._onBeforeScript) await executeScriptWithDriver(this._driver, this._onBeforeScript).catch( this.handleScriptError @@ -257,10 +249,32 @@ export default class SnapShotter { if (this._removeElements) await this.removeTheSelectors(); if (this.wait) await this.snooze(this.wait); + } catch (err) { + this._onInfo(); + logger.info(`Pre-snapshoot error: ${err}`); + } + } + + async takeSnap() { + const filename = `${this._latest}/${this._label}-${ + this._viewportLabel + }.png`; + + try { + await this.driverSetup(); + } catch (err) { + this._onError(); + logger.error( + 'snapshotter', + `❌ Unable to connect to the grid at ${this._gridUrl}` + ); + process.exitCode = 1; + return; + } + + await this.preSnapshootSetup(); - const filename = `${this._latest}/${this._label}-${ - this._viewportLabel - }.png`; + try { const screenshot = await this.driver.takeScreenshot(); if (this._cropToSelector) { @@ -272,8 +286,6 @@ export default class SnapShotter { } else { this.writeScreenshot(filename, screenshot); } - - this._onComplete(); } catch (err) { this._onError(); logger.error( @@ -282,10 +294,10 @@ export default class SnapShotter { this._viewportLabel }! ❌ : ${err}` ); - process.exitCode = 1; } finally { - await this.driver.quit(); + if (this.driver) await this.driver.quit(); } + this._onComplete(); } } diff --git a/src/snapshotter.test.js b/src/snapshotter.test.js index 0acdad9..f9b1b41 100644 --- a/src/snapshotter.test.js +++ b/src/snapshotter.test.js @@ -10,10 +10,17 @@ jest.mock('jimp'); jest.mock('./executeScript'); const onComplete = jest.fn(); +const onInfo = jest.fn(); const onError = jest.fn(); function createMockSnapshotter(config) { - return new SnapShotter(config, { webdriver, By, until }, onComplete, onError); + return new SnapShotter( + config, + { webdriver, By, until }, + onComplete, + onInfo, + onError + ); } describe('The snapshotter', () => { @@ -276,11 +283,12 @@ describe('The snapshotter', () => { onReadyScript: '/brokenfile.js' }; - logger.error = jest.fn(); + logger.info = jest.fn(); const mockSnapshot = createMockSnapshotter(config); await mockSnapshot.takeSnap(); - expect(logger.error.mock.calls.length).toBe(1); + expect(logger.error.mock.calls.length).toBe(0); + expect(logger.info.mock.calls.length).toBe(1); }); it('Returns the mobile browser capabilities when called with a mobile emulator', async () => { @@ -318,7 +326,9 @@ describe('The snapshotter', () => { onBeforeScript: 'willthrow' }).takeSnap(); } finally { - expect(onError.mock.calls.length).toBe(1); + expect(onError.mock.calls.length).toBe(0); + expect(onInfo.mock.calls.length).toBe(1); + expect(onComplete.mock.calls.length).toBe(1); } });