From a36a8d46cc48496962cd3fadbf086b8a40159ba2 Mon Sep 17 00:00:00 2001 From: Gleb Bahmutov Date: Sun, 29 Jan 2017 23:40:34 -0500 Subject: [PATCH] feat(screenshot): runs our callback first to capture right screenshot --- .gitignore | 2 ++ README.md | 4 ++- cypress/integration/failing-spec.js | 7 +++-- src/index.js | 47 +++++++++++++++++++++++++---- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 4b4cfc3..202149c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ npm-debug.log failed-*.json +.DS_Store +cypress/screenshots/ diff --git a/README.md b/README.md index 4a983a4..7c76215 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ title - the name of the test testName - full name of the test, including the suite name testError - error message string testCommands - array of strings, last failing command is last +screenshot - filename of PNG file taken right after failure ``` ## Example @@ -69,7 +70,8 @@ and each test command before the test are recorded "assert expected **#/about** to equal **#/about**", "contains Join Us", "assert expected **body :not(script):contains(**'Join Us'**), [type='submit'][value~='Join Us']** to exist in the DOM" - ] + ], + "screenshot": "opens-login.png" } ``` diff --git a/cypress/integration/failing-spec.js b/cypress/integration/failing-spec.js index 8938ae8..b17713e 100644 --- a/cypress/integration/failing-spec.js +++ b/cypress/integration/failing-spec.js @@ -1,13 +1,14 @@ describe('cypress failed log', () => { const url = 'https://glebbahmutov.com' - beforeEach(() => { + + beforeEach(function openUrl () { cy.visit(url) }) - afterEach(() => { + afterEach(function makeDummyCommands () { // more dummy commands on purpose. Can we get - // the right screenshot when the test actuall failed? + // the right screenshot when the test actual failed? cy.visit(url) .wait(100) .wait(100) diff --git a/src/index.js b/src/index.js index 2caf93b..815ccac 100644 --- a/src/index.js +++ b/src/index.js @@ -6,8 +6,9 @@ const reject = require('lodash.reject') const cleanupFilename = s => kebabCase(deburr(s)) -function writeFailedTestInfo ({title, testName, testError, testCommands}) { - const info = {title, testName, testError, testCommands} +function writeFailedTestInfo ({title, testName, testError, testCommands, + screenshot}) { + const info = {title, testName, testError, testCommands, screenshot} const str = JSON.stringify(info, null, 2) + '\n' const cleaned = cleanupFilename(testName) const filename = `failed-${cleaned}.json` @@ -83,6 +84,10 @@ function onFailed () { return } const title = this.currentTest.title + + const screenshotName = `${cleanupFilename(title)}-failed` + cy.screenshot(screenshotName) + const testName = this.currentTest.fullTitle() const testError = this.currentTest.err.message // when running with UI, there are currentTest.commands @@ -95,16 +100,46 @@ function onFailed () { // so filter and cleanup const testCommands = reject(commands.filter(notEmpty), duplicate) + const screenshot = `${screenshotName}.png` + console.log('=== test failed ===') console.log(title) console.log(testName) - console.log('=== commands ===') - console.log(testCommands.join('\n')) console.log('=== error ===') console.log(testError) - writeFailedTestInfo({title, testName, testError, testCommands}) + console.log('=== commands ===') + console.log(testCommands.join('\n')) + console.log('=== screenshot ===') + console.log(screenshot) + writeFailedTestInfo({ + title, testName, testError, testCommands, screenshot + }) +} + +// We have to do a hack to make sure OUR "afterEach" callback function +// runs BEFORE any user supplied "afterEach" callback. This is necessary +// to take screenshot of the failure AS SOON AS POSSIBLE. +// Otherwise commands executed by the user callback might destroys the +// screen and add too many commands to the log, making post-mortem +// triage very difficult. In this case we just wrap client supplied +// "afterEach" function with our callback "onFailed". This ensures we run +// first. + +const _afterEach = afterEach +afterEach = (name, fn) => { // eslint-disable-line + if (typeof name === 'function') { + fn = name + name = fn.name + } + // before running the client function "fn" + // run our "onFailed" to capture the screenshot sooner + _afterEach(name, function () { + // TODO prevent running multiple times if there are multiple + // "afterEach" blocks in single suite + onFailed.call(this) + fn() + }) } startLogging() beforeEach(initLog) -afterEach(onFailed)