Skip to main content

Adding Projects

Status: Stable This guide explains how to add new projects to an existing @qs/design-tests configuration.

Adding a New Project​

Edit your .designTests.js configuration to add a new project:

module.exports = {
projects: {
// Existing project
webapp: {
key: "webapp",
name: "Web Application",
url: "http://localhost:6006",
},

// NEW PROJECT
mobile: {
key: "mobile",
name: "Mobile Application",
url: "http://localhost:6007",
// Optional: different viewports for mobile
viewports: [
{ name: "mobile", width: 375, height: 812 },
],
}
},

// Global viewports (used by projects that don't specify their own)
viewports: [
{ name: "desktop", width: 1440, height: 900 },
],
};

Run Tests​

Run the tests - the new project will automatically create its baseline:

npx design-tests run --config .designTests.js --all

Output:

📝 Project "mobile": Creating baseline (no existing baseline found)
🔍 Project "webapp": Comparing against baseline
...
📊 Test Results: 0 visual differences, 45 new baselines detected

Notice:

  • Existing project (webapp): Compares against existing baseline
  • New project (mobile): Creates baseline automatically

Directory Structure​

The baseline images are organized by project:

design-tests/
├── baseline-images/
│ ├── webapp/ # Existing project
│ │ └── components/
│ └── mobile/ # NEW - automatically created
│ └── screens/
├── actual-images/
├── diff-output/
└── report/

Commit New Baseline​

Commit only the new project's baseline:

git add design-tests/baseline-images/mobile/
git commit -m "Add baseline for mobile project"
git push

Test a Single Project​

To test only the new project during setup:

npx design-tests run --config .designTests.js --project mobile

This is useful for:

  • Faster iteration during project setup
  • Debugging project-specific configuration
  • Testing URL accessibility

Multi-Project Example​

Here's an example with multiple projects (like EWZ's 8 Storybooks):

module.exports = {
projects: {
components: {
key: "components",
url: "http://localhost:6001",
ignoreList: ["internal/*"],
},
widgets: {
key: "widgets",
url: "http://localhost:6002",
},
templates: {
key: "templates",
url: "http://localhost:6003",
},
// ... up to 25+ projects
},

viewports: [
{ name: "mobile", width: 375, height: 812 },
{ name: "desktop", width: 1440, height: 900 },
],
};

Same Storybook, Multiple Tenants (storyParams)​

Some Storybooks render several tenant themes from one deployment, selected by a query parameter rather than by URL. EKZ's Storybook serves eight tenants this way, picked with ?theme=Enpuls. storyParams appends a project's query parameters to every story URL it builds, so each tenant becomes its own project — with its own baseline — against the same url. Generate the eight blocks from a tenant array instead of pasting eight near-identical objects:

const tenants = ["EKZ", "Enpuls", "Eltop", "Certum", "HansSpillmann", "WellenbergWind", "ZuerichWind", "WaermeThalwil"];

module.exports = {
projects: Object.fromEntries(
tenants.map((tenant) => {
const key = tenant.toLowerCase();
return [
key,
{
key,
name: `EKZ Storybook (${tenant})`,
url: "http://ekz-storybook.internal.svc",
publicUrl: "https://ekz-storybook.external.quatico.dev",
storyParams: { theme: tenant },
},
];
})
),

viewports: [
{ name: "mobile", width: 375, height: 812 },
{ name: "desktop", width: 1440, height: 900 },
],
};

This produces capture URLs like http://ekz-storybook.internal.svc/iframe.html?id=basis-base-colors--base-colors&hideAnimation=true&theme=Enpuls and matching public links with &theme=Enpuls appended, so the HTML report links to the exact tenant rendering that was captured. A project with no storyParams is unaffected — its URLs are unchanged. The same mechanism covers other query-driven variants, such as language or grid.

A story that pins its own variant overrides the query parameter. Storybook reads parameters before the URL, so a story declaring parameters: { theme: "Certum" } renders Certum in every project and produces one identical image per project. That is redundant rather than wrong — exclude such stories per project with ignoreList when the duplicate captures cost more than the config does.

Per-Project Configuration​

Each project can override global settings:

projects: {
slowapp: {
key: "slowapp",
url: "http://localhost:8080",

// Override stability settings for this project
stability: {
strategy: "advanced",
waitForNetworkIdle: true,
networkIdleTimeout: 60000, // Longer timeout
waitBeforeScreenshot: 2000,
},

// Custom screenshot path builder
snapshotsDirFn: ({ projectKey, story, viewport }) => {
return `${projectKey}/${story.title}/${story.name}-${viewport.name}`;
},

// Project-specific viewports
viewports: [
{ name: "ultra-wide", width: 2560, height: 1440 },
],
}
}

Testing Non-Storybook Pages​

For projects without Storybook (like BFH), use manual page lists:

projects: {
cms: {
key: "cms",
name: "CMS Pages",
url: "http://www.example.com",

// Manual page list instead of Storybook discovery
pages: [
{ name: "Homepage/DE", path: "/de/" },
{ name: "Homepage/EN", path: "/en/" },
{ name: "Products/Listing", path: "/en/products" },
{ name: "Contact", url: "http://contact.example.com/form" }, // Absolute URL
],
}
}

Removing a Project​

To remove a project:

  1. Delete it from .designTests.js
  2. Delete its baseline directory
  3. Commit the changes
rm -rf design-tests/baseline-images/old-project/
git add .designTests.js design-tests/baseline-images/
git commit -m "Remove old-project from design tests"

Next Steps​