Code Monkey home page Code Monkey logo

jsdom-screenshot's Introduction

jsdom-screenshot

Generate screenshots of JSDOM.

โš ๏ธ This package is useful for visual regression testing, but highly experimental.

If you just want visual regression testing that works, I'd recommend using a CI service for it. Otherwise you'll run differences due to different operating systems, font-rendering, animations and even GPUs.

This package will only give you the image, you'll have to diff it with something else (like jest-image-snapshot). If you are using Jest, you might be interested in jest-transform-css, which allows you to load styles into your Jest test setup.

This package can be paired with jest-transform-css and jest-image-snapshot to enable Visual Regression Testing in Jest. See jest-transform-css for more information.

Table of Contents

Install

npm install jsdom-screenshot --save-dev

Usage

You must be in a jsdom environment.

import { generateImage } from "jsdom-screenshot";

// add some content to jsdom (this could also be React or any other library!)
const div = document.createElement("div");
div.innerHTML = "Hello World";
document.body.appendChild(div);

// take screenshot
generateImage();

Usage in Jest, React & react-testing-library

It is recommended to use this package with jest-image-snapshot and react-testing-library. Use it as together like this:

import React from "react";
import { generateImage, setDefaultOptions } from "jsdom-screenshot";
import { render } from "react-testing-library";
import { SomeComponent } from "<your-code>";

it("should have no visual regressions", async () => {
  render(<SomeComponent />);
  expect(await generateImage()).toMatchImageSnapshot();
});

You probably want to use a setupTestFrameworkScriptFile like this:

// react-testing-library setup
import "jest-dom/extend-expect";
import "react-testing-library/cleanup-after-each";
// set up visual regression testing
import { toMatchImageSnapshot } from "jest-image-snapshot";
import { setDefaultOptions } from "jsdom-screenshot";

// TravisCI and Linux OS require --no-sandbox to be able to run the tests
// https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#running-puppeteer-on-travis-ci
setDefaultOptions({
  launch: { args: process.env.CI === "true" ? ["--no-sandbox"] : [] }
});

// give tests more time as taking screenshots takes a while
jest.setTimeout(10000);

expect.extend({ toMatchImageSnapshot });

API

generateImage(options)

generateImage is the main function you're going to use to take a screenshot of the JSDOM. It supports these options.

Tip: You can use react-testing-library's fireEvent to get the component into any state before taking the screenshot.

Options

options = {
  // Options used to launch Puppeteer (puppeteer.launch(options))
  launch: {},
  // Options used to take a screenshot (puppeteer.screenshot(options))
  screenshot: {},
  // An array of folders containing static files to be served
  serve: ["public", "assets"],
  // Prints the jsdom markup to the console before taking the screenshot
  debug: true,
  // Wait for resources to be loaded before taking the screenshot
  waitUntilNetworkIdle: false,
  // Shortcut to set launch.defaultViewport
  viewport: {},
  // Enables request interception
  intercept: () => {}
};
options.launch

launch options are passed to puppeteer.launch([options]), see docs.

options.screenshot

screenshot options are passed to page.screenshot([options]), see docs.

options.serve

serve is an array of strings. You can provide a list of folders to serve statically. This is useful when your component uses assets through relative links like <img src="/party-parrot.gif" />.

In this case, you could provide serve: ["images"] when the images folder at the root of your project (where you launch the tests from) contains party-parrot.gif.

options.debug

Prints the jsdom markup to the console before taking the screenshot.

See the Debugging JSDOM section below for more information.

options.viewport

This is a shortcut to set options.launch.defaultViewport. options.launch.defaultViewport will take precedence in case both are passed.

options.targetSelector

A CSS selector can be provided to take a screenshot only of an element found by given selector. This will set puppeteers options.screenshot.clip to match the given element's offset properties (offsetLeft, offsetTop, offsetWidth and offsetHeight).

Example:

import React from "react";
import { generateImage, setDefaultOptions } from "jsdom-screenshot";
import { render } from "react-testing-library";
import { SomeComponent } from "<your-code>";

