@manufac/general-tests
v0.10.1
Published
A library for general tests for landing pages
Keywords
Readme
@manufac/general-tests
Shared Playwright regression suite for landing pages, built from the Landing Page Test Plan. It ships generic, page-agnostic checks (page load, responsive layout, forms, CTAs/links, media, orientation, touch targets, etc.) that any landing-page app in the monorepo can run against its own pages.
See CHANGELOG.md for release notes.
Consuming from an app
Add it as a normal dependency, pinned to the version you want to consume:
npm install @manufac/general-testsThen wire it into the app's playwright.config.ts.
First, declare the app-specific pages in a small local config file, typed against ManufacTestConfig:
// manufac-test.config.ts
import type { ManufacTestConfig } from "@manufac/general-tests/schema/index.js";
export const Config: ManufacTestConfig = {
pages: ["/", "/pricing", "/contact-us", "/privacy"],
};Then reference it from playwright.config.ts:
import { defineConfig, devices } from "@playwright/test";
import { dirname, join } from "node:path";
import {
DesktopBreakpoints,
ResponsiveLayoutBreakpoints,
LoadingPageTestMatch,
ResponsiveLayoutTestMatch,
} from "@manufac/general-tests/config/projects.js";
import type { PageTestOptions } from "@manufac/general-tests/fixtures/index.js";
import type { Project } from "@playwright/test";
import { Config } from "./manufac-test.config.js";
// Resolves to node_modules/@manufac/general-tests/dist/specs, wherever it's installed from.
const GeneralTestsSpecsDir = join(dirname(require.resolve("@manufac/general-tests/config/projects.js")), "..", "specs");
const Projects: Project[] = [
{
name: "Desktop Chrome",
testDir: GeneralTestsSpecsDir,
testMatch: [LoadingPageTestMatch, ResponsiveLayoutTestMatch],
use: { ...devices["Desktop Chrome"], viewport: DesktopBreakpoints.Desktop },
},
{
name: "Desktop Safari",
testDir: GeneralTestsSpecsDir,
testMatch: [LoadingPageTestMatch, ResponsiveLayoutTestMatch],
use: { ...devices["Desktop Safari"], viewport: DesktopBreakpoints.Desktop },
},
{
name: "Desktop Firefox",
testDir: GeneralTestsSpecsDir,
testMatch: [LoadingPageTestMatch, ResponsiveLayoutTestMatch],
use: { ...devices["Desktop Firefox"], viewport: DesktopBreakpoints.Desktop },
},
{
name: "Responsive 375",
testDir: GeneralTestsSpecsDir,
testMatch: ResponsiveLayoutTestMatch,
use: { ...devices["Desktop Chrome"], viewport: ResponsiveLayoutBreakpoints.MobileL },
},
{
name: "Responsive 768",
testDir: GeneralTestsSpecsDir,
testMatch: ResponsiveLayoutTestMatch,
use: { ...devices["Desktop Chrome"], viewport: ResponsiveLayoutBreakpoints.Tablet },
},
];
export default defineConfig<PageTestOptions>({
testDir: "./e2e",
use: {
baseURL: "https://your-app.example.com",
pages: Config.pages,
},
projects: Projects,
});LoadingPageTestMatchrunsloading-page.spec.ts— page-load/console-error/network checks, once per project, over every page inConfig.pages.ResponsiveLayoutTestMatchrunsresponsive-layout.spec.ts— checked at whicheverviewporteach project sets, so add one project per breakpoint you care about (ResponsiveLayoutBreakpoints.MobileL/.Tablet/.Desktop).require.resolve+join(..., "..", "specs")finds the package's builtspecsdirectory regardless of how it's installed (registry,file:, or a vendored tarball) —testDirmust point there since these two spec files aren't re-exported any other way.PageTestOptions(fromfixtures/index.js) types theuseblock sopages(andforms, if you set it) are checked against the fixture's actual shape.
Storybook visual regression
VisualRegressionTestMatch runs visual-regression.spec.ts — a single generic test that walks every story in a running Storybook instance and compares it against a baseline screenshot with Playwright's toHaveScreenshot(). No per-component spec files are needed; new stories are picked up automatically.
It needs its own Playwright config, separate from the one above, since it runs against Storybook's dev server instead of the app itself:
// playwright.storybook.config.ts
import { defineConfig, devices } from "@playwright/test";
import { dirname, join } from "node:path";
import { VisualRegressionTestMatch } from "@manufac/general-tests/config/projects.js";
const GeneralTestsSpecsDir = join(dirname(require.resolve("@manufac/general-tests/config/projects.js")), "..", "specs");
export default defineConfig({
testDir: GeneralTestsSpecsDir,
testMatch: VisualRegressionTestMatch,
// Relative, so Playwright resolves it against this config file's own directory (not
// testDir, which points into the installed package) — baselines stay in this repo.
snapshotPathTemplate: "e2e-visual/__screenshots__/{arg}{ext}",
reporter: [["html", { open: "never" }]],
expect: { toHaveScreenshot: { maxDiffPixelRatio: 0.001, animations: "disabled" } },
use: { ...devices["Desktop Chrome"], viewport: { width: 1366, height: 768 }, baseURL: "http://localhost:6006" },
webServer: {
command: "npm run start:sb -- --ci --port 6006",
url: "http://localhost:6006",
reuseExistingServer: true,
},
});Then add two scripts to the app's package.json (adjust start:sb/build:sb to however the app runs Storybook):
{
"scripts": {
"test:visual": "npm run build:sb && playwright test -c playwright.storybook.config.ts",
"update:visual": "npm run build:sb && playwright test -c playwright.storybook.config.ts --update-snapshots"
}
}Baselines land in e2e-visual/__screenshots__/<story-id>.png, next to playwright.storybook.config.ts.
Full-page visual regression
PageVisualRegressionTestMatch runs page-visual-regression.spec.ts — one generic test that visits every page in Config.pages and compares a full-page screenshot against a saved baseline with toHaveScreenshot(). A page path becomes the snapshot name by trimming slashes (/ -> home, /pricing -> pricing, /docs/setup -> docs-setup). Failures are collected, so one broken page doesn't hide the others.
Setup
Run it in its own project, so it starts only after the other projects finish. Full-page screenshots of long pages are slow and sensitive to load, and running them next to other tests makes them flaky. Projects that list each other in dependencies run one after another, so only one visual project runs at a time.
import { LoadingPageTestMatch, PageVisualRegressionTestMatch } from "@manufac/general-tests/config/projects.js";
const Projects: Project[] = [
{
name: "Desktop Chrome",
testDir: GeneralTestsSpecsDir,
testMatch: [LoadingPageTestMatch],
use: { ...devices["Desktop Chrome"], viewport: DesktopBreakpoints.Desktop },
},
{
name: "Desktop Chrome Visual",
testDir: GeneralTestsSpecsDir,
testMatch: PageVisualRegressionTestMatch,
dependencies: ["Desktop Chrome"],
snapshotPathTemplate: "e2e/__screenshots__/Desktop-Chrome/{arg}{ext}",
use: { ...devices["Desktop Chrome"], viewport: DesktopBreakpoints.Desktop },
},
{
// Chain the next visual project onto the previous one.
name: "Responsive 375 Visual",
testDir: GeneralTestsSpecsDir,
testMatch: PageVisualRegressionTestMatch,
dependencies: ["Desktop Chrome Visual"],
snapshotPathTemplate: "e2e/__screenshots__/Responsive-375/{arg}{ext}",
use: { ...devices["Desktop Chrome"], viewport: ResponsiveLayoutBreakpoints.MobileL },
},
];
export default defineConfig<PageTestOptions>({
testDir: "./e2e",
expect: { timeout: 10_000, toHaveScreenshot: { maxDiffPixelRatio: 0.001, animations: "disabled" } },
use: { baseURL: "https://your-app.example.com", pages: Config.pages, actionTimeout: 10_000 },
projects: Projects,
});- Each visual project has its own
snapshotPathTemplate, so baselines are kept per project (Desktop Chrome and 375 differ in layout) and stay in the repo. - The image and video waits have no timeout of their own. They use
use.actionTimeout, so one stuck image fails that page instead of hanging the suite. workersis a global Playwright setting and can't be set per project. Chaining the projects gives the same result for the visual ones, because each has a single test and only one project runs at a time.- If a project in
dependenciesfails, the visual projects are skipped. Use--no-depsto run them on their own.
Scripts
{
"scripts": {
"test:visual:pages": "playwright test page-visual-regression --no-deps --workers=1 --project=\"* Visual\"",
"update:visual:pages": "npm run test:visual:pages -- --update-snapshots=all"
}
}- The first run has no baselines, so use
update:visual:pages. - Use
--update-snapshots=allto rewrite every baseline. A plain--update-snapshotsonly rewrites the ones that fail the comparison, so old files can stay behind. - Review the new images before committing them. Check that the header is at the top and the video area is a solid box.
- Don't run two Playwright runs at once in the same app. They share
test-results, and one run can delete the other's files.
CTA & link destination checks
CTADestinationsTestMatch runs cta-destinations.spec.ts — CT-01 clicks each configured CTA and verifies it lands on its expected destination (same tab by default, or a new tab when opensInNewTab is set); CT-02 visits every internal link reachable from Config.pages and checks it isn't a 404 or blank page, then verifies each configured legal link still points to its expected URL.
Both are driven entirely by config, since the shared package has no way to know an individual app's design spec on its own:
// manufac-test.config.ts
import type { ManufacTestConfig } from "@manufac/general-tests/schema/index.js";
export const Config: ManufacTestConfig = {
pages: ["/", "/pricing", "/contact-us", "/privacy"],
ctas: [
{ ctaID: "hero-cta", page: "/", name: "Get Started", expectedHref: "/contact-us" },
{ ctaID: "pricing-cta", page: "/pricing", name: "Book a demo", expectedHref: "/contact-us", opensInNewTab: true },
],
legalLinks: [{ page: "/", name: "Privacy Policy", expectedHref: "/privacy" }],
};Add CTADestinationsTestMatch to a project's testMatch the same way as LoadingPageTestMatch. CT-01 skips entirely if ctas is left empty.
