This article is not yet published and is not visible to search engines.
Visual Testing in a Monorepo: How Not to Test 47 Projects on Every Commit

Visual Testing in a Monorepo: How Not to Test 47 Projects on Every Commit

A monorepo (monolithic repository) is a source code management architecture where multiple projects, applications, and shared libraries coexist in a single Git repository, managed by orchestration tools like Nx, Turborepo, or Lerna that optimize builds and tests based on the dependency graph.

Monorepos have won. Front-end teams managing multiple applications — a marketing site, an admin dashboard, a customer app, a partner portal — group them into a single repository. It's simpler for sharing code, more consistent for the design system, more efficient for cross-cutting updates.

But for visual testing, it's a logistical nightmare. Not because visual testing doesn't work in a monorepo. It works perfectly well. The problem is that it works too well: a change in a shared package potentially triggers visual tests across every project that depends on it. And when your monorepo contains 15, 30, or 50 projects, "test everything on every commit" is no longer an option.

The Fundamental Problem: The Dependency Graph

In a monorepo, projects are not independent. They share packages: a design system, a utility library, common components, shared configurations. These dependencies form a graph — and it's this graph that makes visual testing complex.

Let's take a concrete example. You have a "ui-components" package containing your buttons, forms, and navigation components. This package is used by 12 applications in your monorepo. You modify the padding of a button in "ui-components." Visually, this change potentially affects all 12 applications. Every page containing a button could render differently.

If you run visual tests on all 12 applications, you're potentially capturing hundreds of screenshots. The CI/CD pipeline takes 45 minutes. Developers wait. And if one test fails — a false positive due to a headless rendering issue — the "ui-components" developer has to investigate a failure in an application they don't even know.

It's unsustainable. And it's exactly what happens in monorepos that don't have a visual testing strategy.

Test What Changed, Not Everything: The Affectation Principle

The first rule of visual testing in a monorepo is simple in theory, complex in practice: only test what is affected by the change.

Modern monorepo tools — Nx and Turborepo leading the way — can calculate the dependency graph and identify projects "affected" by a change. When you modify a file in the "ui-components" package, Nx can tell you that applications A, C, F, and K depend on this package, but not B, D, and the others.

That's the foundation. But for visual testing, it's not enough. Because "application A depends on ui-components" doesn't mean all pages of application A use the modified component. If you changed the button, only pages containing a button are affected. Pages without one are stable.

Intelligent visual testing in a monorepo therefore operates on two levels of filtering. First level: which projects are affected by the change, according to the dependency graph. Second level: which pages or components within those projects actually use the modified element.

The first level is solved by monorepo tools. The second level is much harder — it requires either static code analysis (which pages import which component) or an explicit mapping maintained by the team (this component is used on these pages).

The Reality of Shared Packages

Shared packages are the strength and weakness of a monorepo. They enable consistency: a single design system, a single source of truth for visual components. But they also create a considerable blast radius.

There are three types of shared packages that pose specific problems for visual testing.

UI component packages. This is the most obvious case. A change to a button, form, or modal visually affects every page that uses them. The blast radius is proportional to the component's popularity. A layout component used everywhere (Header, Footer, Sidebar) has a maximum blast radius.

Style and token packages. A change in design tokens — colors, spacing, typography — is often invisible in the traditional dependency graph. If you modify the primary color in your token file, the dependency graph sees a change in the "design-tokens" package. But this change visually affects everything: every primary button, every link, every accented element across all applications. The blast radius is total.

Utility packages with visual side effects. Less obvious: utility functions that format dates, truncate text, or calculate layouts. A change in a text truncation function — going from 100 to 80 max characters — has a direct visual impact on every component that uses it. But the dependency graph doesn't know it's a "visual" change.

Strategies That Work

After observing dozens of teams manage visual testing in monorepos, here are the strategies that produce the best results.

Strategy 1: Layered Visual Testing

Organize your visual tests in three layers with different execution frequencies.

The component layer. Test each design system component in isolation, via Storybook or equivalent. These tests run only when the component package changes. They're fast (a few minutes) and protect the library itself. This is the first safety net, the one described in our article about visual testing with Storybook.

The affected page layer. Test the pages of applications affected by the change, identified by the dependency graph. These tests run on every pull request, but only on affected projects. They're the core of your strategy.

The full layer. Test all pages of all applications. This layer doesn't run on every PR — it's too expensive. Run it once a day (nightly), or before each release. It catches regressions that the dependency graph couldn't predict.

Strategy 2: Explicit Component-to-Page Mapping

Maintain a configuration file that associates each shared component with the pages that use it. When the Button component changes, the file indicates that the /login, /signup, /checkout, and /settings pages of application A use that component. Only those pages are tested.

This mapping is tedious to maintain manually. But it can be auto-generated through static code analysis — by traversing each page's imports and walking up the dependencies. Nx offers plugins that facilitate this kind of analysis.

The benefit is substantial: instead of testing 200 pages of application A when a component changes, you test only 12. The pipeline goes from 30 minutes to 3 minutes.

