0
0
mirror of https://github.com/PostHog/posthog.git synced 2024-11-21 13:39:22 +01:00

test(frontend): Cover practically all the Storybook tests with snapshots (#14001)

This commit is contained in:
Michael Matloka 2023-02-03 13:06:21 +01:00 committed by GitHub
parent ca2d618ad3
commit 406b6d9357
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
131 changed files with 169 additions and 126 deletions

View File

@ -3,7 +3,7 @@ import { DecoratorFn } from '@storybook/react'
/** Workaround for https://github.com/storybookjs/test-runner/issues/74 */
// TODO: Smoke-test all the stories by removing this decorator, once all the stories pass
export const withSnapshotsDisabled: DecoratorFn = (Story, { parameters }) => {
if (parameters?.chromatic?.disableSnapshot && navigator.userAgent.includes('StorybookTestRunner')) {
if (parameters?.testOptions?.skip && navigator.userAgent.includes('StorybookTestRunner')) {
return <>Disabled for Test Runner</>
}
return <Story />

View File

@ -1,7 +1,7 @@
const { createEntry } = require('../webpack.config')
const babelConfig = require('../babel.config')
import type { StorybookConfig } from '@storybook/react/types'
import { createEntry } from '../webpack.config'
module.exports = {
const config: StorybookConfig = {
stories: ['../frontend/src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
{
@ -19,28 +19,20 @@ module.exports = {
'storybook-addon-pseudo-states',
],
staticDirs: ['public'],
babel: async () => {
// compile babel to "defaults" target (ES5)
const envPreset = babelConfig.presets.find(
(preset) => Array.isArray(preset) && preset[0] === '@babel/preset-env'
)
envPreset[1].targets = 'defaults'
return babelConfig
},
webpackFinal: (config) => {
const mainConfig = createEntry('main')
return {
...config,
resolve: {
...config.resolve,
extensions: [...config.resolve.extensions, ...mainConfig.resolve.extensions],
alias: { ...config.resolve.alias, ...mainConfig.resolve.alias },
extensions: [...config.resolve!.extensions!, ...mainConfig.resolve.extensions],
alias: { ...config.resolve!.alias, ...mainConfig.resolve.alias },
},
module: {
...config.module,
rules: [
...mainConfig.module.rules,
...config.module.rules.filter((rule) => rule.test.toString().includes('.mdx')),
...config.module!.rules.filter((rule) => rule.test!.toString().includes('.mdx')),
{
test: /\.stories\.tsx?$/,
use: [
@ -59,3 +51,5 @@ module.exports = {
postcss: false,
},
}
export default config

View File

@ -1,6 +1,6 @@
import '~/styles'
import './storybook.scss'
import type { Meta } from '@storybook/react'
import type { Meta, Parameters } from '@storybook/react'
import { worker } from '~/mocks/browser'
import { loadPostHogJS } from '~/loadPostHogJS'
import { getStorybookAppContext } from './app-context'
@ -29,7 +29,7 @@ const setupPosthogJs = () => {
setupPosthogJs()
/** Storybook global parameters. See https://storybook.js.org/docs/react/writing-stories/parameters#global-parameters */
export const parameters = {
export const parameters: Parameters = {
actions: { argTypesRegex: '^on[A-Z].*', disabled: true },
controls: {
matchers: {
@ -51,7 +51,6 @@ export const parameters = {
'Filters',
'Layout',
],
includeName: true,
},
},
viewMode: 'docs',

View File

@ -1,8 +1,35 @@
import { toMatchImageSnapshot } from 'jest-image-snapshot'
import { OptionsParameter } from '@storybook/addons'
import { getStoryContext, TestRunnerConfig, TestContext } from '@storybook/test-runner'
import { Locator, Page, LocatorScreenshotOptions } from 'playwright-core'
import type { Locator, Page, LocatorScreenshotOptions } from 'playwright-core'
import type { Mocks } from '~/mocks/utils'
// Extend Storybook interface `Parameters` with Chromatic parameters
declare module '@storybook/react' {
interface Parameters {
options?: OptionsParameter
layout?: 'padded' | 'fullscreen' | 'centered'
testOptions?: {
/** Whether the test should be a no-op (doesn't jest.skip as @storybook/test-runner doesn't allow that). **/
skip?: boolean
/**
* Whether navigation (sidebar + topbar) should be excluded from the snapshot.
* Warning: Fails if enabled for stories in which navigation is not present.
*/
excludeNavigationFromSnapshot?: boolean
}
mockDate?: string | number | Date
msw?: {
mocks?: Mocks
}
[name: string]: any
}
}
const RETRY_TIMES = 3
const customSnapshotsDir = `${process.cwd()}/frontend/__snapshots__`
const updateSnapshot = expect.getState().snapshotState._updateSnapshot === 'all'
async function expectStoryToMatchFullPageSnapshot(page: Page, context: TestContext): Promise<void> {
await expectLocatorToMatchStorySnapshot(page, context)
@ -15,13 +42,11 @@ async function expectStoryToMatchSceneSnapshot(page: Page, context: TestContext)
async function expectStoryToMatchComponentSnapshot(page: Page, context: TestContext): Promise<void> {
await page.evaluate(() => {
const rootEl = document.getElementById('root')
if (rootEl) {
// don't expand the container element to limit the screenshot
// to the component's size
rootEl.style.display = 'inline-block'
}
// make the body transparent to take the screenshot
// without background
document.body.style.background = 'transparent'
@ -35,7 +60,7 @@ async function expectLocatorToMatchStorySnapshot(
context: TestContext,
options?: LocatorScreenshotOptions
): Promise<void> {
const image = await locator.screenshot(options)
const image = await locator.screenshot({ timeout: 3000, ...options })
expect(image).toMatchImageSnapshot({
customSnapshotsDir,
customSnapshotIdentifier: context.id,
@ -45,7 +70,7 @@ async function expectLocatorToMatchStorySnapshot(
module.exports = {
setup() {
expect.extend({ toMatchImageSnapshot })
jest.retryTimes(3, { logErrorsBeforeRetry: true })
jest.retryTimes(RETRY_TIMES, { logErrorsBeforeRetry: true })
},
async postRender(page, context) {
const storyContext = await getStoryContext(page, context)
@ -55,13 +80,13 @@ module.exports = {
document.body.classList.add('dangerously-stop-all-animations')
})
if (!storyContext.parameters?.chromatic?.disableSnapshot) {
if (!storyContext.parameters?.testOptions?.skip) {
let expectStoryToMatchSnapshot: (page: Page, context: TestContext) => Promise<void>
if (storyContext.parameters?.layout === 'fullscreen') {
if (storyContext.parameters.testRunner?.includeNavigation) {
expectStoryToMatchSnapshot = expectStoryToMatchFullPageSnapshot
} else {
if (storyContext.parameters.testOptions?.excludeNavigationFromSnapshot) {
expectStoryToMatchSnapshot = expectStoryToMatchSceneSnapshot
} else {
expectStoryToMatchSnapshot = expectStoryToMatchFullPageSnapshot
}
} else {
expectStoryToMatchSnapshot = expectStoryToMatchComponentSnapshot
@ -70,7 +95,9 @@ module.exports = {
// You'd expect that the 'load' state which @storybook/test-runner waits for would already mean
// the story is ready, and definitely that 'networkidle' would indicate all assets to be ready.
// But that's not the case, so we need to introduce a bit of a delay.
await page.waitForTimeout(200)
// The delay is extended when updating snapshots, so that we're 100% sure they represent the final state.
const delayMultiplier: number = updateSnapshot ? RETRY_TIMES : 1
await page.waitForTimeout(250 * delayMultiplier)
await expectStoryToMatchSnapshot(page, context) // Don't retry when updating
}
},

View File

@ -18,5 +18,3 @@ RUN pnpm install
COPY playwright.config.ts webpack.config.js babel.config.js tsconfig.json ./
COPY .storybook/ .storybook/
COPY frontend/ frontend/

View File

@ -12,6 +12,7 @@ module.exports = {
{
useBuiltIns: 'usage',
corejs: 3,
targets: 'defaults', // browserlist's defaults - https://github.com/browserslist/browserslist#full-list
},
],
[

View File

@ -5,7 +5,9 @@ services:
dockerfile: Dockerfile.playwright
network_mode: host
volumes:
- './frontend/__snapshots__:/work/frontend/__snapshots__'
# Mount the whole frontend/ directory to avoid to a new Docker image being generated each time
# frontend code changes. Also, this allows frontend/__snapshots__/ to be updated.
- './frontend:/work/frontend'
- './playwright:/work/playwright'
- './playwright-report:/work/playwright-report'
- './test-results:/work/test-results'

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -13,7 +13,6 @@ export default {
layout: 'fullscreen',
options: { showPanel: false },
viewMode: 'story',
testRunner: { includeNavigation: true },
},
} as Meta

View File

@ -11,7 +11,7 @@ import { ActivityScope } from 'lib/components/ActivityLog/humanizeActivity'
export default {
title: 'Components/ActivityLog',
component: ActivityLog,
parameters: { chromatic: { disableSnapshot: true } }, // FIXME: Currently disabled as the Timeout story is flaky
parameters: { testOptions: { skip: true } }, // FIXME: Currently disabled as the Timeout story is flaky
decorators: [
mswDecorator({
get: {

View File

@ -11,7 +11,7 @@ export default {
'Animations are [LottieFiles.com](https://lottiefiles.com/) animations that we load asynchronously.',
},
},
chromatic: { disableSnapshot: true },
testOptions: { skip: true }, // Animations aren't particularly snapshotable
},
argTypes: {
size: {

View File

@ -5,7 +5,7 @@ export default {
title: 'Components/Hedgehog Buddy',
component: HedgehogBuddy,
parameters: {
chromatic: { disableSnapshot: true },
testOptions: { skip: true }, // Hedgehogs aren't particularly snapshotable
},
} as ComponentMeta<typeof HedgehogBuddy>

View File

@ -8,9 +8,6 @@ import { personPropertiesModel } from '~/models/personPropertiesModel'
export default {
title: 'Filters/PropertyFilters',
component: PropertyFilters,
parameters: {
chromatic: { disableSnapshot: true },
},
} as ComponentMeta<typeof PropertyFilters>
const propertyFilters = [

View File

@ -10,9 +10,6 @@ import { cohortsModel } from '~/models/cohortsModel'
export default {
title: 'Filters/PropertyGroupFilters',
component: PropertyGroupFilters,
parameters: {
chromatic: { disableSnapshot: true },
},
} as ComponentMeta<typeof PropertyGroupFilters>
const propertyFilters = [

View File

@ -7,7 +7,7 @@ export default {
title: 'Lemon UI/Icons/Property Icon',
component: PropertyIcon,
parameters: {
chromatic: { disableSnapshot: true }, // There are too many icons, the snapshots get very big in table form
testOptions: { skip: true }, // There are too many icons, the snapshots are huge in table form
},
} as ComponentMeta<typeof PropertyIcon>

View File

@ -5,7 +5,6 @@ import { PropertyKeyInfo } from './PropertyKeyInfo'
export default {
title: 'Components/Property Key Info',
component: PropertyKeyInfo,
chromatic: { disableSnapshot: true },
} as ComponentMeta<typeof PropertyKeyInfo>
const Template: ComponentStory<typeof PropertyKeyInfo> = (args) => {

View File

@ -19,7 +19,6 @@ export default {
layout: 'fullscreen',
options: { showPanel: false },
viewMode: 'story',
chromatic: { disableSnapshot: true },
},
} as ComponentMeta<typeof SharingModal>

View File

@ -16,7 +16,7 @@ export default {
layout: 'fullscreen',
options: { showPanel: false },
viewMode: 'story',
chromatic: { disableSnapshot: true },
mockDate: '2023-01-31 12:00:00',
},
} as ComponentMeta<typeof SubscriptionsModal>

View File

@ -9,7 +9,7 @@ export default {
title: 'Filters',
decorators: [taxonomicFilterMocksDecorator],
parameters: {
chromatic: { disableSnapshot: true }, // FIXME: This is currently excluded due to flaky loading of data in it
testOptions: { skip: true }, // FIXME: This is currently excluded due to flaky loading of data in it
},
}

View File

@ -16,7 +16,7 @@ export default {
title: 'Lemon UI/Hog illustrations',
parameters: {
options: { showPanel: false },
chromatic: { disableSnapshot: true },
testOptions: { skip: true }, // Not valuable to take snapshots of these hedgehogs
docs: {
description: {
component: `

View File

@ -4,8 +4,12 @@ import { AlertMessage, AlertMessageProps } from './AlertMessage'
export default {
title: 'Lemon UI/Alert Message',
component: AlertMessage,
// See https://github.com/storybookjs/addon-smart-knobs/issues/63#issuecomment-995798227
parameters: { actions: { argTypesRegex: null }, chromatic: { disableSnapshot: false } },
parameters: {
actions: {
// See https://github.com/storybookjs/addon-smart-knobs/issues/63#issuecomment-995798227
argTypesRegex: null,
},
},
} as ComponentMeta<typeof AlertMessage>
const Template: ComponentStory<typeof AlertMessage> = (props: AlertMessageProps) => {

View File

@ -6,7 +6,6 @@ import { SurprisedHog, BlushingHog } from 'lib/components/hedgehogs'
export default {
title: 'Lemon UI/Lemon Select',
component: LemonSelect,
parameters: { chromatic: { disableSnapshot: false } },
argTypes: {
options: {
defaultValue: [

View File

@ -7,8 +7,8 @@ export default {
title: 'Lemon UI/Popover',
component: Popover,
parameters: {
chromatic: {
disableSnapshot: true, // FIXME: This story needs a play test for the popover to show up in snapshots
testOptions: {
skip: true, // FIXME: This story needs a play test for the popup to show up in snapshots
},
},
} as ComponentMeta<typeof Popover>

View File

@ -20,7 +20,7 @@ export default {
title: 'Lemon UI/Icons',
parameters: {
options: { showPanel: false },
chromatic: { disableSnapshot: true }, // There are too many icons, the snapshots get very big in table form
testOptions: { skip: true }, // There are too many icons, the snapshots are huge in table form
docs: {
description: {
component: `

View File

@ -16,7 +16,7 @@ export const mswDecorator = (mocks: Mocks): DecoratorFunction<JSX.Element> => {
for (const restMethod of Object.keys(rest)) {
mergedMocks[restMethod] = {}
// Ensure trailing slashes to avoid default handlers accidentally overshadowing story mocks
for (const [path, handler] of Object.entries(parameters.msw.mocks?.[restMethod] || {})) {
for (const [path, handler] of Object.entries(parameters.msw?.mocks?.[restMethod] || {})) {
const cleanedPath = path.replace(/\/?$/, '/')
mergedMocks[restMethod][cleanedPath] = handler
}

View File

@ -467,7 +467,7 @@
"is_debug": true,
"is_event_property_usage_enabled": true,
"licensed_users_available": 21311,
"site_url": "http://localhost:8000",
"site_url": "http://localhost:6006",
"instance_preferences": {
"debug_queries": false,
"disable_paid_fs": false

Some files were not shown because too many files have changed in this diff Show More