Is your feature request related to a problem? Please describe.
Storyshots with Puppeteer has a config option to supply Puppeteer's screenshot function with options (getScreenshotOptions). See available options.
Passing fullPage: false
here will take a screenshot of the page's viewport (instead of the full page with scrollbars). However, if I have a small component, I don't want to take a screenshot of a lot of empty space, since it's unnecesary. I only care about the bounds of my component.
Describe the solution you'd like
There's a couple potential solutions for this. The first is providing a new option to imageSnapshots
like clipScreenshot
, which will internally select the component's root node, e.g. with the #root > *
selector, and then do element.screenshot()
.
Describe alternatives you've considered
The alternative is to allow the end user to use the clip
object option that Puppeteer provides. This would require the user to obtain the element, work out the x
, y
, width
and height
properties.
const getScreenshotOptions = async page => {
await page.waitForSelector('#root > *');
const { x, y, width, height } = await page.evaluate(() => {
const element = document.querySelector('#root > *');
const rect = element.getBoundingClientRect();
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
});
return {
clip: { x, y, width, height }
};
};
In order to support this, one small change is required - to make getScreenshotOptions
asynchronous.
Are you able to assist bring the feature to reality?
Yes
Additional context
Normal page screenshot:
Clipped screenshot:
Currently I use the following options to get screenshots of just the components:
initStoryshots({
test: imageSnapshot({
beforeScreenshot: async (page, { context }) => {
context.clip = await page.evaluate(() => {
// display 'table' is optional, but it prevents div's from using the full viewport width
document.body.style.display = 'table';
const { height, width, left: x, top: y } = document.body.getBoundingClientRect();
return { x, y, height, width };
});
},
getScreenshotOptions: ({ context: { clip } }) => {
// omitBackground: true is optional, but I like to distinguish between white and transparent
return { clip, omitBackground: true };
},
// also optional, but I like having my stories structured using folders
getMatchOptions({ context: { kind, story } }) {
return {
customSnapshotsDir: path.join(
__dirname,
'__image_snapshots__',
...kind.split('/').map(sanitizeFileName)
),
customSnapshotIdentifier: sanitizeFileName(story)
};
}
})
});
function sanitizeFileName(name: string) {
return name.replace(/[<>:"\/\\\|\?\*]/g, '-');
}
Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!
Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!
Many thanks for sharing the reply! :smile: I've managed to make it working albeit had to wrap dom code into async func. Otherwise puppeteer didn't want to work.
initStoryshots({
storyKindRegex: new RegExp(`/${storyRegex}$`),
suite: 'Imageshots',
test: imageSnapshot({
storybookUrl: 'http://localhost:9002',
getMatchOptions,
beforeScreenshot: async (page, { context }) => {
// eslint-disable-next-line no-param-reassign
context.clip = await page.evaluate(`(async() => {
const element = document.querySelector('#root > div > *');
const { height, width, left: x, top: y } = element.getBoundingClientRect();
return { x, y, height, width };
})()`);
},
getScreenshotOptions: ({ context: { clip } }) => ({
clip,
}),
}),
});
If someone knows why plz msg.
Now in the docs is specified that you can return the element for screenshot from beforeScreenshot
To create a screenshot of just a single element (with its children), rather than the page or current viewport, an ElementHandle can be returned from beforeScreenshot:
import initStoryshots from '@storybook/addon-storyshots';
import { imageSnapshot } from '@storybook/addon-storyshots-puppeteer';
const beforeScreenshot = (page) => page.$('#root > *');
initStoryshots({
suite: 'Image storyshots',
test: imageSnapshot({ storybookUrl: 'http://localhost:6006', beforeScreenshot }),
});
https://github.com/storybookjs/storybook/blob/next/addons/storyshots/storyshots-puppeteer/README.md#specifying-options-to-screenshot-puppeteer-api
PS: at the moment it seems that TypeScript types maybe are wrong, because beforeScreenshot
wants a return type of Promise<void | ElementHandle>
but page.$
returns Promise<ElementHandle | null>;
I had to use
beforeScreenshot: page => page.$('#root > *').then(root => root ?? undefined),
Most helpful comment
Currently I use the following options to get screenshots of just the components: