cypress-io / cypress

Fast, easy and reliable testing for anything that runs in a browser.
https://cypress.io
MIT License
47.02k stars 3.18k forks source link

Proposal - cy.screenshot enhancements #1424

Closed brian-mann closed 6 years ago

brian-mann commented 6 years ago

Make cy.screenshot() more useful to go down the path towards screenshot diffing.

Let's create a new set of API's to make it easier to take a picture of the AUT (application under test) without involving the Cypress UI.

To make this happen, we'll also need to give the user the option to remove the artificial "scaling" Cypress applies, and we'll need to black out all of the excess areas.

Bonus points for also going down the route of slicing out the "blacked out" areas of the picture so the resulting screenshot has no excess - and is insulated from resolution differences.

This also leads us towards being able to take pictures of just dom elements instead of the whole window.

Cypress.Screenshot.defaults({
  capture: 'commands | app | both', // default 'commands'
  waitForCommandSynchronization: true, // when taking a picture of the commands, wait until they are synchronized
  scaleAppCaptures: false, // when taking a picture of the app, remove the scaling
  disableTimersAndAnimations: true, // prevent app animations and timers from firing
  screenshotOnRunFailure: true, // config.screenshotOnRunFailure
  blackout: ['selectors'], // whatever matches, we'll blackout their box model
  onScreenshot: ($dom) => {

  },
})

To disable animations - we need to keep references for setTimeout, setInterval, requestAnimationFrame during the initial phase of event binding to the window - and then prevent callbacks from firing until the screenshot has been taken.

onScreenshot would yield you the $elements that are being screenshotted - enabling the user to synchronously perform changes on it (like changing dynamic content).

blackout allows the user to easily pick selectors which we'd use to blackout their box model. This would utilize the logic for layering hitboxes (except layer only a blacked out piece)

brian-mann commented 6 years ago

To disable CSS animations insert this <style> into the document:

*, *:before, *:after {
  transition-property: none !important;
  transform: none !important;
  animation: none !important;
}
brian-mann commented 6 years ago

After screenshot perform all the cleanup - remove the CSS, blackouts, and revert timers.

chrisbreiding commented 6 years ago

After some discussion, changing capture to be an array:

Cypress.Screenshot.defaults({
  capture: ['app', 'everything']
})

Still need a decent name for 'everything'. Ideas:

Also include 'app-scaled' as an option? Or, another idea is to accept an object instead of a string for each item (maybe called it screenshots instead of capture?):

Cypress.Screenshot.defaults({
  screenshots: [
    {
      capture: 'app',
      scale: false
    },
    {
      capture: 'everything',
      scale: true
    }
  ]
})

Perhaps the user could still use the string 'app' or 'everything' and it defaults to scale: true? Then they only need the object if they're turning off scaling.

@brian-mann, @jennifer-shehane, @bahmutov, thoughts?

chrisbreiding commented 6 years ago

Latest API:

Cypress.Screenshot.defaults({
  capture: 'app', // or 'runner'
  waitForCommandSynchronization: true,
  scaleAppCaptures: false,
  disableTimersAndAnimations: true,
  screenshotOnRunFailure: true,
  blackout: ['selectors'],
  beforeScreenshot: (document) => {},
  afterScreenshot: (document) => {},
})

By default, taking 'app' captures with cy.screenshot(). Only take 'runner' captures on failures. Don't black out anything on 'runner' captures.

chrisbreiding commented 6 years ago

Some additions:

Allow capturing full page

Add 'fullpage' option for capture.

cy.screenshot({
  capture: 'fullpage'
})

Implement it by scrolling to the top, then taking a screenshot, scrolling, taking a screenshot, and so on, then stitching them together.

When we implement native events, we'll be able to use Chrome's debugger protocol to accomplish this in a better, more succinct way. Its implementation utilizes device emulation overrides. See puppeteer's implementation.

Add element capture support

Make cy.screenshot() a dual command, so you can do:

cy.get('.foo').screenshot()

Implement as follows:

  1. Scroll element into view
  2. Take screenshot, crop out element
  3. Scroll if needed and stitch together

Number 3 will be unnecessary for Chrome once implemented with debugger protocol. See puppeteer's implementation.

chrisbreiding commented 6 years ago

Some more changes to this API before it's released:

brian-mann commented 6 years ago

Released in 3.0.0.

ModiYesha commented 5 years ago

Can Screenshot give us DOM which can be stored in JSON format the way snapshot https://github.com/cypress-io/snapshot gives?

jennifer-shehane commented 5 years ago

@ModiYesha No.