Skip to content

Commit e313678

Browse files
committed
feat: Make a snapshot and/or a screenshot in case of failure automatically
Use the new option `snapshogtOnError` to enable it and set the file name. Snapshots and screenshots need their directories specified for the failure snapshots.
1 parent 63426ba commit e313678

File tree

6 files changed

+172
-61
lines changed

6 files changed

+172
-61
lines changed

Gruntfile.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,24 @@ module.exports = function (grunt) {
203203
})
204204
}
205205
},
206+
{
207+
scroll: {
208+
selector: 'input',
209+
offset: {
210+
top: 0,
211+
left: 0
212+
}
213+
},
214+
clickIfVisible: 'input',
215+
wait: function (browser) {
216+
return browser.hasFocus('input')
217+
.then(function (value) {
218+
if (value === false) {
219+
throw new Error('clickIfVisible on body failed')
220+
}
221+
})
222+
}
223+
},
206224
{
207225
setValue: {
208226
selector: 'input',
@@ -272,11 +290,13 @@ module.exports = function (grunt) {
272290
isFocused: 'select',
273291
isVisible: 'select',
274292
isVisibleWithinViewport: 'select',
293+
isSelected: 'select > option:last-child',
275294
isNotEnabled: 'input',
276295
isNotExisting: 'textarea',
277296
isNotFocused: 'input',
278297
isNotVisible: 'input',
279298
isNotVisibleWithinViewport: 'input',
299+
isNotSelected: 'select > option:first-child',
280300
hasAttribute: {
281301
selector: 'input',
282302
name: 'disabled',
@@ -299,6 +319,17 @@ module.exports = function (grunt) {
299319
value: '<div class="class" tabindex="0">Text</div>'
300320
}
301321
},
322+
{
323+
clickIfVisible: 'select',
324+
wait: function (browser) {
325+
return browser.hasFocus('select')
326+
.then(function (value) {
327+
if (value === false) {
328+
throw new Error('clickIfVisible on select failed')
329+
}
330+
})
331+
}
332+
},
302333
{
303334
hasClass: {
304335
selector: 'div',
@@ -446,7 +477,8 @@ module.exports = function (grunt) {
446477
},
447478
'invalid-go': {
448479
options: {
449-
force: true
480+
force: true,
481+
screenshots: 'test/screenshots'
450482
},
451483
pages: [
452484
{
@@ -519,7 +551,6 @@ module.exports = function (grunt) {
519551
grunt.loadTasks(coverage ? 'coverage/tasks' : 'tasks')
520552

521553
const test = ['clean', 'standard',
522-
'selenium_standalone:server:install',
523554
'selenium_standalone:server:start',
524555
'connect', 'html-dom-snapshot',
525556
'selenium_standalone:server:stop', 'nodeunit']

INSTRUCTIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ Stops executing firther commands and fails current Grunt task with the specified
677677
## file
678678
Type: `String`
679679

680-
Name of the file to write the snapshot to. If it does not end with ".html" or ".htm", the extension ".html" will be appended to it.
680+
Name of the file to write the snapshot to. It will be put to lower-case before it is used. If it does not end with ".html" or ".htm", the extension ".html" will be appended to it.
681681

682682
If writing screenshots is enabled, the same name will be used for the file with the screenshot; just without the extension ".html" or ".htm", if the file name ends to it, and with the extension ".png" appended to the file name instead.
683683

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ Default options support the most usual usage scenario:
9292
fileNumberDigits: 3,
9393
fileNumberSeparator: '.',
9494
hangOnError: false,
95+
snapshotOnError: '_last-error',
9596
force: false
9697
},
9798
'google': {
@@ -209,6 +210,12 @@ Default value: false
209210

210211
If set to `true`, it will not abort the process in case of a failure. It is useful, if you want to investigate the situation on the screen right after an error occurred. If you encounter an error flip this flag and re-run the failing scenario. Once you are finished terminate the process or interrupt it by Ctrl+C.
211212

213+
### snapshotOnError
214+
Type: `String`
215+
Default value: '_last-error'
216+
217+
If set to a non-empty string, if will be used as a file name for an automatically taken snapshot and screenshot (if those are enabled), if the task execution fails.
218+
212219
#### force
213220
Type: `Boolean`
214221
Default value: false

tasks/html-dom-snapshot.js

Lines changed: 124 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,18 @@ module.exports = grunt => {
5151
fileNumberDigits: 3,
5252
fileNumberSeparator: '.',
5353
hangOnError: false,
54+
snapshotOnError: '_last-error',
5455
force: false
5556
})
5657
const target = this.target
5758
const pages = data.pages
58-
const snapshots = options.dest
59+
let snapshots = options.dest
60+
const screenshots = options.screenshots
5961
const viewport = options.viewport
6062
const webdriver = options.webdriver
6163
const browserCapabilities = options.browserCapabilities
64+
const hangOnError = options.hangOnError
65+
const snapshotOnError = options.snapshotOnError
6266
const lastViewport = {
6367
width: viewport.width,
6468
height: viewport.height
@@ -83,6 +87,8 @@ module.exports = grunt => {
8387
'Use "snapshots" with the same content.')
8488
options.snapshots = snapshots
8589
delete options.dest
90+
} else {
91+
snapshots = options.snapshots
8692
}
8793
// TODO: Remove this, as soon as the moveTo command is re-implemented.
8894
webdriver.deprecationWarnings = false
@@ -110,21 +116,29 @@ module.exports = grunt => {
110116
failed = true
111117
grunt.verbose.error(error.stack)
112118
grunt.log.error(error)
113-
if (!options.hangOnError) {
119+
})
120+
.then(() => {
121+
if (failed && snapshotOnError) {
122+
return makeFailureSnapshotAndScreenshot()
123+
.then(() => true, () => false)
124+
}
125+
})
126+
.then(() => {
127+
if (failed && !hangOnError) {
114128
return stop(false)
115129
}
116130
})
117131
.then(() => {
118132
if (failed) {
119-
const warn = options.force || options.hangOnError ? grunt.log.warn : grunt.fail.warn
133+
const warn = options.force || hangOnError ? grunt.log.warn : grunt.fail.warn
120134
warn('Taking snapshots failed.')
121-
if (options.hangOnError) {
135+
if (hangOnError) {
122136
warn('Letting the browser run for your investigation.\nTerminate this process or interrupt it by Ctrl+C, once you are finished.')
123137
}
124138
}
125139
})
126140
.then(() => {
127-
if (!(failed && options.hangOnError)) {
141+
if (!hangOnError) {
128142
done()
129143
}
130144
})
@@ -265,7 +279,7 @@ module.exports = grunt => {
265279
.keys(command)
266280
.forEach(key => {
267281
if (!(instructionKeys.includes(key) || additionalKeys.includes(key))) {
268-
throw new Error('Unrecognized instruction: "' + key + '".')
282+
throw new Error('Unrecognised instruction: "' + key + '".')
269283
}
270284
})
271285
if (!(commandInstructions.some(instruction => instruction.detected) || file)) {
@@ -294,19 +308,7 @@ module.exports = grunt => {
294308
}), viewportSet)
295309
.then(() => {
296310
if (file) {
297-
if (snapshots && screenshots) {
298-
increaseFileCount(file)
299-
return performInstruction(
300-
Promise.all([makeSnapshot(), makeScreenshot()]))
301-
}
302-
if (snapshots) {
303-
increaseFileCount(file)
304-
return performInstruction(makeSnapshot())
305-
}
306-
if (screenshots) {
307-
increaseFileCount(file)
308-
return performInstruction(makeScreenshot())
309-
}
311+
return makeSnapshotAndScreenshot()
310312
}
311313
})
312314

@@ -318,6 +320,22 @@ module.exports = grunt => {
318320
return promise
319321
}
320322

323+
function makeSnapshotAndScreenshot () {
324+
if (snapshots && screenshots) {
325+
increaseFileCount(file)
326+
return performInstruction(
327+
Promise.all([makeSnapshot(), makeScreenshot()]))
328+
}
329+
if (snapshots) {
330+
increaseFileCount(file)
331+
return performInstruction(makeSnapshot())
332+
}
333+
if (screenshots) {
334+
increaseFileCount(file)
335+
return performInstruction(makeScreenshot())
336+
}
337+
}
338+
321339
function makeSnapshot () {
322340
return client.getHTML('html')
323341
.then(saveContent)
@@ -329,53 +347,19 @@ module.exports = grunt => {
329347
}
330348

331349
function saveContent (html) {
332-
let fileName = file.toLowerCase()
333-
fileName = fileName.endsWith('.html') ||
334-
fileName.endsWith('.htm') ? file : file + '.html'
350+
let fileName = makeSnapshotName(file)
335351
if (fileNumbering) {
336352
fileName = numberFileName(fileName, fileNumbering)
337353
}
338-
fileName = join(snapshots, fileName)
339-
grunt.log.ok('Write snapshot to "' + fileName + '".')
340-
const directory = dirname(fileName)
341-
return ensureDirectory(directory)
342-
.then(() => new Promise((resolve, reject) =>
343-
writeFile(fileName, commandOptions.doctype + html,
344-
error => {
345-
if (error) {
346-
reject(error)
347-
} else {
348-
++snapshotCount
349-
resolve()
350-
}
351-
})
352-
))
354+
return writeContent(html, snapshots, fileName, commandOptions)
353355
}
354356

355357
function saveImage (png) {
356-
let fileName = file.toLowerCase()
357-
fileName = fileName.endsWith('.html')
358-
? file.substr(0, file.length - 5)
359-
: fileName.endsWith('.htm')
360-
? file.substr(0, file.length - 4) : file
358+
let fileName = makeScreenshotName(file)
361359
if (fileNumbering) {
362360
fileName = numberFileName(fileName, fileNumbering)
363361
}
364-
fileName = join(screenshots, fileName + '.png')
365-
grunt.log.ok('Write screenshot to "' + fileName + '".')
366-
const directory = dirname(fileName)
367-
return ensureDirectory(directory)
368-
.then(() => new Promise((resolve, reject) =>
369-
writeFile(fileName, Buffer.from(png.value, 'base64'),
370-
error => {
371-
if (error) {
372-
reject(error)
373-
} else {
374-
++screenshotCount
375-
resolve()
376-
}
377-
})
378-
))
362+
return writeImage(png, screenshots, fileName)
379363
}
380364

381365
function increaseFileCount (file) {
@@ -415,6 +399,89 @@ module.exports = grunt => {
415399
}
416400
}
417401

402+
function makeSnapshotName (fileName) {
403+
fileName = fileName.toLowerCase()
404+
return fileName.endsWith('.html') ||
405+
fileName.endsWith('.htm') ? fileName : fileName + '.html'
406+
}
407+
408+
function makeScreenshotName (fileName) {
409+
fileName = fileName.toLowerCase()
410+
return fileName.endsWith('.html')
411+
? fileName.substr(0, fileName.length - 5)
412+
: fileName.endsWith('.htm')
413+
? fileName.substr(0, fileName.length - 4) : fileName
414+
}
415+
416+
function makeFailureSnapshotAndScreenshot () {
417+
if (snapshots && screenshots) {
418+
return Promise.all([makeFailureSnapshot(), makeFailureScreenshot()])
419+
}
420+
if (snapshots) {
421+
return makeFailureSnapshot()
422+
}
423+
if (screenshots) {
424+
return makeFailureScreenshot()
425+
}
426+
return Promise.resolve()
427+
}
428+
429+
function makeFailureSnapshot () {
430+
return client.getHTML('html')
431+
.then(saveFailureContent)
432+
}
433+
434+
function makeFailureScreenshot () {
435+
return client.screenshot()
436+
.then(saveFailureImage)
437+
}
438+
439+
function saveFailureContent (html) {
440+
const fileName = makeSnapshotName(snapshotOnError)
441+
return writeContent(html, snapshots, fileName, options)
442+
}
443+
444+
function saveFailureImage (png) {
445+
const fileName = makeScreenshotName(snapshotOnError)
446+
return writeImage(png, screenshots, fileName)
447+
}
448+
449+
function writeContent (html, snapshots, fileName, options) {
450+
fileName = join(snapshots, fileName)
451+
grunt.log.ok('Write snapshot to "' + fileName + '".')
452+
const directory = dirname(fileName)
453+
return ensureDirectory(directory)
454+
.then(() => new Promise((resolve, reject) =>
455+
writeFile(fileName, options.doctype + html,
456+
error => {
457+
if (error) {
458+
reject(error)
459+
} else {
460+
++snapshotCount
461+
resolve()
462+
}
463+
})
464+
))
465+
}
466+
467+
function writeImage (png, screenshots, fileName) {
468+
fileName = join(screenshots, fileName + '.png')
469+
grunt.log.ok('Write screenshot to "' + fileName + '".')
470+
const directory = dirname(fileName)
471+
return ensureDirectory(directory)
472+
.then(() => new Promise((resolve, reject) =>
473+
writeFile(fileName, Buffer.from(png.value, 'base64'),
474+
error => {
475+
if (error) {
476+
reject(error)
477+
} else {
478+
++screenshotCount
479+
resolve()
480+
}
481+
})
482+
))
483+
}
484+
418485
function ensureArray (item) {
419486
if (item && !Array.isArray(item)) {
420487
item = [item]

test/pages/values.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<body>
77
<select autofocus>
88
<option value="1">One</option>
9-
<option value="2">Two</option>
9+
<option value="2" selected>Two</option>
1010
</select>
1111
<input disabled="disabled" style="display:none" type="text" value="test">
1212
<div class="class" tabindex="0">Text</div>

test/test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,11 @@ exports['html-dom-snapshot'] = {
9797
const snapshot = fs.existsSync('test/snapshots/abort.html')
9898
test.ok(!snapshot, 'abort.html')
9999
test.done()
100+
},
101+
102+
'invalid-go': function (test) {
103+
test.ok(fs.existsSync('test/snapshots/_last-error.html'), '_last-error.html')
104+
test.ok(fs.existsSync('test/screenshots/_last-error.png'), '_last-error.png')
105+
test.done()
100106
}
101107
}

0 commit comments

Comments
 (0)