Skip to content

Make and run tests#

Make tests for the new component (or for your current issue) and set up screenshot tests from the Eufemia portal. The tests should be located under __tests__ in the component folder.

  • Tip 1: Create tests for each prop that change your component.
  • Tip 2: Always check and make the tests fail when you are writing tests.

More on testing in the UI Library.

Running tests locally#

Run the commands from the repository's root folder. Replace breadcrumb with your component's name in the commands.

  1. Run the integration tests:
# Run all tests
yarn test
# Execute the tests on file (git) changes
yarn test:watch
# Run all tests including the word 'breadcrumb'
yarn test breadcrumb
# Or be more specific
yarn test /breadcrumb.test.tsx
# Run several together
yarn test breadcrumb avatar button
  1. Update the changed snapshots:
yarn test:update
# More specific
yarn test:update breadcrumb avatar

Integration tests use this naming convention: /__tests__/{ComponentName}.test.tsx

Vitest already loads @testing-library/jest-dom through packages/dnb-eufemia/src/core/vitest/setupVitest.ts, which imports @testing-library/jest-dom/vitest. DOM matchers such as toBeInTheDocument, toBeVisible, and toHaveTextContent are therefore available in tests without extra per-test setup.

  1. Run visual and end-to-end tests:

NB: Make sure you have the portal running locally on port 8000.

Visual tests:

# 1. First start the portal
yarn start
# 2. Then run screenshot tests for e.g. 'breadcrumb' or 'avatar'
yarn test:screenshots breadcrumb avatar
# You can also start it in watch mode
yarn test:screenshots:watch breadcrumb avatar
# To run against a portal on a different port, set the PORT (or port) env variable
PORT=8001 yarn test:screenshots breadcrumb

Visual screenshot tests use this naming convention: /__tests__/{ComponentName}.screenshot.test.ts

Run selected themes only on main#

For screenshot tests, you can mark individual themes as main-only and keep the rest on all branches.

import {
makeScreenshot,
setupPageScreenshot,
selectThemes,
onMain,
} from '../../../core/vitest-screenshots/setupVitestScreenshots'
describe.each(
selectThemes({
always: ['ui', 'sbanken'],
onMain: ['eiendom'],
})
)('Button for %s', (themeName) => {
setupPageScreenshot({
themeName,
url: '/uilib/components/button/demos/',
})
it('matches default state', async () => {
await makeScreenshot({
selector: '[data-visual-test="button-primary"]',
})
})
})

selectThemes({ always, onMain }) only applies branch filtering in CI. In CI, onMain runs on main and branches starting with v followed by a digit, such as v11 or v11-fix. Outside CI, the guarded themes still run locally.

You can also use callback mode for single tests:

onMain(() =>
it('matches default state', async () => {
await makeScreenshot({
selector: '[data-visual-test="button-primary"]',
})
})
)

Conditional screenshot testing#

In CI, screenshot tests are selected from changed files instead of always running all screenshot suites.

Selection includes:

  • Direct screenshot owners of changed files.
  • Reverse TypeScript/JavaScript dependencies.
  • Reverse SCSS dependencies.
  • Demo/example composition usage from Portal docs.
  • Portal docs/demo path impact (changed docs can trigger only related screenshot tests).

