@manufac/general-tests 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -0
- package/dist/config/projects.d.ts +1 -0
- package/dist/config/projects.js +2 -1
- package/dist/schema/index.d.ts +6 -0
- package/dist/schema/index.js +7 -1
- package/dist/specs/utils.js +4 -1
- package/dist/specs/visual-regression.spec.d.ts +1 -0
- package/dist/specs/visual-regression.spec.js +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,3 +90,47 @@ export default defineConfig<PageTestOptions>({
|
|
|
90
90
|
- `ResponsiveLayoutTestMatch` runs `responsive-layout.spec.ts` — checked at whichever `viewport` each project sets, so add one project per breakpoint you care about (`ResponsiveLayoutBreakpoints.MobileL` / `.Tablet` / `.Desktop`).
|
|
91
91
|
- `require.resolve` + `join(..., "..", "specs")` finds the package's built `specs` directory regardless of how it's installed (registry, `file:`, or a vendored tarball) — `testDir` must point there since these two spec files aren't re-exported any other way.
|
|
92
92
|
- `PageTestOptions` (from `fixtures/index.js`) types the `use` block so `pages` (and `forms`, if you set it) are checked against the fixture's actual shape.
|
|
93
|
+
|
|
94
|
+
## Storybook visual regression
|
|
95
|
+
|
|
96
|
+
`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.
|
|
97
|
+
|
|
98
|
+
It needs its own Playwright config, separate from the one above, since it runs against Storybook's dev server instead of the app itself:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// playwright.storybook.config.ts
|
|
102
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
103
|
+
import { dirname, join } from "node:path";
|
|
104
|
+
import { VisualRegressionTestMatch } from "@manufac/general-tests/config/projects.js";
|
|
105
|
+
|
|
106
|
+
const GeneralTestsSpecsDir = join(dirname(require.resolve("@manufac/general-tests/config/projects.js")), "..", "specs");
|
|
107
|
+
|
|
108
|
+
export default defineConfig({
|
|
109
|
+
testDir: GeneralTestsSpecsDir,
|
|
110
|
+
testMatch: VisualRegressionTestMatch,
|
|
111
|
+
// Relative, so Playwright resolves it against this config file's own directory (not
|
|
112
|
+
// testDir, which points into the installed package) — baselines stay in this repo.
|
|
113
|
+
snapshotPathTemplate: "e2e-visual/__screenshots__/{arg}{ext}",
|
|
114
|
+
reporter: [["html", { open: "never" }]],
|
|
115
|
+
expect: { toHaveScreenshot: { maxDiffPixelRatio: 0.001, animations: "disabled" } },
|
|
116
|
+
use: { ...devices["Desktop Chrome"], viewport: { width: 1366, height: 768 }, baseURL: "http://localhost:6006" },
|
|
117
|
+
webServer: {
|
|
118
|
+
command: "npm run start:sb -- --ci --port 6006",
|
|
119
|
+
url: "http://localhost:6006",
|
|
120
|
+
reuseExistingServer: true,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Then add two scripts to the app's `package.json` (adjust `start:sb`/`build:sb` to however the app runs Storybook):
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"scripts": {
|
|
130
|
+
"test:visual": "npm run build:sb && playwright test -c playwright.storybook.config.ts",
|
|
131
|
+
"update:visual": "npm run build:sb && playwright test -c playwright.storybook.config.ts --update-snapshots"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Baselines land in `e2e-visual/__screenshots__/<story-id>.png`, next to `playwright.storybook.config.ts`.
|
|
@@ -35,3 +35,4 @@ export declare const ProjectTypes: {
|
|
|
35
35
|
export declare const LoadingPageTestMatch = "loading-page.spec.js";
|
|
36
36
|
export declare const ResponsiveLayoutTestMatch = "responsive-layout.spec.js";
|
|
37
37
|
export declare const FormsTestMatch = "forms.spec.js";
|
|
38
|
+
export declare const VisualRegressionTestMatch = "visual-regression.spec.js";
|
package/dist/config/projects.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FormsTestMatch = exports.ResponsiveLayoutTestMatch = exports.LoadingPageTestMatch = exports.ProjectTypes = exports.ResponsiveLayoutBreakpoints = exports.DesktopBreakpoints = void 0;
|
|
3
|
+
exports.VisualRegressionTestMatch = exports.FormsTestMatch = exports.ResponsiveLayoutTestMatch = exports.LoadingPageTestMatch = exports.ProjectTypes = exports.ResponsiveLayoutBreakpoints = exports.DesktopBreakpoints = void 0;
|
|
4
4
|
// Section 3 of the Landing Page Test Plan — breakpoints / viewports.
|
|
5
5
|
exports.DesktopBreakpoints = {
|
|
6
6
|
Desktop: { name: "Desktop", width: 1440, height: 900 },
|
|
@@ -18,3 +18,4 @@ exports.ProjectTypes = {
|
|
|
18
18
|
exports.LoadingPageTestMatch = "loading-page.spec.js";
|
|
19
19
|
exports.ResponsiveLayoutTestMatch = "responsive-layout.spec.js";
|
|
20
20
|
exports.FormsTestMatch = "forms.spec.js";
|
|
21
|
+
exports.VisualRegressionTestMatch = "visual-regression.spec.js";
|
package/dist/schema/index.d.ts
CHANGED
|
@@ -93,4 +93,10 @@ export declare const ManufacTestConfigSchema: z.ZodObject<{
|
|
|
93
93
|
}, z.core.$strip>>>>;
|
|
94
94
|
}, z.core.$strip>;
|
|
95
95
|
export type ManufacTestConfig = z.infer<typeof ManufacTestConfigSchema>;
|
|
96
|
+
export declare const StorybookIndexSchema: z.ZodObject<{
|
|
97
|
+
entries: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
98
|
+
type: z.ZodString;
|
|
99
|
+
id: z.ZodString;
|
|
100
|
+
}, z.core.$strip>>;
|
|
101
|
+
}, z.core.$strip>;
|
|
96
102
|
export {};
|
package/dist/schema/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ManufacTestConfigSchema = exports.ManufacTestFormsSchema = exports.FieldInteractions = void 0;
|
|
3
|
+
exports.StorybookIndexSchema = exports.ManufacTestConfigSchema = exports.ManufacTestFormsSchema = exports.FieldInteractions = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
/** Interaction kind for filling a form field. */
|
|
6
6
|
exports.FieldInteractions = {
|
|
@@ -38,3 +38,9 @@ exports.ManufacTestConfigSchema = zod_1.z.object({
|
|
|
38
38
|
pages: zod_1.z.array(zod_1.z.string()).min(1, "At least one page is required"),
|
|
39
39
|
forms: zod_1.z.array(FormSchema).nullish(),
|
|
40
40
|
});
|
|
41
|
+
exports.StorybookIndexSchema = zod_1.z.object({
|
|
42
|
+
entries: zod_1.z.record(zod_1.z.string(), zod_1.z.object({
|
|
43
|
+
type: zod_1.z.string(),
|
|
44
|
+
id: zod_1.z.string(),
|
|
45
|
+
})),
|
|
46
|
+
});
|
package/dist/specs/utils.js
CHANGED
|
@@ -910,6 +910,9 @@ async function fillField({ field, locator, page }) {
|
|
|
910
910
|
*/
|
|
911
911
|
async function hasNativeValidationError(fieldLocator) {
|
|
912
912
|
return await fieldLocator.evaluate((element) => {
|
|
913
|
-
return element instanceof HTMLInputElement
|
|
913
|
+
return ((element instanceof HTMLInputElement ||
|
|
914
|
+
element instanceof HTMLTextAreaElement ||
|
|
915
|
+
element instanceof HTMLSelectElement) &&
|
|
916
|
+
!element.checkValidity());
|
|
914
917
|
});
|
|
915
918
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const schema_1 = require("../schema");
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
(0, test_1.test)("storybook stories match their baseline screenshots", async ({ page }) => {
|
|
6
|
+
// index.json is generated at runtime by the Storybook dev server (resolved against
|
|
7
|
+
// baseURL in the consuming app's playwright.storybook.config.ts), not a file in the repo.
|
|
8
|
+
const response = await page.request.get("index.json");
|
|
9
|
+
const result = schema_1.StorybookIndexSchema.safeParse(await response.json());
|
|
10
|
+
if (!result.success) {
|
|
11
|
+
throw new Error(`Failed to parse Storybook index: ${result.error.message}`);
|
|
12
|
+
}
|
|
13
|
+
const storyIDs = Object.values(result.data.entries)
|
|
14
|
+
.filter((entry) => {
|
|
15
|
+
return entry.type === "story";
|
|
16
|
+
})
|
|
17
|
+
.map((entry) => {
|
|
18
|
+
return entry.id;
|
|
19
|
+
});
|
|
20
|
+
const errors = [];
|
|
21
|
+
for (const storyID of storyIDs) {
|
|
22
|
+
await test_1.test.step(storyID, async () => {
|
|
23
|
+
try {
|
|
24
|
+
await page.goto(`iframe.html?id=${storyID}&viewMode=story`);
|
|
25
|
+
await (0, test_1.expect)(page.locator("#storybook-root")).toBeVisible();
|
|
26
|
+
await (0, test_1.expect)(page.locator(".sb-preparing-story .sb-loader")).toBeHidden(); // Avoid screenshotting a half-rendered story
|
|
27
|
+
await (0, test_1.expect)(page).toHaveScreenshot(`${storyID}.png`, { fullPage: true, timeout: 10_000 });
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
// Collect failures instead of stopping at the first broken story.
|
|
31
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32
|
+
errors.push(`Story ${storyID} failed: ${message}`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
(0, test_1.expect)(errors, `Visual regression failures:\n${errors.join("\n")}`).toHaveLength(0);
|
|
37
|
+
});
|