Strategy 3: Shared Baseline with Override

In a monorepo with a shared design system, visual baselines (reference screenshots) have a peculiarity: shared components have the same rendering in all applications (in theory). You can therefore maintain a baseline of the design system at the package level, and only re-capture baselines at the application level when the integration context changes.

Concretely: when you modify the Button component, you update the Button baseline in the ui-components package. Applications using the Button inherit this new baseline. Only applications where the Button renders differently in context (due to specific CSS, theming, or overrides) need their own baselines.

Classic Mistakes to Avoid

Testing everything, all the time. This is the natural reaction, and the worst one. Your pipelines become slow, your developers ignore results because there's too much noise, and real bugs drown in false positives. Visual testing in a monorepo must be surgical.

Ignoring the dependency graph. Some teams configure their visual tests independently from the monorepo tool. Result: tests don't know which projects are affected and test either everything or nothing. Integrate your visual testing tool with Nx or Turborepo. Use their "affected" commands to trigger the right tests.

Putting all baselines in a single folder. Visual baselines should live alongside the projects they protect. If you centralize all baselines in a root folder, you lose granularity: you no longer know which baseline belongs to which project, and cleaning up obsolete baselines becomes impossible.

Neglecting baseline versioning during cross-cutting updates. When you modify a design token that affects all projects, all baselines must be updated simultaneously. If you update them project by project in separate PRs, you create a window where some projects have new baselines and others have old ones. Tests become inconsistent.

The Role of CI/CD in Monorepo Visual Testing

CI/CD configuration is critical. Your pipeline must be capable of three things. Our article on visual testing in CI/CD pipelines covers the integration patterns in depth.

First, calculate the set of projects affected by the change. This is the job of Nx (nx affected) or Turborepo (turbo run --filter). This step determines the scope of tests.

Second, parallelize visual tests by project. If three applications are affected, run all three applications' tests in parallel, not sequentially. Each application has its own set of baselines and its own results report. Parallelization is the only way to maintain acceptable pipeline times.

Third, aggregate results and block the PR if necessary. Even if tests are parallelized, the final verdict must be singular: the PR passes or it doesn't. If application A has a visual regression and the others don't, the PR is blocked. The developer knows exactly which application is affected and can investigate.

When No-Code Simplifies Everything

Code-based visual testing tools — Playwright, Cypress with visual plugins — integrate well into a monorepo because they live in the same repository, alongside the code they test. But they add test code to maintain, per-project configurations, and specific CI/CD scripts.

No-code tools like Delta-QA offer a different approach. Rather than testing from source code, they test from deployed URLs. You configure each application's URLs, the tool captures screenshots and compares them against baselines. The monorepo is no longer a complexity factor — the tool only sees URLs, not packages.

The downside: you don't benefit from the dependency graph to filter tests. The upside: configuration is instant, there's no test code to maintain, and the test is independent of the repository architecture. For teams without dedicated test developers, it's often the right trade-off.

FAQ

Is Nx or Turborepo better for visual testing in a monorepo?

Nx has a significant advantage thanks to its more detailed dependency graph and static analysis plugins. The nx affected command precisely identifies impacted projects, and Nx executors allow integrating visual testing tasks into the task graph. Turborepo is simpler but offers less granular filtering.

How do you manage baselines when a design token changes and affects everything?

Treat it as a cross-cutting update. Create a dedicated PR that modifies the token and updates all affected baselines in a single operation. Run the full layer of visual tests, approve expected changes in bulk, and merge. Never fragment a token update into multiple PRs — it opens the door to inconsistencies.

How long should a visual test take in a monorepo pipeline?

Targeting only affected projects: under 10 minutes for a standard PR touching a single project. Under 20 minutes for a change in a shared package affecting 3 to 5 projects. If your tests take more than 30 minutes on a PR, your filtering strategy is insufficient. The full layer (nightly) can take longer — 30 to 60 minutes is acceptable.

Should there be separate baselines per environment (dev, staging, prod)?

Yes, if the environments have different content or configurations. No, if the rendering is identical. In practice, maintain a single baseline per project, captured in a stable staging environment. Comparing against production is tempting, but dynamic content (dates, user data, A/B tests) generates too many false positives.

Does visual testing in a monorepo work with Lerna?

Lerna is primarily a version management and package publishing tool. Its dependency graph is less sophisticated than Nx or Turborepo's. You can use it, but you'll probably need to supplement it with a custom script to identify affected projects. If you're starting a new monorepo, prefer Nx or Turborepo.

How do you prevent developers from ignoring visual test results?

Three rules. Keep the false positive rate near zero — if developers regularly see false positives, they stop looking. Block PRs on visual test failures — no merge without approval. And keep pipeline times short — if visual testing adds 45 minutes to the PR, developers will find ways to bypass it.


Further reading


A monorepo is an excellent architecture for code sharing and consistency. But without an adapted visual testing strategy, it turns every change into a lottery: did this commit break something in a project I don't even know? The answer to that question should never be "I don't know." It should be automatic, fast, and reliable.

Try Delta-QA for Free →