UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

1,139 lines (900 loc) 37.3 kB
const $ = require('jquery') const { _, Promise, Screenshot } = Cypress const getViewportHeight = () => { return Math.min(cy.state('viewportHeight'), $(cy.state('window')).height()) } describe('src/cy/commands/screenshot', () => { beforeEach(function () { cy.stub(Cypress, 'automation').callThrough() this.serverResult = { path: '/path/to/screenshot', size: 12, dimensions: { width: 20, height: 20 }, multipart: false, pixelRatio: 1, takenAt: new Date().toISOString(), name: 'name', blackout: ['.foo'], testAttemptIndex: 0, duration: 100, } this.screenshotConfig = { capture: 'viewport', screenshotOnRunFailure: true, disableTimersAndAnimations: true, scale: true, blackout: ['.foo'], } }) context('runnable:after:run:async', () => { it('is noop when not isTextTerminal', () => { // backup this property so we set it back to whatever // is correct based on what mode we're currently in const isTextTerminal = Cypress.config('isTextTerminal') Cypress.config('isTextTerminal', false) cy.spy(Cypress, 'action').log(false) const test = { err: new Error, } const runnable = cy.state('runnable') Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { expect(Cypress.action).not.to.be.calledWith('test:set:state') expect(Cypress.automation).not.to.be.called }) .finally(() => { Cypress.config('isTextTerminal', isTextTerminal) }) }) it('is noop when no test.err', () => { Cypress.config('isInteractive', false) cy.spy(Cypress, 'action').log(false) const test = {} const runnable = cy.state('runnable') Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { expect(Cypress.action).not.to.be.calledWith('test:set:state') expect(Cypress.automation).not.to.be.called }) }) it('is noop when screenshotOnRunFailure is false', () => { Cypress.config('isInteractive', false) cy.stub(Screenshot, 'getConfig').returns({ screenshotOnRunFailure: false, }) cy.spy(Cypress, 'action').log(false) const test = { err: new Error, } const runnable = cy.state('runnable') Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { expect(Cypress.action).not.to.be.calledWith('test:set:state') expect(Cypress.automation).not.to.be.called }) }) it('is noop when screenshotOnRunFailure is false', () => { Cypress.config('isInteractive', false) Cypress.config('screenshotOnRunFailure', false) cy.spy(Cypress, 'action').log(false) const test = { err: new Error, } const runnable = cy.state('runnable') Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { expect(Cypress.action).not.to.be.calledWith('cy:test:set:state') expect(Cypress.automation).not.to.be.called }) }) it('sends before/after events', function () { Cypress.config('isInteractive', false) this.screenshotConfig.scale = false cy.stub(Screenshot, 'getConfig').returns(this.screenshotConfig) Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.stub(Cypress, 'action').log(false) .callThrough() .withArgs('cy:before:screenshot') .yieldsAsync() const test = { id: '123', err: new Error() } const runnable = cy.state('runnable') Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { expect(Cypress.action).to.be.calledWith('cy:before:screenshot', { id: runnable.id, isOpen: true, appOnly: false, scale: true, waitForCommandSynchronization: true, disableTimersAndAnimations: true, blackout: [], testAttemptIndex: 0, }) expect(Cypress.action).to.be.calledWith('cy:after:screenshot', { id: runnable.id, isOpen: false, appOnly: false, scale: true, waitForCommandSynchronization: true, disableTimersAndAnimations: true, blackout: [], testAttemptIndex: 0, }) }) }) it('takes screenshot when not isInteractive', function () { Cypress.config('isInteractive', false) cy.stub(Screenshot, 'getConfig').returns(this.screenshotConfig) Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) const test = { id: '123', err: new Error, } const runnable = cy.state('runnable') Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { expect(Cypress.automation).to.be.calledWith('take:screenshot') let args = Cypress.automation.withArgs('take:screenshot').args[0][1] args = _.omit(args, 'padding', 'clip', 'userClip', 'viewport', 'takenPaths', 'startTime') expect(args).to.eql({ testId: runnable.id, titles: [ 'src/cy/commands/screenshot', 'runnable:after:run:async', runnable.title, ], capture: 'runner', simple: true, testFailure: true, blackout: [], scaled: true, testAttemptIndex: 0, }) }) }) describe('if screenshot has been taken in test', () => { beforeEach(() => { cy.state('screenshotTaken', true) }) it('sends simple: false', function () { Cypress.config('isInteractive', false) cy.stub(Screenshot, 'getConfig').returns(this.screenshotConfig) Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) const test = { id: '123', err: new Error, } const runnable = cy.state('runnable') Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { expect(Cypress.automation.withArgs('take:screenshot')).to.be.calledOnce let args = Cypress.automation.withArgs('take:screenshot').args[0][1] args = _.omit(args, 'padding', 'clip', 'userClip', 'viewport', 'takenPaths', 'startTime') expect(args).to.eql({ testId: runnable.id, titles: [ 'src/cy/commands/screenshot', 'runnable:after:run:async', 'if screenshot has been taken in test', runnable.title, ], capture: 'runner', testFailure: true, simple: false, scaled: true, blackout: [], testAttemptIndex: 0, }) }) }) }) }) context('runnable:after:run:async hooks', () => { beforeEach(function () { Cypress.config('isInteractive', false) cy.stub(Screenshot, 'getConfig').returns(this.screenshotConfig) Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) const test = { id: '123', err: new Error, } const runnable = cy.state('runnable') Cypress.action('runner:runnable:after:run:async', test, runnable) .then(() => { expect(Cypress.automation).to.be.calledWith('take:screenshot') let args = Cypress.automation.withArgs('take:screenshot').args[0][1] args = _.omit(args, 'padding', 'clip', 'userClip', 'viewport', 'takenPaths', 'startTime') expect(args).to.eql({ testId: runnable.id, titles: [ 'src/cy/commands/screenshot', 'runnable:after:run:async hooks', 'takes screenshot of hook title with test', '"before each" hook', ], capture: 'runner', simple: true, testFailure: true, scaled: true, blackout: [], testAttemptIndex: 0, }) }) }) it('takes screenshot of hook title with test', () => {}) }) context('#screenshot', () => { beforeEach(function () { cy.stub(Screenshot, 'getConfig').returns(this.screenshotConfig) }) it('sets name to undefined when not passed name', function () { const runnable = cy.state('runnable') runnable.title = 'foo bar' Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.screenshot().then(() => { expect(Cypress.automation.withArgs('take:screenshot').args[0][1].name).to.be.undefined }) }) it('can pass name', function () { const runnable = cy.state('runnable') runnable.title = 'foo bar' Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.screenshot('my/file').then(() => { expect(Cypress.automation.withArgs('take:screenshot').args[0][1].name).to.equal('my/file') }) }) it('calls onBeforeScreenshot callback with documentElement', function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.stub(Screenshot, 'onBeforeScreenshot') cy.spy(Cypress, 'action').log(false) cy .screenshot('foo') .then(() => { expect(Screenshot.onBeforeScreenshot).to.be.calledOnce expect(Screenshot.onBeforeScreenshot.firstCall.args[0].get(0)).to.eq(cy.state('document').documentElement) }) }) it('calls onAfterScreenshot callback with documentElement', function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.stub(Screenshot, 'onAfterScreenshot') cy.spy(Cypress, 'action').log(false) cy .screenshot('foo') .then(() => { expect(Screenshot.onAfterScreenshot).to.be.calledOnce expect(Screenshot.onAfterScreenshot.firstCall.args[0].get(0)).to.eq(cy.state('document').documentElement) }) }) it('pauses then unpauses timers if disableTimersAndAnimations is true', function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.spy(Cypress, 'action').log(false) cy.spy(cy, 'pauseTimers') cy .screenshot('foo') .then(() => { expect(cy.pauseTimers).to.be.calledWith(true) expect(cy.pauseTimers).to.be.calledWith(false) }) }) it('does not pause timers if disableTimersAndAnimations is false', function () { this.screenshotConfig.disableTimersAndAnimations = false Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.spy(Cypress, 'action').log(false) cy .screenshot('foo') .then(() => { expect(Cypress.action.withArgs('cy:pause:timers')).not.to.be.called }) }) it('sends clip as userClip if specified', function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.spy(Cypress, 'action').log(false) const clip = { width: 100, height: 100, x: 0, y: 0 } cy .screenshot({ clip }) .then(() => { expect(Cypress.automation.withArgs('take:screenshot').args[0][1].userClip).to.equal(clip) }) }) it('sends viewport dimensions of main browser window', function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.spy(Cypress, 'action').log(false) cy .screenshot() .then(() => { expect(Cypress.automation.withArgs('take:screenshot').args[0][1].viewport).to.eql({ width: window.parent.innerWidth, height: window.parent.innerHeight, }) }) }) it('can handle window w/length > 1 as a subject', () => { cy.visit('/fixtures/dom.html') cy.window().should('have.length.gt', 1) .screenshot() }) describe('before/after events', () => { beforeEach(function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.spy(Cypress, 'action').log(false) }) it('sends before:screenshot', () => { const runnable = cy.state('runnable') cy .screenshot('foo') .then(() => { expect(Cypress.action.withArgs('cy:before:screenshot')).to.be.calledOnce expect(Cypress.action.withArgs('cy:before:screenshot').args[0][1]).to.eql({ id: runnable.id, isOpen: true, appOnly: true, scale: true, waitForCommandSynchronization: false, disableTimersAndAnimations: true, blackout: ['.foo'], testAttemptIndex: 0, }) }) }) it('sends after:screenshot', () => { const runnable = cy.state('runnable') cy .screenshot('foo') .then(() => { expect(Cypress.action.withArgs('cy:after:screenshot')).to.be.calledOnce expect(Cypress.action.withArgs('cy:after:screenshot').args[0][1]).to.eql({ id: runnable.id, isOpen: false, appOnly: true, scale: true, waitForCommandSynchronization: false, disableTimersAndAnimations: true, blackout: ['.foo'], testAttemptIndex: 0, }) }) }) it('always sends scale: true, waitForCommandSynchronization: true, and blackout: [] for non-app captures', function () { const runnable = cy.state('runnable') this.screenshotConfig.capture = 'runner' this.screenshotConfig.scale = false cy .screenshot('foo') .then(() => { expect(Cypress.action.withArgs('cy:before:screenshot').args[0][1]).to.eql({ id: runnable.id, isOpen: true, appOnly: false, scale: true, waitForCommandSynchronization: true, disableTimersAndAnimations: true, blackout: [], testAttemptIndex: 0, }) }) }) it('always sends waitForCommandSynchronization: false for viewport/fullPage captures', function () { const runnable = cy.state('runnable') this.screenshotConfig.waitForAnimations = true cy .screenshot('foo') .then(() => { expect(Cypress.action.withArgs('cy:before:screenshot').args[0][1]).to.eql({ id: runnable.id, isOpen: true, appOnly: true, scale: true, waitForCommandSynchronization: false, disableTimersAndAnimations: true, blackout: ['.foo'], testAttemptIndex: 0, }) }) }) }) describe('capture: fullPage', () => { beforeEach(function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.spy(Cypress, 'action').log(false) cy.viewport(600, 200) cy.visit('/fixtures/screenshots.html') }) it('takes a screenshot for each time it needs to scroll', () => { cy.screenshot({ capture: 'fullPage' }) .then(() => { expect(Cypress.automation.withArgs('take:screenshot')).to.be.calledThrice }) }) it('sends capture: fullPage', () => { cy.screenshot({ capture: 'fullPage' }) .then(() => { const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].capture).to.equal('fullPage') expect(take.args[1][1].capture).to.equal('fullPage') expect(take.args[2][1].capture).to.equal('fullPage') }) }) it('sends number of current screenshot for each time it needs to scroll', () => { cy.screenshot({ capture: 'fullPage' }) .then(() => { const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].current).to.equal(1) expect(take.args[1][1].current).to.equal(2) expect(take.args[2][1].current).to.equal(3) }) }) it('sends total number of screenshots for each time it needs to scroll', () => { cy.screenshot({ capture: 'fullPage' }) .then(() => { const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].total).to.equal(3) expect(take.args[1][1].total).to.equal(3) expect(take.args[2][1].total).to.equal(3) }) }) it('scrolls the window to the right place for each screenshot', () => { const win = cy.state('window') win.scrollTo(0, 100) const scrollTo = cy.spy(win, 'scrollTo') cy.screenshot({ capture: 'fullPage' }) .then(() => { expect(scrollTo.getCall(0).args.join(',')).to.equal('0,0') expect(scrollTo.getCall(1).args.join(',')).to.equal('0,200') expect(scrollTo.getCall(2).args.join(',')).to.equal('0,400') }) }) it('scrolls the window back to the original place', () => { const win = cy.state('window') win.scrollTo(0, 100) const scrollTo = cy.spy(win, 'scrollTo') cy.screenshot({ capture: 'fullPage' }) .then(() => { expect(scrollTo.getCall(3).args.join(',')).to.equal('0,100') }) }) it('sends the right clip values', () => { cy.screenshot({ capture: 'fullPage' }) .then(() => { const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].clip).to.eql({ x: 0, y: 0, width: 600, height: 200 }) expect(take.args[1][1].clip).to.eql({ x: 0, y: 0, width: 600, height: 200 }) expect(take.args[2][1].clip).to.eql({ x: 0, y: 120, width: 600, height: 80 }) }) }) }) describe('element capture', () => { beforeEach(function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.spy(Cypress, 'action').log(false) cy.viewport(600, 200) cy.visit('/fixtures/screenshots.html') }) it('yields an object with details', function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.stub(Screenshot, 'onAfterScreenshot') cy.stub(Screenshot, 'onBeforeScreenshot') cy .get('.tall-element') .screenshot('name', { onBeforeScreenshot ($el) { expect($el).to.match('.tall-element') }, onAfterScreenshot ($el, results) { expect($el).to.match('.tall-element') expect(results).to.deep.eq(this.serverResult) expect(results.name).to.eq('name') expect(results.blackout).to.eql(this.screenshotConfig.blackout) expect(results.dimensions).to.eql(this.serverResult.dimensions) expect(Screenshot.onBeforeScreenshot).to.be.calledOnce expect(Screenshot.onBeforeScreenshot.firstCall.args[0]).to.match('.tall-element') expect(Screenshot.onAfterScreenshot).not.to.be.called }, }) .then(() => { expect(Screenshot.onAfterScreenshot).to.be.calledOnce expect(Screenshot.onAfterScreenshot.firstCall.args[0]).to.match('.tall-element') }) }) it('takes a screenshot for each time it needs to scroll', () => { cy.get('.tall-element').screenshot() .then(() => { expect(Cypress.automation.withArgs('take:screenshot')).to.be.calledTwice }) }) it('sends number of current screenshot for each time it needs to scroll', () => { cy.get('.tall-element').screenshot() .then(() => { const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].current).to.equal(1) expect(take.args[1][1].current).to.equal(2) }) }) it('sends total number of screenshots for each time it needs to scroll', () => { cy.get('.tall-element').screenshot() .then(() => { const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].total).to.equal(2) expect(take.args[1][1].total).to.equal(2) }) }) it('scrolls the window to the right place for each screenshot', () => { const win = cy.state('window') win.scrollTo(0, 100) const scrollTo = cy.spy(win, 'scrollTo') cy.get('.tall-element').screenshot() .then(() => { expect(scrollTo.getCall(0).args.join(',')).to.equal('0,140') expect(scrollTo.getCall(1).args.join(',')).to.equal('0,340') }) }) it('scrolls the window back to the original place', () => { const win = cy.state('window') win.scrollTo(0, 100) const scrollTo = cy.spy(win, 'scrollTo') cy.get('.tall-element').screenshot() .then(() => { expect(scrollTo.getCall(2).args.join(',')).to.equal('0,100') }) }) it('sends the right clip values for elements that need scrolling', () => { const scrollTo = cy.spy(cy.state('window'), 'scrollTo') cy.get('.tall-element').screenshot() .then(() => { expect(scrollTo.getCall(0).args).to.eql([0, 140]) const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].clip).to.eql({ x: 20, y: 0, width: 560, height: 200 }) expect(take.args[1][1].clip).to.eql({ x: 20, y: 60, width: 560, height: 120 }) }) }) it('sends the right clip values for elements that don\'t need scrolling', () => { const scrollTo = cy.spy(cy.state('window'), 'scrollTo') cy.get('.short-element').screenshot() .then(() => { // even though we don't need to scroll, the implementation behaviour is to // try to scroll until the element is at the top of the viewport. expect(scrollTo.getCall(0).args).to.eql([0, 20]) const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].clip).to.eql({ x: 40, y: 0, width: 200, height: 100 }) }) }) it('applies padding to clip values for elements that need scrolling', () => { const padding = 10 const scrollTo = cy.spy(cy.state('window'), 'scrollTo') cy.get('.tall-element').screenshot({ padding }) .then(() => { const viewportHeight = getViewportHeight() expect(scrollTo.getCall(0).args).to.eql([0, 140 - padding]) expect(scrollTo.getCall(1).args).to.eql([0, (140 + viewportHeight) - padding]) const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].clip).to.eql({ x: 20 - padding, y: 0, width: 560 + (padding * 2), height: viewportHeight, }) expect(take.args[1][1].clip).to.eql({ x: 20 - padding, y: 60 - padding, width: 560 + (padding * 2), height: 120 + (padding * 2), }) }) }) it('applies padding to clip values for elements that don\'t need scrolling', () => { const padding = 10 const scrollTo = cy.spy(cy.state('window'), 'scrollTo') cy.get('.short-element').screenshot({ padding }) .then(() => { expect(scrollTo.getCall(0).args).to.eql([0, padding]) const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].clip).to.eql({ x: 30, y: 0, width: 220, height: 120, }) }) }) it('works with cy.within()', () => { cy.get('.short-element').within(() => { cy.screenshot() }).then(() => { const take = Cypress.automation.withArgs('take:screenshot') expect(take.args[0][1].clip).to.eql({ x: 40, y: 0, width: 200, height: 100 }) }) }) // https://github.com/cypress-io/cypress/issues/14253 it('ignores within subject when capturing the runner', () => { cy.get('.short-element').within(() => { cy.screenshot({ capture: 'runner' }) }).then(() => { // the runner was captured expect(Cypress.action.withArgs('cy:before:screenshot').args[0][1].appOnly).to.be.true expect(Cypress.automation.withArgs('take:screenshot').args[0][1].capture).to.equal('viewport') }) }) it('coerces capture option into \'app\'', function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.get('.short-element').screenshot({ capture: 'runner' }) .then(() => { expect(Cypress.action.withArgs('cy:before:screenshot').args[0][1].appOnly).to.be.true expect(Cypress.automation.withArgs('take:screenshot').args[0][1].capture).to.equal('viewport') }) }) it('passes through the existing $l subject', () => { cy .get('.short-element').then(($el) => { cy .get('.short-element') .screenshot() .then(($el2) => { expect($el2.get(0)).to.equal($el.get(0)) }) }) }) it('passes through window', () => { cy .window() .then((win) => { cy.wrap(win) .screenshot() .then((w) => { expect(win === w).to.be.true }) }) }) it('passes through document', () => { cy .document() .then((doc) => { cy.wrap(doc) .screenshot() .then((d) => { expect(doc === d).to.be.true }) }) }) // https://github.com/cypress-io/cypress/issues/6099 it('can screenshot when element height changes on scroll', () => { cy.visit('fixtures/issue-6099.html') cy.get('main').screenshot() }) }) describe('timeout', () => { beforeEach(function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) }) it('sets timeout to Cypress.config(responseTimeout)', { responseTimeout: 2500, }, () => { const timeout = cy.spy(Promise.prototype, 'timeout') cy.screenshot().then(() => { expect(timeout).to.be.calledWith(2500) }) }) it('can override timeout', () => { const timeout = cy.spy(Promise.prototype, 'timeout') cy.screenshot({ timeout: 1000 }).then(() => { expect(timeout).to.be.calledWith(1000) }) }) it('can override timeout and pass name', () => { const timeout = cy.spy(Promise.prototype, 'timeout') cy.screenshot('foo', { timeout: 1000 }).then(() => { expect(timeout).to.be.calledWith(1000) }) }) it('clears the current timeout and restores after success', () => { cy.timeout(100) cy.spy(cy, 'clearTimeout') cy.screenshot().then(() => { expect(cy.clearTimeout).to.be.calledWith('take:screenshot') // restores the timeout afterwards expect(cy.timeout()).to.eq(100) }) }) }) describe('errors', { defaultCommandTimeout: 100, }, () => { beforeEach(function () { this.logs = [] cy.on('log:added', (attrs, log) => { if (attrs.name === 'screenshot') { this.lastLog = log this.logs.push(log) } }) this.assertErrorMessage = function (message, done) { cy.on('fail', (err) => { const lastErr = this.lastLog.get('error') expect(lastErr.message).to.eq(message) done() }) } return null }) it('throws if capture is not a string', function (done) { cy.on('fail', (err) => { const lastErr = this.lastLog.get('error') expect(lastErr.message).to.eq('`cy.screenshot()` `capture` option must be one of the following: `fullPage`, `viewport`, or `runner`. You passed: `true`') expect(lastErr.docsUrl).to.eq('https://on.cypress.io/screenshot') done() }) cy.screenshot({ capture: true }) }) it('throws if capture is not a valid option', function (done) { cy.on('fail', (err) => { const lastErr = this.lastLog.get('error') expect(lastErr.message).to.eq('`cy.screenshot()` `capture` option must be one of the following: `fullPage`, `viewport`, or `runner`. You passed: `foo`') expect(lastErr.docsUrl).to.eq('https://on.cypress.io/screenshot') done() }) cy.screenshot({ capture: 'foo' }) }) it('throws if scale is not a boolean', function (done) { cy.on('fail', (err) => { const lastErr = this.lastLog.get('error') expect(lastErr.message).to.eq('`cy.screenshot()` `scale` option must be a boolean. You passed: `foo`') expect(lastErr.docsUrl).to.eq('https://on.cypress.io/screenshot') done() }) cy.screenshot({ scale: 'foo' }) }) it('throws if disableTimersAndAnimations is not a boolean', function (done) { cy.on('fail', (err) => { const lastErr = this.lastLog.get('error') expect(lastErr.message).to.eq('`cy.screenshot()` `disableTimersAndAnimations` option must be a boolean. You passed: `foo`') expect(lastErr.docsUrl).to.eq('https://on.cypress.io/screenshot') done() }) cy.screenshot({ disableTimersAndAnimations: 'foo' }) }) it('throws if blackout is not an array', function (done) { cy.on('fail', (err) => { const lastErr = this.lastLog.get('error') expect(lastErr.message).to.eq('`cy.screenshot()` `blackout` option must be an array of strings. You passed: `foo`') expect(lastErr.docsUrl).to.eq('https://on.cypress.io/screenshot') done() }) cy.screenshot({ blackout: 'foo' }) }) it('throws if blackout is not an array of strings', function (done) { cy.on('fail', (err) => { const lastErr = this.lastLog.get('error') expect(lastErr.message).to.eq('`cy.screenshot()` `blackout` option must be an array of strings. You passed: `true`') expect(lastErr.docsUrl).to.eq('https://on.cypress.io/screenshot') done() }) cy.screenshot({ blackout: [true] }) }) it('throws if there is a 0px tall element height', function (done) { this.assertErrorMessage('`cy.screenshot()` only works with a screenshot area with a height greater than zero.', done) cy.visit('/fixtures/screenshots.html') cy.get('.empty-element').screenshot() }) it('throws if padding is not a number', function (done) { this.assertErrorMessage('`cy.screenshot()` `padding` option must be either a number or an array of numbers with a maximum length of 4. You passed: `50px`', done) cy.screenshot({ padding: '50px' }) }) it('throws if padding is not an array of numbers', function (done) { this.assertErrorMessage('`cy.screenshot()` `padding` option must be either a number or an array of numbers with a maximum length of 4. You passed: `bad, bad, bad, bad`', done) cy.screenshot({ padding: ['bad', 'bad', 'bad', 'bad'] }) }) it('throws if padding is not an array with a length between 1 and 4', function (done) { this.assertErrorMessage('`cy.screenshot()` `padding` option must be either a number or an array of numbers with a maximum length of 4. You passed: `20, 10, 20, 10, 50`', done) cy.screenshot({ padding: [20, 10, 20, 10, 50] }) }) it('throws if padding is a large negative number that causes a 0px tall element height', function (done) { this.assertErrorMessage('`cy.screenshot()` only works with a screenshot area with a height greater than zero.', done) cy.visit('/fixtures/screenshots.html') cy.get('.tall-element').screenshot({ padding: -161 }) }) it('throws if clip is not an object', function (done) { this.assertErrorMessage('`cy.screenshot()` `clip` option must be an object with the keys `{ width, height, x, y }` and number values. You passed: `true`', done) cy.screenshot({ clip: true }) }) it('throws if clip is lacking proper keys', function (done) { this.assertErrorMessage('`cy.screenshot()` `clip` option must be an object with the keys `{ width, height, x, y }` and number values. You passed: `{x: 5}`', done) cy.screenshot({ clip: { x: 5 } }) }) it('throws if clip has extraneous keys', function (done) { this.assertErrorMessage('`cy.screenshot()` `clip` option must be an object with the keys `{ width, height, x, y }` and number values. You passed: `Object{5}`', done) cy.screenshot({ clip: { width: 100, height: 100, x: 5, y: 5, foo: 10 } }) }) it('throws if clip has non-number values', function (done) { this.assertErrorMessage('`cy.screenshot()` `clip` option must be an object with the keys `{ width, height, x, y }` and number values. You passed: `Object{4}`', done) cy.screenshot({ clip: { width: 100, height: 100, x: 5, y: '5' } }) }) it('throws if element capture with multiple elements', function (done) { cy.on('fail', (err) => { const lastErr = this.lastLog.get('error') expect(lastErr.message).to.eq('`cy.screenshot()` only works for a single element. You attempted to screenshot 4 elements.') expect(lastErr.docsUrl).to.eq('https://on.cypress.io/screenshot') done() }) cy.visit('/fixtures/screenshots.html') cy.get('.multiple').screenshot() }) it('logs once on error', function (done) { const error = new Error('some error') error.name = 'foo' error.stack = 'stack' Cypress.automation.withArgs('take:screenshot').rejects(error) cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error').message).to.eq(error.message) expect(lastLog.get('error').name).to.eq(error.name) expect(lastLog.get('error').stack).to.eq(error.stack) expect(lastLog.get('error')).to.eq(err) done() }) cy.screenshot() }) it('throws after timing out', function (done) { Cypress.automation.withArgs('take:screenshot').resolves(Promise.delay(1000)) cy.on('fail', (err) => { const { lastLog } = this expect(this.logs.length).to.eq(1) expect(lastLog.get('error')).to.eq(err) expect(lastLog.get('state')).to.eq('failed') expect(lastLog.get('name')).to.eq('screenshot') expect(lastLog.get('message')).to.eq('foo') expect(err.message).to.eq('`cy.screenshot()` timed out waiting `50ms` to complete.') expect(err.docsUrl).to.eq('https://on.cypress.io/screenshot') done() }) cy.screenshot('foo', { timeout: 50 }) }) }) describe('.log', () => { beforeEach(function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) cy.on('log:added', (attrs, log) => { if (attrs.name === 'screenshot') { this.lastLog = log } }) return null }) it('can turn off logging', () => { cy.screenshot('bar', { log: false }).then(function () { expect(this.lastLog).to.be.undefined }) }) it('ends immediately', () => { cy.screenshot().then(function () { const { lastLog } = this expect(lastLog.get('ended')).to.be.true expect(lastLog.get('state')).to.eq('passed') }) }) it('snapshots immediately', () => { cy.screenshot().then(function () { const { lastLog } = this expect(lastLog.get('snapshots').length).to.eq(1) expect(lastLog.get('snapshots')[0]).to.be.an('object') }) }) it('#consoleProps', function () { Cypress.automation.withArgs('take:screenshot').resolves(this.serverResult) let expected = _.extend({}, this.serverResult, this.screenshotConfig, { Command: 'screenshot', scaled: true, duration: '100ms', }) expected = _.omit(expected, 'blackout', 'dimensions', 'screenshotOnRunFailure', 'scale', 'size') cy.screenshot().then(() => { const consoleProps = this.lastLog.invoke('consoleProps') const actual = _.omit(consoleProps, 'blackout', 'dimensions', 'size') const { width, height } = this.serverResult.dimensions expect(actual).to.eql(expected) expect(consoleProps.size).to.eq('12 B') expect(consoleProps.blackout).to.eql(this.screenshotConfig.blackout) expect(consoleProps.dimensions).to.equal(`${width}px x ${height}px`) }) }) }) }) })