Skip to content

Commit 0e7d882

Browse files
davidmunechikaflotwigchrisbreiding
authored
feat(driver): Add "overwrite" option to cy.screenshot() (#18280)
Co-authored-by: Zach Bloomquist <github@chary.us> Co-authored-by: Chris Breiding <chrisbreiding@users.noreply.github.com>
1 parent cb5eeec commit 0e7d882

File tree

6 files changed

+59
-17
lines changed

6 files changed

+59
-17
lines changed

cli/types/cypress.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2992,6 +2992,7 @@ declare namespace Cypress {
29922992
disableTimersAndAnimations: boolean
29932993
padding: Padding
29942994
scale: boolean
2995+
overwrite: boolean
29952996
onBeforeScreenshot: ($el: JQuery) => void
29962997
onAfterScreenshot: ($el: JQuery, props: {
29972998
path: string

packages/driver/cypress/integration/commands/screenshot_spec.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ describe('src/cy/commands/screenshot', () => {
2727
capture: 'viewport',
2828
screenshotOnRunFailure: true,
2929
disableTimersAndAnimations: true,
30+
overwrite: false,
3031
scale: true,
3132
blackout: ['.foo'],
3233
}
@@ -135,6 +136,7 @@ describe('src/cy/commands/screenshot', () => {
135136
isOpen: true,
136137
appOnly: false,
137138
scale: true,
139+
overwrite: false,
138140
waitForCommandSynchronization: true,
139141
disableTimersAndAnimations: true,
140142
blackout: [],
@@ -146,6 +148,7 @@ describe('src/cy/commands/screenshot', () => {
146148
isOpen: false,
147149
appOnly: false,
148150
scale: true,
151+
overwrite: false,
149152
waitForCommandSynchronization: true,
150153
disableTimersAndAnimations: true,
151154
blackout: [],
@@ -276,7 +279,7 @@ describe('src/cy/commands/screenshot', () => {
276279
})
277280
})
278281

279-
it('takes screenshot of hook title with test', () => {})
282+
it('takes screenshot of hook title with test', () => { })
280283
})
281284

282285
context('#screenshot', () => {
@@ -411,6 +414,7 @@ describe('src/cy/commands/screenshot', () => {
411414
isOpen: true,
412415
appOnly: true,
413416
scale: true,
417+
overwrite: false,
414418
waitForCommandSynchronization: false,
415419
disableTimersAndAnimations: true,
416420
blackout: ['.foo'],
@@ -431,6 +435,7 @@ describe('src/cy/commands/screenshot', () => {
431435
isOpen: false,
432436
appOnly: true,
433437
scale: true,
438+
overwrite: false,
434439
waitForCommandSynchronization: false,
435440
disableTimersAndAnimations: true,
436441
blackout: ['.foo'],
@@ -453,6 +458,7 @@ describe('src/cy/commands/screenshot', () => {
453458
isOpen: true,
454459
appOnly: false,
455460
scale: true,
461+
overwrite: false,
456462
waitForCommandSynchronization: true,
457463
disableTimersAndAnimations: true,
458464
blackout: [],
@@ -474,6 +480,7 @@ describe('src/cy/commands/screenshot', () => {
474480
isOpen: true,
475481
appOnly: true,
476482
scale: true,
483+
overwrite: false,
477484
waitForCommandSynchronization: false,
478485
disableTimersAndAnimations: true,
479486
blackout: ['.foo'],

packages/driver/src/cy/commands/screenshot.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ const takeScreenshot = (Cypress, state, screenshotConfig, options = {}) => {
284284
capture,
285285
padding,
286286
clip,
287+
overwrite,
287288
disableTimersAndAnimations,
288289
onBeforeScreenshot,
289290
onAfterScreenshot,
@@ -313,6 +314,7 @@ const takeScreenshot = (Cypress, state, screenshotConfig, options = {}) => {
313314
waitForCommandSynchronization: !isAppOnly(screenshotConfig),
314315
disableTimersAndAnimations,
315316
blackout: getBlackout(screenshotConfig),
317+
overwrite,
316318
}
317319
}
318320

@@ -353,6 +355,7 @@ const takeScreenshot = (Cypress, state, screenshotConfig, options = {}) => {
353355
},
354356
scaled: getShouldScale(screenshotConfig),
355357
blackout: getBlackout(screenshotConfig),
358+
overwrite,
356359
startTime: startTime.toISOString(),
357360
})
358361

@@ -450,7 +453,7 @@ export default function (Commands, Cypress, cy, state, config) {
450453

451454
const isWin = $dom.isWindow(subject)
452455

453-
let screenshotConfig = _.pick(options, 'capture', 'scale', 'disableTimersAndAnimations', 'blackout', 'waitForCommandSynchronization', 'padding', 'clip', 'onBeforeScreenshot', 'onAfterScreenshot')
456+
let screenshotConfig = _.pick(options, 'capture', 'scale', 'disableTimersAndAnimations', 'overwrite', 'blackout', 'waitForCommandSynchronization', 'padding', 'clip', 'onBeforeScreenshot', 'onAfterScreenshot')
454457

455458
screenshotConfig = $Screenshot.validate(screenshotConfig, 'screenshot', options._log)
456459
screenshotConfig = _.extend($Screenshot.getConfig(), screenshotConfig)

packages/driver/src/cypress/screenshot.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,9 @@ const validate = (props, cmd, log) => {
133133
values.capture = capture
134134
}
135135

136-
validateAndSetBoolean(props, values, cmd, log, 'scale')
137-
validateAndSetBoolean(props, values, cmd, log, 'disableTimersAndAnimations')
138-
validateAndSetBoolean(props, values, cmd, log, 'screenshotOnRunFailure')
136+
['scale', 'disableTimersAndAnimations', 'screenshotOnRunFailure', 'overwrite'].forEach((key) => {
137+
validateAndSetBoolean(props, values, cmd, log, key)
138+
})
139139

140140
if (blackout) {
141141
const existsNonString = _.some(blackout, (selector) => {

packages/server/lib/screenshots.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -298,8 +298,9 @@ const getDimensions = function (details) {
298298
return pick(details.image.bitmap)
299299
}
300300

301-
const ensureSafePath = function (withoutExt, extension, num = 0) {
302-
const suffix = `${num ? ` (${num})` : ''}.${extension}`
301+
const ensureSafePath = function (withoutExt, extension, overwrite, num = 0) {
302+
const suffix = `${(num && !overwrite) ? ` (${num})` : ''}.${extension}`
303+
303304
const maxSafePrefixBytes = maxSafeBytes - suffix.length
304305
const filenameBuf = Buffer.from(path.basename(withoutExt))
305306

@@ -315,8 +316,8 @@ const ensureSafePath = function (withoutExt, extension, num = 0) {
315316

316317
return fs.pathExists(fullPath)
317318
.then((found) => {
318-
if (found) {
319-
return ensureSafePath(withoutExt, extension, num + 1)
319+
if (found && !overwrite) {
320+
return ensureSafePath(withoutExt, extension, overwrite, num + 1)
320321
}
321322

322323
// path does not exist, attempt to create it to check for an ENAMETOOLONG error
@@ -328,7 +329,7 @@ const ensureSafePath = function (withoutExt, extension, num = 0) {
328329
if (err.code === 'ENAMETOOLONG' && maxSafePrefixBytes >= MIN_PREFIX_BYTES) {
329330
maxSafeBytes -= 1
330331

331-
return ensureSafePath(withoutExt, extension, num)
332+
return ensureSafePath(withoutExt, extension, overwrite, num)
332333
}
333334

334335
throw err
@@ -342,7 +343,7 @@ const sanitizeToString = (title) => {
342343
return sanitize(_.toString(title))
343344
}
344345

345-
const getPath = function (data, ext, screenshotsFolder) {
346+
const getPath = function (data, ext, screenshotsFolder, overwrite) {
346347
let names
347348
const specNames = (data.specName || '')
348349
.split(pathSeparatorRe)
@@ -371,13 +372,13 @@ const getPath = function (data, ext, screenshotsFolder) {
371372

372373
const withoutExt = path.join(screenshotsFolder, ...specNames, ...names)
373374

374-
return ensureSafePath(withoutExt, ext)
375+
return ensureSafePath(withoutExt, ext, overwrite)
375376
}
376377

377378
const getPathToScreenshot = function (data, details, screenshotsFolder) {
378379
const ext = mime.getExtension(getType(details))
379380

380-
return getPath(data, ext, screenshotsFolder)
381+
return getPath(data, ext, screenshotsFolder, data.overwrite)
381382
}
382383

383384
module.exports = {
@@ -392,9 +393,8 @@ module.exports = {
392393
copy (src, dest) {
393394
return fs
394395
.copyAsync(src, dest, { overwrite: true })
395-
.catch({ code: 'ENOENT' }, () => {})
396+
.catch({ code: 'ENOENT' }, () => { })
396397
},
397-
// dont yell about ENOENT errors
398398

399399
get (screenshotsFolder) {
400400
// find all files in all nested dirs

packages/server/test/support/fixtures/projects/e2e/cypress/integration/screenshots_spec.js

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,13 +302,44 @@ describe('taking screenshots', () => {
302302
})
303303
})
304304

305+
// @see https://github.com/cypress-io/cypress/issues/7955
306+
it('can pass overwrite option to replace existing filename', () => {
307+
cy.viewport(600, 200)
308+
cy.visit('http://localhost:3322/color/yellow')
309+
cy.screenshot('overwrite-test', {
310+
overwrite: false,
311+
clip: { x: 10, y: 10, width: 160, height: 80 },
312+
})
313+
314+
cy.task('check:screenshot:size', {
315+
name: `${path.basename(__filename)}/overwrite-test.png`,
316+
width: 160,
317+
height: 80,
318+
devicePixelRatio,
319+
})
320+
321+
cy.screenshot('overwrite-test', {
322+
overwrite: true,
323+
clip: { x: 10, y: 10, width: 100, height: 50 },
324+
})
325+
326+
cy.readFile(`cypress/screenshots/${path.basename(__filename)}/overwrite-test (1).png`).should('not.exist')
327+
328+
cy.task('check:screenshot:size', {
329+
name: `${path.basename(__filename)}/overwrite-test.png`,
330+
width: 100,
331+
height: 50,
332+
devicePixelRatio,
333+
})
334+
})
335+
305336
context('before hooks', () => {
306337
before(() => {
307338
// failure 2
308339
throw new Error('before hook failing')
309340
})
310341

311-
it('empty test 1', () => {})
342+
it('empty test 1', () => { })
312343
})
313344

314345
context('each hooks', () => {
@@ -322,7 +353,7 @@ describe('taking screenshots', () => {
322353
throw new Error('after each hook failed')
323354
})
324355

325-
it('empty test 2', () => {})
356+
it('empty test 2', () => { })
326357
})
327358

328359
context(`really long test title ${Cypress._.repeat('a', 255)}`, () => {

0 commit comments

Comments
 (0)