Skip to content

Commit

Permalink
feat(screenshot): runs our callback first to capture right screenshot
Browse files Browse the repository at this point in the history
  • Loading branch information
bahmutov committed Jan 30, 2017
1 parent 6e97354 commit a36a8d4
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 10 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
node_modules/
npm-debug.log
failed-*.json
.DS_Store
cypress/screenshots/
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
}
```

Expand Down
7 changes: 4 additions & 3 deletions cypress/integration/failing-spec.js
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
47 changes: 41 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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
Expand All @@ -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)

0 comments on commit a36a8d4

Please sign in to comment.