test-runner icon indicating copy to clipboard operation
test-runner copied to clipboard

[Bug] Tests always run in the default viewport

Open schneefux opened this issue 2 years ago • 5 comments

Describe the bug

The test runner runs tests full screen inside the default playwright Chrome viewport. It does not resize the viewport according to the viewport parameter of the story.

Steps to reproduce the behavior

I have a component where a dropdown is hidden on large screens using a media query. I configured the story to use a mobile2 viewport by default and wrote a play function to test the visibility of the element:

export const Mobile = Template.bind({})
Mobile.parameters = {
  viewport: {
    defaultViewport: 'mobile2',
  },
}
Mobile.play = async ({ canvasElement }) => {
  const canvas = within(canvasElement)
  const toggleButton = await canvas.findByTestId('dropdownToggle')
  await waitFor(() => expect(toggleButton).toBeVisible())
}

When I run storybook locally and use the interactions addon, the test runs fine, but when I run the test runner, it fails.

I debugged the test runner using this custom test-runner-jest.config.js, --maxWorkers 1 and by adding sleep timeouts after every line. I can see that the playwright Chrome window has the wrong size and that it loads the story iframe in fullscreen.

const { getJestConfig } = require('@storybook/test-runner');

module.exports = {
  ...getJestConfig(),
  testEnvironmentOptions: {
    'jest-playwright': {
      launchOptions: {
        headless: false,
      }
    },
  },
}

Environment

  • Storybook 6.4.20
  • @storybook/test-runner 0.0.4
  • @storybook/testing-library 0.0.9

Additional context

As a workaround, I wrote a prerender hook in .storybook/test-runner.js that resizes the playwright page if the test has Mobile in its name. A solution that does not rely on magic strings would be better though.

module.exports = {
  preRender(page, story) {
    if (story.name.includes('Mobile')) {
      page.setViewportSize({ width: 896, height: 414 })
    }
  },
}

schneefux avatar Apr 04 '22 17:04 schneefux

Thanks for using this library and opening this issue @schneefux! There's currently a limitation that won't allow you to get info from the viewports addon, hopefully we can add support for that soon.

@shilman WDYT of adding parameters to the context? It will make stories-json mode feel more limited but it is what it is I guess

yannbf avatar Apr 04 '22 19:04 yannbf

It will make stories-json mode feel more limited.

Is that because parameters are not available in stories-json mode? This could be another good use for tags in storybook, perhaps.

IanVS avatar Apr 06 '22 16:04 IanVS

I just want to share that this is possible now with the getStoryContext/page.evaluate.

I'm using it to get storyContext.parameters.viewport.defaultViewport and then calling page.setViewportSize(devices[storyContext.parameters.viewport.defaultViewport].viewport) where devices is a shared list between the custom viewports I've set up in storybook (and I'm leveraging playwright's devices because why not :) const { devices } = require("playwright");)

VickyKoblinski avatar Jul 11 '22 06:07 VickyKoblinski

Thanks @VickyKoblinski! Your project must be super cool, given all the stuff you've been doing with the test runner!

yannbf avatar Aug 14 '22 09:08 yannbf

TODO: add a recipe for that ☝️

yannbf avatar Aug 14 '22 09:08 yannbf

We got inspired by @VickyKoblinski's answer with little changes, our stories are configured with viewports imported or customized with @storybook/addon-viewport. So here's our little magic 🍡 : `

const { getStoryContext } = require('@storybook/test-runner'); 
module.exports = {
    async preRender(page, story) {
      const context = await getStoryContext(page, story);
      const viewPortParams = context.parameters?.viewport;
      const defaultViewport = viewPortParams?.defaultViewport;
      const viewport =
        defaultViewport && viewPortParams.viewports[defaultViewport].styles;

      const parsedViewportSizes =
        viewport &&
        Object.entries(viewport).reduce(
          (acc, [screen, size]) => ({
            ...acc,
            [screen]: parseInt(size),
          }),
          {},
        );

      if (parsedViewportSizes) page.setViewportSize(parsedViewportSizes);
  },
};`

ibrahimgabsi avatar Jan 27 '23 08:01 ibrahimgabsi

@ibrahimgabsi I want to note that when the viewPort is resized, it will not return to its original state for the following stories. So we have to call something like setViewportSize(globalDefaultSize) if no custom window is specified.

dimkatet avatar Apr 13 '23 15:04 dimkatet

I slightly improved solution of @ibrahimgabsi to use built-in viewport sizes or reset to default on pages without viewport parameter.

Create .storybook/test-runner.js:

const { getStoryContext } = require('@storybook/test-runner');
const { MINIMAL_VIEWPORTS } = require('@storybook/addon-viewport');

const DEFAULT_VP_SIZE = { width: 1280, height: 720 };

module.exports = {
	async preRender(page, story) {
		const context = await getStoryContext(page, story);
		const vpName = context.parameters?.viewport?.defaultViewport;
		const vpParams = MINIMAL_VIEWPORTS[vpName];

		if (vpParams) {
			const vpSize = Object.entries(vpParams.styles).reduce(
				(acc, [screen, size]) => ({
					...acc,
					[screen]: parseInt(size),
				}),
				{}
			);

			page.setViewportSize(vpSize);
		} else {
			page.setViewportSize(DEFAULT_VP_SIZE);
		}
	},
};

vlad-iakovlev avatar Jun 05 '23 09:06 vlad-iakovlev

Hey peeps! I will close this issue as the solution is now available and its recipe has been documented in the README here. Thank you!

yannbf avatar Oct 25 '23 10:10 yannbf