it("should have no visual regressions", async () => {
  // display: "table" prevents div from using full width,
  // so the screenshot would not cover the full width here
  render(
    <div data-testid="root" style={{ display: "table" }}>
      <SomeComponent />
    </div>
  );

  const image = await generateImage({
    targetSelector: "[data-testid=root]"
  });
  expect(image).toMatchImageSnapshot();
});
options.waitUntilNetworkIdle

When set to true, jsdom-screenshot will wait until the network becomes idle (all resources are loaded) before taking a screenshot. You can use this to ensure that all resources are loaded before the screenshot is taken.

It is disabled by default as it adds roughly one second to each screenshot. Use it wisely to avoid slowing down tests unnecessarily. You can mock requests using options.intercept.

options.intercept

When provided, puppeteer's request interception will be enabled. The provided function will be called with the intercepted request.

Activating request interception enables request.abort, request.continue and request.respond methods. This provides the capability to modify network requests that are made by a page.

This can be used to speed up tests by stubbing requests.

generateImage({
  intercept: request => {
    if (request.url().endsWith(".png") || request.url().endsWith(".jpg")) {
      // Blocks some images.
      request.abort();
    } else if (request.url().endsWith("/some-big-library.css")) {
      // Fake a response
      request.respond({
        status: 200,
        contentType: "text/css",
        body: "html, body { background: red }"
      });
    } else {
      // Call request.continue() for requests which should not be intercepted
      request.continue();
    }
  }
});

See page.setintercept of puppeteer.

Changing viewport

Puppeteer will use an 800x600 viewport by default. You can change the viewport by passing launch.defaultViewport:

generateImage({
  launch: {
    defaultViewport: { width: 1024, height: 768 }
  }
});

As this is a lot of typing, there is a shortcut for it:

generateImage({ viewport: { width: 1024, height: 768 } });

launch.defaultViewport / viewport also supports deviceScaleFactor, isMobile, hasTouch and isLandscape.

See launch.defaultViewport.

setDefaultOptions(options)

Having to reapply the same options for every test is pretty inconvenient. The setDefaultOptions function can be used to set default options for every generateImage call. Any options passed to the generateImage call will get merged with the specified defaultOptions.

This function can be used to provide global defaults. Note that these defaults are global for all generateImage calls. You should typically only call setDefaultOptions once in your test-setup file.

For example with Jest, you could do the following in your setupTestFrameworkScriptFile file:

import { setDefaultOptions } from "jsdom-screenshot";

/*
  TravisCI requires --no-sandbox to be able to run the tests.
  We the launch options globally here so that they don't need to be
  repeated for every `generateImage` call.
*/
setDefaultOptions({
  launch: { args: process.env.CI === "true" ? ["--no-sandbox"] : [] }
});

restoreDefaultOptions()

The restoreDefaultOptions function restores the default options provided by jsdom-screenshot. See setDefaultOptions for a usage example.

debug(element)

Logs the JSDOM contents to the console. See Debugging for more information.

How it works

High level

jsdom is an emulator of a subset of browser features. jsdom does not have the capability to render visual content, and will act like a headless browser by default. jsdom does not do any layout or rendering ref. We use jsdom to obtain the state of the HTML which we want to take a screenshot of. Consumers can use jsdom to easily get components into the state they want to take a screenshot of. jsdom-screenshot then uses the markup ("the HTML") at that moment (of that state). jsdom-screenshot launches a local webserver and serves the obtained markup as index.html. It further serves assets provided through serve so that local assets are loaded. Then jsdom-screenshot uses puppeteer to take a screenshot take screenshots of that page using headless Google Chrome.

Technically

The generateImage function reads the whole markup of jsdom using document.documentElement.outerHTML.

It then starts a local webserver on a random open port to serve the obtained markup as as index.html.

Once the server is read, it launches a puppeteer instance and opens that index.html page. It waits until all resources are loaded (the network becomes idle) before taking a screenshot.

It then returns that screenshot.

Performance

Launching puppeteer to take a screenshot takes around 750ms. The rest depends on your application. You should try to mock/stub network requests to keep tests fast (see options.intercept).

You should not go overboard with Visual Regression Tests, but a few errors caught with good Visual Regression Tests will make up for the lost time in tests. Find a good balance that works for you.

Debugging

Debugging JSDOM

You can print the markup of jsdom which gets passed to puppeteer to take the screenshot by passing debug: true:

generateImage({ debug: true });

You can also import the debug function and call it manually at any point. It will log the markup of jsdom to the console:

import { generateImage, debug } from "jsdom-screenshot";

it("should have no visual regressions", async () => {
  const div = document.createElement("div");
  div.innerText = "Hello World";
  document.body.appendChild(div);

  debug(); // <---- prints the jsdom markup to the console

  expect(await generateImage()).toMatchImageSnapshot();
});

Debugging puppeteer

You can set the following launch in case you need to debug what the page looks like before taking a screenshot:

generateImage({
  launch: {
    // Whether to auto-open a DevTools panel for each tab.
    // If this option is true, the headless option will be set false.
    devtools: true,
    // Whether to run browser in headless mode.
    // Defaults to true unless the devtools option is true.
    headless: false,
    // Slows down Puppeteer operations by the specified amount of milliseconds.
    // Useful so that you can see what is going on.
    slowMo: 500
  }
});

Attribution

This package was built by massively rewriting component-image. Huge thanks to @corygibbons for laying the foundation of this package.

jsdom-screenshot's People

Contributors

cancerberosgx avatar dependabot[bot] avatar dferber90 avatar luckylooke avatar mfolnovic avatar richard-lopes-ifood avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

jsdom-screenshot's Issues

Sometimes await generateImage never returns and tests timeout

With some certain page content, the server under the hood to take a screenshot from never serves the webpage and the generateImage never returns.
I try to access the server's url like http://localhost:60957/ and it hangs too until I hit Ctrl-C on my unit tests command, then suddenly the pages loads once correctly.
I will try to gather more information and submit some fixes if possible. I'm interested in some pointer if people have clues what could be causing this.

takeScreenshot hangs on missing assets

I was having an issue where my test suddenly started timing out -- I finally debugged it by launching chromium with generateImage({ launch: { ... } }). I noticed in the network tab that the request for a font file was pending, and would never resolve (either with the resource or as a 404).

This was solved by adding my asset directory to the options (serve: ['public']). But, it was quite difficult to figure out that this was the issue. Ideally, puppeteer would just treat it as a normal 404 and not load the asset -- then I would see in my screenshots that my fonts aren't loading.

I see that opts.waitUntilNetworkIdle is false by default.. So I'm not sure why puppeteer was stuck on this asset. I've tried goofing around in createServer to handle this but I didn't have much luck.

Document the default screenshot directory

My test calls generateImage(); without issue, but I have no idea where the screenshot went. This is probably documented within the screenshot options, but the link to that in the readme 404s. Regardless, it might be worth stating directly in the readme of this project as I'm sure it's something any first-time user would want to know.

Unexpected token ... in debug.js:17

When I install this I am getting error. My node version is v8.2.1

/Users/om/Documents/projects/odoo/ekogrid/npms/node_modules/jsdom-screenshot/debug.js:17
    ...options
    ^^^

SyntaxError: Unexpected token ...

Cannot find module 'ws'

Getting the following error after following the guide: https://gist.github.com/dferber90/6fe76cde582b8746191478fac34c8b7d

