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.
- Run the integration tests:
# Run all testsyarn test
# Execute the tests on file (git) changesyarn test:watch# Run all tests including the word 'breadcrumb'yarn test breadcrumb# Or be more specificyarn test /breadcrumb.test.tsx# Run several togetheryarn test breadcrumb avatar button
- Update the changed snapshots:
yarn test:update# More specificyarn 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.
- 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 portalyarn start# 2. Then run screenshot tests for e.g. 'breadcrumb' or 'avatar'yarn test:screenshots breadcrumb avatar# You can also start it in watch modeyarn test:screenshots:watch breadcrumb avatar# To run against a portal on a different port, set the PORT (or port) env variablePORT=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:conditionalyarn 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 --branchyarn workspace @dnb/eufemia test:screenshots:ci:conditional:explain --uncommitted
auto behavior:
- Local: combines
uncommitted+branchfiles (deduplicated). - CI: uses
VISUAL_TEST_CHANGED_FILESprovided by GitHub Actions frompulls.listFiles. - CI does not fallback to git history when
VISUAL_TEST_CHANGED_FILESis missing.
In explain mode, each selected test includes one or more causes:
TS/JS dependency impactSCSS dependency impactComponent usage in demo/examplesPortal 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 portalyarn start# 2. Then run Playwright tests including 'Slider' or 'Button'yarn test:e2e /Slider\|Button/# You can also start it in watch modeyarn test:e2e:watch# Or run the tests for the portalyarn test:e2e:portalyarn test:e2e:portal:watch
- 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
- 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.pngand*.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(<Breadcrumbdata={[{ 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.ymlafterpostbuild: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-iconsentry 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:sizeoutput 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 measuresyarn workspace @dnb/eufemia build:ci# Check the watched bundle sizes locallyyarn workspace @dnb/eufemia build:size