Global impact still runs all screenshot tests when shared visual config/style paths are changed (for example packages/dnb-eufemia/package.json, src/style/* or it runs on the main branch).

Reverse-dependency selection resolves imports by TypeScript symbol, so an import through a re-export barrel (for example src/components/index.ts) is attributed to the module that actually defines the symbol. Pure re-export barrels are therefore transparent, and type-only imports are ignored because they cannot change rendered output.

You can run the same logic locally:

yarn workspace @dnb/eufemia test:screenshots:ci:conditional
yarn workspace @dnb/eufemia test:screenshots:ci:conditional:explain

You can choose change scope explicitly if needed:

yarn workspace @dnb/eufemia test:screenshots:ci:conditional:explain --branch
yarn workspace @dnb/eufemia test:screenshots:ci:conditional:explain --uncommitted

auto behavior:

  • Local: combines uncommitted + branch files (deduplicated).
  • CI: uses VISUAL_TEST_CHANGED_FILES provided by GitHub Actions from pulls.listFiles.
  • CI does not fallback to git history when VISUAL_TEST_CHANGED_FILES is missing.

In explain mode, each selected test includes one or more causes:

  • TS/JS dependency impact
  • SCSS dependency impact
  • Component usage in demo/examples
  • Portal docs/demo impact

By default, CI still stops on the first failure (--bail). You can force a full run without --bail by including --run-all in your commit message.

Default behavior (stops on first failure):

git commit -m "feat: implement new feature"
# Runs: vitest run --config vitest.config.screenshots.ts

Run all tests (continues on failures):

git commit -m "feat: implement new feature --run-all"
# Runs: vitest run --config vitest.config.screenshots.ts

This is useful when you want to see all visual test failures at once, rather than stopping at the first one. The CI/CD pipeline automatically detects this flag and adjusts test behavior accordingly.

Playwright end-to-end tests:

# 1. First start the portal
yarn start
# 2. Then run Playwright tests including 'Slider' or 'Button'
yarn test:e2e /Slider\|Button/
# You can also start it in watch mode
yarn test:e2e:watch
# Or run the tests for the portal
yarn test:e2e:portal
yarn test:e2e:portal:watch
  1. Update any new or changed visual PNG snapshots:
# Update screenshot tests including 'breadcrumb'
yarn test:screenshots:update breadcrumb

You can also press the u during a watch mode to update outdated snapshots.

To fully renew snapshots (delete all existing PNGs first, then regenerate from scratch), use test:screenshots:renew. This is useful when snapshots have drifted or you want a clean baseline:

# Delete and regenerate all snapshots for 'phone' and 'radio'
yarn test:screenshots:renew phone radio
  1. How to deal with failing visual tests?

When a visual test fails, a visual comparison file (diff) will be created. Its location and name will be:

  • **/__tests__/__image_snapshots__/.diff/*.diff.png and *.actual.png

you can find a report entry (index.html), that lists all of the failed tests here:

  • /packages/dnb-eufemia/visual-diff-report/index.html

You may check out the CI/CLI logs for more details.

GitHub Actions: When a visual screenshot test fails on the CI, the failed screenshots are listed directly on the run's "Summary" page. When report hosting is available, each row links to a hosted before/after/diff view, so no download is needed. The same report is also attached as the visual-test-artifact zip file (containing /visual-diff-report with index.html and the diff images) for offline inspection.

Support SCSS snapshot test#

Add a similar code snippet to your tests for watching changes in the SCSS you just created.

import { loadScss } from '../../../core/test-utils/testSetup'
describe('Button scss', () => {
it('has to match style dependencies css', () => {
const css = loadScss(require.resolve('../style/deps.scss'))
expect(css).toMatchSnapshot()
})
it.each(['ui', 'sbanken'])(
'has to match theme css for %s',
(themeName) => {
const css = loadScss(
require.resolve(
`../style/themes/dnb-button-theme-${themeName}.scss`
)
)
expect(css).toMatchSnapshot()
}
)
})

Support Axe test#

Add a similar code snippet to your tests (as the last test). It will test the accessibility of your new component. Read more on Jest Axe.

describe('Breadcrumb aria', () => {
it('should validate', async () => {
const Component = render(
<Breadcrumb
data={[
{ href: '/' },
{ href: '/page1', text: 'Page 1' },
{ href: '/page1/page2', text: 'Page 2' },
]}
variant="collapse"
collapsed={false}
/>
)
expect(await axeComponent(Component)).toHaveNoViolations()
})
})

Bundle size checks#

Eufemia uses BundleWatch to track selected build artifacts in CI.

How it works:

  • The setup lives in packages/dnb-eufemia/bundlewatch.config.js.
  • CI runs the check in .github/workflows/verify.yml after postbuild:ci, so it measures the actual emitted build files.
  • Each watched file has its own maxSize, based on the current gzip-compressed output with some headroom for future changes.
  • The check is meant to catch regressions in meaningful emitted bundles, not to report every generated file in the package.

What is included:

  • UMD and ESM dnb-ui-* bundles, except icon entry bundles.
  • Core dnb-ui-* CSS bundles.
  • Non-isolated theme CSS bundles under build/style/themes/.

What is excluded:

  • dnb-ui-icons entry bundles are excluded on purpose because they are thin import/re-export wrappers and do not represent a meaningful payload by themselves.
  • Isolated CSS bundles are excluded to keep the reporting focused on the primary outputs.

When updating limits:

  • If a deliberate change increases a bundle size, update the matching limit in bundlewatch.config.js.
  • Prefer checking the local build:size output before changing limits, so the new threshold is based on the current emitted gzip size.

Run the commands locally if needed to emulate the CI checks:

# Build the library outputs that BundleWatch measures
yarn workspace @dnb/eufemia build:ci
# Check the watched bundle sizes locally
yarn workspace @dnb/eufemia build:size
Edit on GitHub