Test suite failed to run

    Cannot find module 'ws' from '../../node_modules/jsdom-screenshot/node_modules/puppeteer/lib/WebSocketTransport.js'

    Require stack:
      /Users/omaranshasi/Code/cygnus-stack/node_modules/jsdom-screenshot/node_modules/puppeteer/lib/WebSocketTransport.js
      /Users/omaranshasi/Code/cygnus-stack/node_modules/jsdom-screenshot/node_modules/puppeteer/lib/Launcher.js
      /Users/omaranshasi/Code/cygnus-stack/node_modules/jsdom-screenshot/node_modules/puppeteer/lib/Puppeteer.js
      /Users/omaranshasi/Code/cygnus-stack/node_modules/jsdom-screenshot/node_modules/puppeteer/lib/api.js
      /Users/omaranshasi/Code/cygnus-stack/node_modules/jsdom-screenshot/node_modules/puppeteer/index.js
      /Users/omaranshasi/Code/cygnus-stack/node_modules/jsdom-screenshot/generateImage.js
      /Users/omaranshasi/Code/cygnus-stack/node_modules/jsdom-screenshot/index.js
      src/components/__tests__/testUtils.jsx
      src/components/rooms/creation/bulk/__tests__/BulkImportPage.spec.jsx

      at Resolver.resolveModule (../../node_modules/jest-resolve/build/index.js:306:11)
      at Object.<anonymous> (../../node_modules/jsdom-screenshot/node_modules/puppeteer/lib/WebSocketTransport.js:16:19)

Any ideas how to solve it?
Thanks

Feedback

Hey @cancerberoSgx,

would you mind giving some feedback of what you're using this package for?

I've used it to experiment with Visual Regression Testing, but ultimately ended up using percy.io instead. They take care of differences in screenshots due to e.g. different font rendering on different machines. I kinda want to "warn" you that it can be a bit of a time-sink and inform that it's usually better to use a service for Visual Regression Testing instead.

Cheers

jsdom-screenshot not generating a screenshot for web components

Hi, i have a React component that uses web components (imported from a third-party library).
I noticed that jsdom-screenshot does not render the UI rendered by those web components, and only renders the UI of other HTML elements.
Is there a way to get the screenshots to render the web components as well?

How can I load external assets before my tests run?

Hello,
I was trying to add some snapshot tests in my design system repo, and I stumbled upon a problem:
I have to load some internal assets (fonts and icons) that are hosted in a second "assets" repository, accessible via URL, but I can't seem to find a way to load them before my tests run.

This is the snapshot of a button with an icon on the right side.

  • the font is wrong
  • the icon is missing

image

Any suggestions?

Thanks a lot

Images depend on LCD font smoothing

Problem

The screenshot puppeteer takes is dependant of the "Use LCD font smoothing when available" setting of Mac OSX.

Consequences:

  • when the setting is changed on the operating system, the tests fail
  • when the setting is enabled and the tests were generated with LCD font smoothing, but are now run on a non-LCD monitor (not sure what is different exactly), the tests suddenly fail

See here for an example of a failing visual regression test caused by this:

image

Solution

There is a chrome-flag called lcd-text-aa at chrome://flags/#lcd-text-aa which we can always disable. Then the screenshots will never use LCD text anti-aliasing. The tests now no longer depend on the monitor or Mac OSX "Use LCD font smoothing when available" setting.

Technical

puppeteer.launch(options) accepts options.args and we can pass --disable-lcd-text to disable the lcd text antialiasing.

Change puppeteer to peer dependency?

This package specifies puppeteer ^1.12.2 as a dependency, which is a version that is pinned to Chrome 73. As this is a dependency of the package, there's no way to specify a different Chrome version.

Changing puppeteer to be a peer dependency would allow users of the package to specify their own version of puppeteer and be in control of the version of Chrome that gets used.

Can we take component level screenshot with CSS

First of all, great tool. Thanks for creating it. I tried using it but could only get screenshot without any styling. Is there a way a component can be rendered and screenshot with CSS? All examples that I found was rendering the first and then screenshot will capture a selector. However in a complicated app which has multiple components, tests are written inside the components to render only that component. Unfortunately, when you take a screenshot, the screenshots do not inherit the CSS which the parent component may be importing. Any ideas around here?

Is it possible to get the puppeteer instance?

I'd like to be able to call await page.evaluate(() => { debugger }) so I can get a better idea of problems within the chromium devtools. But, I see that the puppeteer browser and page are created within the closure of generateImage(), and are not returned. Do you know of any way to tap into puppeteer's instances, maybe through some jsdom export?

If not, I have an idea of how to make this an option within this library without any breaking changes ~ let me know what you think and I'd be happy to make a PR.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.