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:
- Delete it from
.designTests.js - Delete its baseline directory
- 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
- Baseline Management - Update and recreate baselines
- Getting Started - Initial setup guide
- CLI Reference - Full command options