@sit-onyx/playwright-utils 0.1.0-beta.0 → 1.0.0-beta.2
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/dist/index.cjs +2 -0
- package/dist/index.js +123 -0
- package/dist/screenshots/ScreenshotMatrix.d.ts +23 -0
- package/dist/screenshots/index.d.ts +19 -0
- package/dist/screenshots/types.d.ts +58 -0
- package/{src/screenshots/utils.ts → dist/screenshots/utils.d.ts} +1 -3
- package/package.json +15 -4
- package/src/screenshots/ScreenshotMatrix.vue +0 -121
- package/src/screenshots/index.tsx +0 -141
- package/src/screenshots/types.ts +0 -84
- /package/{src/index.ts → dist/index.d.ts} +0 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("playwright/jsx-runtime"),x=require("@playwright/experimental-ct-vue"),l=e=>e.replace(/\W/g,"-"),L=e=>{const o=()=>{const t=[`"blank ${e.columns.map(m=>`column-${l(m)}`).join(" ")}"`];return e.rows.forEach(m=>{t.push(`"row-${l(m)} ${e.columns.map(s=>`${l(m)}-${l(s)}`).join(" ")}"`)}),t.join(`
|
|
2
|
+
`)};return c.jsxs("div",{style:{width:"max-content",fontFamily:"Arial, sans-serif"},children:[c.jsxs("div",{style:{marginBottom:"2rem"},children:[c.jsxs("h1",{style:{fontSize:"1.25rem",lineHeight:"1.75rem",margin:"0"},children:["Screenshot test: ",e.name]}),c.jsxs("div",{children:["Browser: ",e.browserName]})]}),c.jsxs("div",{style:{display:"grid",gap:"2rem",gridTemplateRows:"auto",width:"max-content",alignItems:"center",justifyContent:"center",gridTemplateColumns:`auto repeat(${e.columns.length}, 1fr)`,gridTemplateAreas:o()},children:[c.jsx("div",{style:{gridArea:"blank"}}),e.children]})]})},z=({defaults:e})=>({executeMatrixScreenshotTest:async t=>{x.test(`${t.name}`,async({mount:m,page:s,browserName:P,context:b})=>{x.test.setTimeout(t.columns.length*t.rows.length*25e3);const M=async(n,r,i)=>{var u,S,w,v,g,T,y,A;await s.getByRole("document").focus(),await s.getByRole("document").hover({position:{x:0,y:0},force:!0}),await s.mouse.up();const a=await m(n);await((S=(u=e==null?void 0:e.hooks)==null?void 0:u.beforeEach)==null?void 0:S.call(u,a,s,r,i,t.context)),await((v=(w=t.hooks)==null?void 0:w.beforeEach)==null?void 0:v.call(w,a,s,r,i,t.context));const h=await a.screenshot({animations:"disabled"}),d=await a.boundingBox(),H=`${l(i)}-${l(r)}`;return await((T=(g=e==null?void 0:e.hooks)==null?void 0:g.afterEach)==null?void 0:T.call(g,a,s,r,i,t.context)),await((A=(y=t.hooks)==null?void 0:y.afterEach)==null?void 0:A.call(y,a,s,r,i,t.context)),{box:d,id:H,screenshot:h}},$=new Map;for(const n of t.rows)for(const r of t.columns){const i=t.component(r,n),a=t.removePadding??(e==null?void 0:e.removePadding),d=await M(c.jsx("div",{style:{display:"grid",width:"max-content",padding:a?void 0:"1rem"},children:i}),r,n);$.set(d.id,d)}const j="/_playwright-matrix-screenshot";await b.route(`${j}*`,(n,r)=>{var h;const a=new URL(r.url()).searchParams.get("id")??"";return n.fulfill({status:200,contentType:"image/png",body:(h=$.get(a))==null?void 0:h.screenshot})});const R=Array.from($.values()).map(({box:n,id:r})=>c.jsx("img",{width:n==null?void 0:n.width,height:n==null?void 0:n.height,style:{gridArea:r},src:`${j}?id=${r}`,alt:r})),f=t.rows.map(n=>E({name:n,type:"row"})),k=t.columns.map(n=>E({name:n,type:"column"})),B=L({columns:t.columns,rows:t.rows,name:t.name,browserName:P,children:[...R,...f,...k]}),C=await m(B);await x.expect(C).toHaveScreenshot(`${t.name}.png`),await s.unroute(`${j}*`)})}}),G=async e=>{await x.expect(e).toBeVisible(),await e.evaluate(o=>{o.style.height=`${o.scrollHeight}px`,o.style.width=`${o.scrollWidth}px`})},E=e=>c.jsx("div",{style:{textAlign:"center",gridArea:`${e.type}-${l(e.name)}`},children:e.name});exports.adjustSizeToAbsolutePosition=G;exports.useMatrixScreenshotTest=z;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsxs as l, jsx as $ } from "playwright/jsx-runtime";
|
|
2
|
+
import { expect as P, test as j } from "@playwright/experimental-ct-vue";
|
|
3
|
+
const m = (e) => e.replace(/\W/g, "-"), N = (e) => {
|
|
4
|
+
const i = () => {
|
|
5
|
+
const t = [
|
|
6
|
+
`"blank ${e.columns.map((o) => `column-${m(o)}`).join(" ")}"`
|
|
7
|
+
];
|
|
8
|
+
return e.rows.forEach((o) => {
|
|
9
|
+
t.push(
|
|
10
|
+
`"row-${m(o)} ${e.columns.map((a) => `${m(o)}-${m(a)}`).join(" ")}"`
|
|
11
|
+
);
|
|
12
|
+
}), t.join(`
|
|
13
|
+
`);
|
|
14
|
+
};
|
|
15
|
+
return /* @__PURE__ */ l("div", { style: { width: "max-content", fontFamily: "Arial, sans-serif" }, children: [
|
|
16
|
+
/* @__PURE__ */ l("div", { style: { marginBottom: "2rem" }, children: [
|
|
17
|
+
/* @__PURE__ */ l("h1", { style: { fontSize: "1.25rem", lineHeight: "1.75rem", margin: "0" }, children: [
|
|
18
|
+
"Screenshot test: ",
|
|
19
|
+
e.name
|
|
20
|
+
] }),
|
|
21
|
+
/* @__PURE__ */ l("div", { children: [
|
|
22
|
+
"Browser: ",
|
|
23
|
+
e.browserName
|
|
24
|
+
] })
|
|
25
|
+
] }),
|
|
26
|
+
/* @__PURE__ */ l(
|
|
27
|
+
"div",
|
|
28
|
+
{
|
|
29
|
+
style: {
|
|
30
|
+
display: "grid",
|
|
31
|
+
gap: "2rem",
|
|
32
|
+
gridTemplateRows: "auto",
|
|
33
|
+
width: "max-content",
|
|
34
|
+
alignItems: "center",
|
|
35
|
+
justifyContent: "center",
|
|
36
|
+
gridTemplateColumns: `auto repeat(${e.columns.length}, 1fr)`,
|
|
37
|
+
gridTemplateAreas: i()
|
|
38
|
+
},
|
|
39
|
+
children: [
|
|
40
|
+
/* @__PURE__ */ $("div", { style: { gridArea: "blank" } }),
|
|
41
|
+
e.children
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
] });
|
|
46
|
+
}, U = ({
|
|
47
|
+
defaults: e
|
|
48
|
+
}) => ({
|
|
49
|
+
/**
|
|
50
|
+
* Creates a single matrix screenshot that includes the screenshots for every column-row combination.
|
|
51
|
+
*/
|
|
52
|
+
executeMatrixScreenshotTest: async (t) => {
|
|
53
|
+
j(`${t.name}`, async ({ mount: o, page: a, browserName: k, context: B }) => {
|
|
54
|
+
j.setTimeout(t.columns.length * t.rows.length * 25e3);
|
|
55
|
+
const M = async (n, r, s) => {
|
|
56
|
+
var w, S, g, T, u, E, y, A;
|
|
57
|
+
await a.getByRole("document").focus(), await a.getByRole("document").hover({ position: { x: 0, y: 0 }, force: !0 }), await a.mouse.up();
|
|
58
|
+
const c = await o(n);
|
|
59
|
+
await ((S = (w = e == null ? void 0 : e.hooks) == null ? void 0 : w.beforeEach) == null ? void 0 : S.call(w, c, a, r, s, t.context)), await ((T = (g = t.hooks) == null ? void 0 : g.beforeEach) == null ? void 0 : T.call(g, c, a, r, s, t.context));
|
|
60
|
+
const h = await c.screenshot({ animations: "disabled" }), d = await c.boundingBox(), G = `${m(s)}-${m(r)}`;
|
|
61
|
+
return await ((E = (u = e == null ? void 0 : e.hooks) == null ? void 0 : u.afterEach) == null ? void 0 : E.call(u, c, a, r, s, t.context)), await ((A = (y = t.hooks) == null ? void 0 : y.afterEach) == null ? void 0 : A.call(y, c, a, r, s, t.context)), { box: d, id: G, screenshot: h };
|
|
62
|
+
}, x = /* @__PURE__ */ new Map();
|
|
63
|
+
for (const n of t.rows)
|
|
64
|
+
for (const r of t.columns) {
|
|
65
|
+
const s = t.component(r, n), c = t.removePadding ?? (e == null ? void 0 : e.removePadding), d = await M(/* @__PURE__ */ $(
|
|
66
|
+
"div",
|
|
67
|
+
{
|
|
68
|
+
style: {
|
|
69
|
+
display: "grid",
|
|
70
|
+
width: "max-content",
|
|
71
|
+
padding: c ? void 0 : "1rem"
|
|
72
|
+
},
|
|
73
|
+
children: s
|
|
74
|
+
}
|
|
75
|
+
), r, n);
|
|
76
|
+
x.set(d.id, d);
|
|
77
|
+
}
|
|
78
|
+
const v = "/_playwright-matrix-screenshot";
|
|
79
|
+
await B.route(`${v}*`, (n, r) => {
|
|
80
|
+
var h;
|
|
81
|
+
const c = new URL(r.url()).searchParams.get("id") ?? "";
|
|
82
|
+
return n.fulfill({
|
|
83
|
+
status: 200,
|
|
84
|
+
contentType: "image/png",
|
|
85
|
+
body: (h = x.get(c)) == null ? void 0 : h.screenshot
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
const R = Array.from(x.values()).map(({ box: n, id: r }) => /* @__PURE__ */ $(
|
|
89
|
+
"img",
|
|
90
|
+
{
|
|
91
|
+
width: n == null ? void 0 : n.width,
|
|
92
|
+
height: n == null ? void 0 : n.height,
|
|
93
|
+
style: { gridArea: r },
|
|
94
|
+
src: `${v}?id=${r}`,
|
|
95
|
+
alt: r
|
|
96
|
+
}
|
|
97
|
+
)), b = t.rows.map((n) => f({ name: n, type: "row" })), H = t.columns.map(
|
|
98
|
+
(n) => f({ name: n, type: "column" })
|
|
99
|
+
), L = N({
|
|
100
|
+
columns: t.columns,
|
|
101
|
+
rows: t.rows,
|
|
102
|
+
name: t.name,
|
|
103
|
+
browserName: k,
|
|
104
|
+
children: [...R, ...b, ...H]
|
|
105
|
+
}), C = await o(L);
|
|
106
|
+
await P(C).toHaveScreenshot(`${t.name}.png`), await a.unroute(`${v}*`);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}), W = async (e) => {
|
|
110
|
+
await P(e).toBeVisible(), await e.evaluate((i) => {
|
|
111
|
+
i.style.height = `${i.scrollHeight}px`, i.style.width = `${i.scrollWidth}px`;
|
|
112
|
+
});
|
|
113
|
+
}, f = (e) => /* @__PURE__ */ $(
|
|
114
|
+
"div",
|
|
115
|
+
{
|
|
116
|
+
style: { textAlign: "center", gridArea: `${e.type}-${m(e.name)}` },
|
|
117
|
+
children: e.name
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
export {
|
|
121
|
+
W as adjustSizeToAbsolutePosition,
|
|
122
|
+
U as useMatrixScreenshotTest
|
|
123
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type ScreenshotMatrixProps = {
|
|
2
|
+
/**
|
|
3
|
+
* Test name. Will be displayed above the matrix screenshot and be used as filename.
|
|
4
|
+
*/
|
|
5
|
+
name: string;
|
|
6
|
+
/**
|
|
7
|
+
* Matrix columns. Must not contain spaces.
|
|
8
|
+
*/
|
|
9
|
+
columns: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* Matrix rows. Must not contain spaces.
|
|
12
|
+
*/
|
|
13
|
+
rows: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* Current Playwright browser name.
|
|
16
|
+
*/
|
|
17
|
+
browserName: string;
|
|
18
|
+
/**
|
|
19
|
+
* Matrix children. Must contain column/row labels and screenshots for each column-row combination.
|
|
20
|
+
*/
|
|
21
|
+
children: unknown[];
|
|
22
|
+
};
|
|
23
|
+
export declare const ScreenshotMatrix: (props: ScreenshotMatrixProps) => import("vue/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Locator } from "@playwright/test";
|
|
2
|
+
import type { HookContext, MatrixScreenshotTestOptions, UseMatrixScreenshotTestOptions } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a screenshot utility that can be used to capture matrix screenshots.
|
|
5
|
+
* Useful for capturing a single screenshot/image that contains multiple variants of a component.
|
|
6
|
+
*/
|
|
7
|
+
export declare const useMatrixScreenshotTest: <TContext extends HookContext = HookContext>({ defaults, }: UseMatrixScreenshotTestOptions<TContext>) => {
|
|
8
|
+
/**
|
|
9
|
+
* Creates a single matrix screenshot that includes the screenshots for every column-row combination.
|
|
10
|
+
*/
|
|
11
|
+
executeMatrixScreenshotTest: <TColumn extends string, TRow extends string>(options: MatrixScreenshotTestOptions<TColumn, TRow, TContext>) => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Sets the component size to fit all absolute positioned content so it is fully included in screenshots.
|
|
15
|
+
* Useful if component includes flyouts etc. that use CSS `position: absolute`.
|
|
16
|
+
*
|
|
17
|
+
* Will wait for the component to be visible.
|
|
18
|
+
*/
|
|
19
|
+
export declare const adjustSizeToAbsolutePosition: (component: Locator) => Promise<void>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { MountResultJsx, test } from "@playwright/experimental-ct-vue";
|
|
2
|
+
import type { JSX } from "vue/jsx-runtime";
|
|
3
|
+
export type UseMatrixScreenshotTestOptions<TContext extends HookContext = HookContext> = {
|
|
4
|
+
/**
|
|
5
|
+
* Global default options for the matrix screenshot tests.
|
|
6
|
+
* Will be merged with the options passed to a single screenshot test.
|
|
7
|
+
*/
|
|
8
|
+
defaults?: Partial<Pick<MatrixScreenshotTestOptions<string, string, TContext>, "removePadding" | "hooks">>;
|
|
9
|
+
};
|
|
10
|
+
export type MatrixScreenshotTestOptions<TColumn extends string = string, TRow extends string = string, TContext extends HookContext = HookContext> = {
|
|
11
|
+
/**
|
|
12
|
+
* Test name. Will be displayed above the matrix screenshot and be used as filename.
|
|
13
|
+
*/
|
|
14
|
+
name: string;
|
|
15
|
+
/**
|
|
16
|
+
* Matrix columns.
|
|
17
|
+
*/
|
|
18
|
+
columns: readonly TColumn[];
|
|
19
|
+
/**
|
|
20
|
+
* Matrix rows.
|
|
21
|
+
*/
|
|
22
|
+
rows: readonly TRow[];
|
|
23
|
+
/**
|
|
24
|
+
* Function that returns the component for the given column and row.
|
|
25
|
+
*/
|
|
26
|
+
component: (column: TColumn, row: TRow) => JSX.Element;
|
|
27
|
+
/**
|
|
28
|
+
* Custom hooks/callbacks that can be executed at specific points in time during the matrix screenshot.
|
|
29
|
+
*/
|
|
30
|
+
hooks?: ScreenshotTestHooks<TColumn, TRow, TContext>;
|
|
31
|
+
/**
|
|
32
|
+
* If `true`, no padding will be added to the screenshots.
|
|
33
|
+
* By default a `1rem` padding is added around the component/screenshot.
|
|
34
|
+
*/
|
|
35
|
+
removePadding?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Context that is passed to all `hooks`.
|
|
38
|
+
* Useful for passing options to (global) hooks per matrix screenshot, e.g. for disabling specific accessibility test rule.
|
|
39
|
+
*/
|
|
40
|
+
context?: TContext;
|
|
41
|
+
};
|
|
42
|
+
export type ScreenshotTestHooks<TColumn extends string, TRow extends string, TContext extends HookContext = HookContext> = Partial<{
|
|
43
|
+
/**
|
|
44
|
+
* Optional callback to be executed before capturing each individual screenshot (column + row combination).
|
|
45
|
+
* Useful for performing `expect()` or e.g. hover, focus-visible state etc.
|
|
46
|
+
* Focus and mouse will be reset after each screenshot.
|
|
47
|
+
*/
|
|
48
|
+
beforeEach: ScreenshotTestHook<TColumn, TRow, TContext>;
|
|
49
|
+
/**
|
|
50
|
+
* Optional callback to be executed after capturing each individual screenshot (column + row combination).
|
|
51
|
+
* Useful for performing clean ups of side effects created by the `beforeEach` hook.
|
|
52
|
+
* Focus and mouse will be reset after each screenshot.
|
|
53
|
+
*/
|
|
54
|
+
afterEach: ScreenshotTestHook<TColumn, TRow, TContext>;
|
|
55
|
+
}>;
|
|
56
|
+
export type ScreenshotTestHook<TColumn extends string, TRow extends string, TContext extends HookContext = HookContext> = (component: MountResultJsx, page: TestArgs["page"], column: TColumn, row: TRow, context?: TContext) => Promise<void>;
|
|
57
|
+
export type TestArgs = Parameters<Parameters<typeof test>[2]>[0];
|
|
58
|
+
export type HookContext = Record<PropertyKey, unknown>;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Escapes the given string so its save to be used as CSS grid-area, e.g. by replacing whitespaces.
|
|
3
3
|
*/
|
|
4
|
-
export const escapeGridAreaName
|
|
5
|
-
return name.replace(/\W/g, "-");
|
|
6
|
-
};
|
|
4
|
+
export declare const escapeGridAreaName: (name: string) => string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sit-onyx/playwright-utils",
|
|
3
3
|
"description": "Utilities for Vue component testing with Playwright",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "1.0.0-beta.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -13,17 +13,28 @@
|
|
|
13
13
|
"url": "https://github.com/schwarzit/onyx/issues"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
|
-
"
|
|
16
|
+
"dist"
|
|
17
17
|
],
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
18
19
|
"exports": {
|
|
19
|
-
".":
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"require": "./dist/index.cjs"
|
|
24
|
+
}
|
|
20
25
|
},
|
|
21
26
|
"peerDependencies": {
|
|
22
27
|
"@playwright/experimental-ct-vue": ">= 1",
|
|
23
28
|
"@playwright/test": ">= 1",
|
|
29
|
+
"playwright": ">= 1",
|
|
24
30
|
"vue": ">= 3"
|
|
25
31
|
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"typescript": "5.6.3",
|
|
34
|
+
"vite": "5.4.11",
|
|
35
|
+
"@sit-onyx/shared": "0.0.1-beta.0"
|
|
36
|
+
},
|
|
26
37
|
"scripts": {
|
|
27
|
-
"build": "vue-tsc --
|
|
38
|
+
"build": "vite build && vue-tsc --emitDeclarationOnly"
|
|
28
39
|
}
|
|
29
40
|
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import { escapeGridAreaName } from "./utils";
|
|
4
|
-
|
|
5
|
-
defineSlots<{
|
|
6
|
-
default: () => unknown;
|
|
7
|
-
}>();
|
|
8
|
-
|
|
9
|
-
const props = defineProps<{
|
|
10
|
-
/**
|
|
11
|
-
* Test name. Will be displayed above the matrix screenshot and be used as filename.
|
|
12
|
-
*/
|
|
13
|
-
name: string;
|
|
14
|
-
/**
|
|
15
|
-
* Matrix columns. Must not contain spaces.
|
|
16
|
-
*/
|
|
17
|
-
columns: readonly string[];
|
|
18
|
-
/**
|
|
19
|
-
* Matrix rows. Must not contain spaces.
|
|
20
|
-
*/
|
|
21
|
-
rows: readonly string[];
|
|
22
|
-
/**
|
|
23
|
-
* Current Playwright browser name.
|
|
24
|
-
*/
|
|
25
|
-
browserName: string;
|
|
26
|
-
}>();
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* CSS "grid-template-areas" for the current columns and rows.
|
|
30
|
-
* Every grid element must have the "grid-area" set to `{row}-{column}` to place it correctly.
|
|
31
|
-
*/
|
|
32
|
-
const gridTemplateAreas = computed(() => {
|
|
33
|
-
const lines: string[] = [
|
|
34
|
-
`"blank ${props.columns.map((col) => `column-${escapeGridAreaName(col)}`).join(" ")}"`,
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
props.rows.forEach((row) => {
|
|
38
|
-
lines.push(
|
|
39
|
-
`"row-${escapeGridAreaName(row)} ${props.columns.map((col) => `${escapeGridAreaName(row)}-${escapeGridAreaName(col)}`).join(" ")}"`,
|
|
40
|
-
);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
return lines.join("\n");
|
|
44
|
-
});
|
|
45
|
-
</script>
|
|
46
|
-
|
|
47
|
-
<template>
|
|
48
|
-
<div class="wrapper">
|
|
49
|
-
<div class="meta">
|
|
50
|
-
<h1 class="meta__name">Screenshot test: {{ props.name }}</h1>
|
|
51
|
-
<div>Browser: {{ props.browserName }}</div>
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<div
|
|
55
|
-
class="grid"
|
|
56
|
-
:style="{
|
|
57
|
-
gridTemplateColumns: `auto repeat(${props.columns.length}, 1fr)`,
|
|
58
|
-
gridTemplateAreas,
|
|
59
|
-
}"
|
|
60
|
-
>
|
|
61
|
-
<div
|
|
62
|
-
v-for="column of props.columns"
|
|
63
|
-
:key="column"
|
|
64
|
-
class="grid__label"
|
|
65
|
-
:style="{ gridArea: `column-${escapeGridAreaName(column)}` }"
|
|
66
|
-
>
|
|
67
|
-
{{ column }}
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
<div
|
|
71
|
-
v-for="row of props.rows"
|
|
72
|
-
:key="row"
|
|
73
|
-
class="grid__label"
|
|
74
|
-
:style="{ gridArea: `row-${escapeGridAreaName(row)}` }"
|
|
75
|
-
>
|
|
76
|
-
{{ row }}
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
<!-- blank / placeholder element for the first column/row to align the matrix correctly -->
|
|
80
|
-
<div class="grid__blank"></div>
|
|
81
|
-
|
|
82
|
-
<slot></slot>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
</template>
|
|
86
|
-
|
|
87
|
-
<style scoped>
|
|
88
|
-
.wrapper {
|
|
89
|
-
width: max-content;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
.meta {
|
|
93
|
-
font-family: Arial, sans-serif;
|
|
94
|
-
margin-bottom: 2rem;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
.meta__name {
|
|
98
|
-
font-size: 1.25rem;
|
|
99
|
-
line-height: 1.75rem;
|
|
100
|
-
margin: 0;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.grid {
|
|
104
|
-
font-family: Arial, sans-serif;
|
|
105
|
-
display: grid;
|
|
106
|
-
gap: 2rem;
|
|
107
|
-
grid-template-rows: auto;
|
|
108
|
-
width: max-content;
|
|
109
|
-
|
|
110
|
-
align-items: center;
|
|
111
|
-
justify-content: center;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.grid__label {
|
|
115
|
-
text-align: center;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.grid__blank {
|
|
119
|
-
grid-area: blank;
|
|
120
|
-
}
|
|
121
|
-
</style>
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "@playwright/experimental-ct-vue";
|
|
2
|
-
import type { Locator } from "@playwright/test";
|
|
3
|
-
import type { JSX } from "vue/jsx-runtime";
|
|
4
|
-
import ScreenshotMatrix from "./ScreenshotMatrix.vue";
|
|
5
|
-
import type {
|
|
6
|
-
HookContext,
|
|
7
|
-
MatrixScreenshotTestOptions,
|
|
8
|
-
UseMatrixScreenshotTestOptions,
|
|
9
|
-
} from "./types";
|
|
10
|
-
import { escapeGridAreaName } from "./utils";
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Creates a screenshot utility that can be used to capture matrix screenshots.
|
|
14
|
-
* Useful for capturing a single screenshot/image that contains multiple variants of a component.
|
|
15
|
-
*/
|
|
16
|
-
export const useMatrixScreenshotTest = <TContext extends HookContext = HookContext>({
|
|
17
|
-
defaults,
|
|
18
|
-
}: UseMatrixScreenshotTestOptions<TContext>) => {
|
|
19
|
-
const executeMatrixScreenshotTest = async <TColumn extends string, TRow extends string>(
|
|
20
|
-
options: MatrixScreenshotTestOptions<TColumn, TRow, TContext>,
|
|
21
|
-
) => {
|
|
22
|
-
test(`${options.name}`, async ({ mount, page, browserName, context }) => {
|
|
23
|
-
// limit the max timeout per permutation
|
|
24
|
-
const timeoutPerScreenshot = 25 * 1000;
|
|
25
|
-
test.setTimeout(options.columns.length * options.rows.length * timeoutPerScreenshot);
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Mounts the given element, captures a screenshot and returns and HTML `<img />` containing the captured screenshot.
|
|
29
|
-
*/
|
|
30
|
-
const getScreenshot = async (element: JSX.Element, column: TColumn, row: TRow) => {
|
|
31
|
-
await page.getByRole("document").focus(); // reset focus
|
|
32
|
-
await page.getByRole("document").hover({ position: { x: 0, y: 0 } }); // reset mouse
|
|
33
|
-
await page.mouse.up(); // reset mouse
|
|
34
|
-
|
|
35
|
-
const component = await mount(element);
|
|
36
|
-
|
|
37
|
-
// BEFORE hook
|
|
38
|
-
await defaults?.hooks?.beforeEach?.(component, page, column, row, options.context);
|
|
39
|
-
await options.hooks?.beforeEach?.(component, page, column, row, options.context);
|
|
40
|
-
|
|
41
|
-
const screenshot = await component.screenshot({ animations: "disabled" });
|
|
42
|
-
|
|
43
|
-
// some browser (e.g. safari) have different device resolutions which would cause the screenshot
|
|
44
|
-
// to be twice as large (or more) so we need to get the actual size here to set the correct image size below
|
|
45
|
-
// see (`scale` option of `component.screenshot()` above)
|
|
46
|
-
const box = await component.boundingBox();
|
|
47
|
-
const id = `${escapeGridAreaName(row)}-${escapeGridAreaName(column)}`;
|
|
48
|
-
|
|
49
|
-
// AFTER hook
|
|
50
|
-
await defaults?.hooks?.afterEach?.(component, page, column, row, options.context);
|
|
51
|
-
await options.hooks?.afterEach?.(component, page, column, row, options.context);
|
|
52
|
-
|
|
53
|
-
return { box, id, screenshot };
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const screenshotMap = new Map<string, Awaited<ReturnType<typeof getScreenshot>>>();
|
|
57
|
-
|
|
58
|
-
for (const row of options.rows) {
|
|
59
|
-
for (const column of options.columns) {
|
|
60
|
-
const jsxElement = options.component(column, row);
|
|
61
|
-
const removePadding = options.removePadding ?? defaults?.removePadding;
|
|
62
|
-
|
|
63
|
-
const wrappedElement = (
|
|
64
|
-
<div
|
|
65
|
-
style={{
|
|
66
|
-
display: "grid",
|
|
67
|
-
width: "max-content",
|
|
68
|
-
padding: removePadding ? undefined : "1rem",
|
|
69
|
-
}}
|
|
70
|
-
>
|
|
71
|
-
{jsxElement}
|
|
72
|
-
</div>
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
const data = await getScreenshot(wrappedElement, column, row);
|
|
76
|
-
screenshotMap.set(data.id, data);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const SCREENSHOT_ROUTE = "/_playwright-matrix-screenshot";
|
|
81
|
-
|
|
82
|
-
await context.route(`${SCREENSHOT_ROUTE}*`, (route, request) => {
|
|
83
|
-
const url = new URL(request.url());
|
|
84
|
-
const wantedId = url.searchParams.get("id") ?? "";
|
|
85
|
-
|
|
86
|
-
return route.fulfill({
|
|
87
|
-
status: 200,
|
|
88
|
-
contentType: "image/png",
|
|
89
|
-
body: screenshotMap.get(wantedId)?.screenshot,
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
const screenshots = Array.from(screenshotMap.values()).map(({ box, id }) => (
|
|
94
|
-
<img
|
|
95
|
-
width={box?.width}
|
|
96
|
-
height={box?.height}
|
|
97
|
-
style={{ gridArea: id }}
|
|
98
|
-
src={`${SCREENSHOT_ROUTE}?id=${id}`}
|
|
99
|
-
alt={id}
|
|
100
|
-
/>
|
|
101
|
-
));
|
|
102
|
-
|
|
103
|
-
const component = await mount(
|
|
104
|
-
<ScreenshotMatrix
|
|
105
|
-
columns={options.columns}
|
|
106
|
-
rows={options.rows}
|
|
107
|
-
name={options.name}
|
|
108
|
-
browserName={browserName}
|
|
109
|
-
>
|
|
110
|
-
{screenshots}
|
|
111
|
-
</ScreenshotMatrix>,
|
|
112
|
-
);
|
|
113
|
-
|
|
114
|
-
await expect(component).toHaveScreenshot(`${options.name}.png`);
|
|
115
|
-
|
|
116
|
-
await page.unroute(`${SCREENSHOT_ROUTE}*`);
|
|
117
|
-
});
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
return {
|
|
121
|
-
/**
|
|
122
|
-
* Creates a single matrix screenshot that includes the screenshots for every column-row combination.
|
|
123
|
-
*/
|
|
124
|
-
executeMatrixScreenshotTest,
|
|
125
|
-
};
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Sets the component size to fit all absolute positioned content so it is fully included in screenshots.
|
|
130
|
-
* Useful if component includes flyouts etc. that use CSS `position: absolute`.
|
|
131
|
-
*
|
|
132
|
-
* Will wait for the component to be visible.
|
|
133
|
-
*/
|
|
134
|
-
export const adjustSizeToAbsolutePosition = async (component: Locator) => {
|
|
135
|
-
await expect(component).toBeVisible();
|
|
136
|
-
|
|
137
|
-
await component.evaluate((element) => {
|
|
138
|
-
element.style.height = `${element.scrollHeight}px`;
|
|
139
|
-
element.style.width = `${element.scrollWidth}px`;
|
|
140
|
-
});
|
|
141
|
-
};
|
package/src/screenshots/types.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { MountResultJsx, test } from "@playwright/experimental-ct-vue";
|
|
2
|
-
import type { JSX } from "vue/jsx-runtime";
|
|
3
|
-
|
|
4
|
-
export type UseMatrixScreenshotTestOptions<TContext extends HookContext = HookContext> = {
|
|
5
|
-
/**
|
|
6
|
-
* Global default options for the matrix screenshot tests.
|
|
7
|
-
* Will be merged with the options passed to a single screenshot test.
|
|
8
|
-
*/
|
|
9
|
-
defaults?: Partial<
|
|
10
|
-
Pick<MatrixScreenshotTestOptions<string, string, TContext>, "removePadding" | "hooks">
|
|
11
|
-
>;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type MatrixScreenshotTestOptions<
|
|
15
|
-
TColumn extends string = string,
|
|
16
|
-
TRow extends string = string,
|
|
17
|
-
TContext extends HookContext = HookContext,
|
|
18
|
-
> = {
|
|
19
|
-
/**
|
|
20
|
-
* Test name. Will be displayed above the matrix screenshot and be used as filename.
|
|
21
|
-
*/
|
|
22
|
-
name: string;
|
|
23
|
-
/**
|
|
24
|
-
* Matrix columns.
|
|
25
|
-
*/
|
|
26
|
-
columns: readonly TColumn[];
|
|
27
|
-
/**
|
|
28
|
-
* Matrix rows.
|
|
29
|
-
*/
|
|
30
|
-
rows: readonly TRow[];
|
|
31
|
-
/**
|
|
32
|
-
* Function that returns the component for the given column and row.
|
|
33
|
-
*/
|
|
34
|
-
component: (column: TColumn, row: TRow) => JSX.Element;
|
|
35
|
-
/**
|
|
36
|
-
* Custom hooks/callbacks that can be executed at specific points in time during the matrix screenshot.
|
|
37
|
-
*/
|
|
38
|
-
hooks?: ScreenshotTestHooks<TColumn, TRow, TContext>;
|
|
39
|
-
/**
|
|
40
|
-
* If `true`, no padding will be added to the screenshots.
|
|
41
|
-
* By default a `1rem` padding is added around the component/screenshot.
|
|
42
|
-
*/
|
|
43
|
-
removePadding?: boolean;
|
|
44
|
-
/**
|
|
45
|
-
* Context that is passed to all `hooks`.
|
|
46
|
-
* Useful for passing options to (global) hooks per matrix screenshot, e.g. for disabling specific accessibility test rule.
|
|
47
|
-
*/
|
|
48
|
-
context?: TContext;
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
export type ScreenshotTestHooks<
|
|
52
|
-
TColumn extends string,
|
|
53
|
-
TRow extends string,
|
|
54
|
-
TContext extends HookContext = HookContext,
|
|
55
|
-
> = Partial<{
|
|
56
|
-
/**
|
|
57
|
-
* Optional callback to be executed before capturing each individual screenshot (column + row combination).
|
|
58
|
-
* Useful for performing `expect()` or e.g. hover, focus-visible state etc.
|
|
59
|
-
* Focus and mouse will be reset after each screenshot.
|
|
60
|
-
*/
|
|
61
|
-
beforeEach: ScreenshotTestHook<TColumn, TRow, TContext>;
|
|
62
|
-
/**
|
|
63
|
-
* Optional callback to be executed after capturing each individual screenshot (column + row combination).
|
|
64
|
-
* Useful for performing clean ups of side effects created by the `beforeEach` hook.
|
|
65
|
-
* Focus and mouse will be reset after each screenshot.
|
|
66
|
-
*/
|
|
67
|
-
afterEach: ScreenshotTestHook<TColumn, TRow, TContext>;
|
|
68
|
-
}>;
|
|
69
|
-
|
|
70
|
-
export type ScreenshotTestHook<
|
|
71
|
-
TColumn extends string,
|
|
72
|
-
TRow extends string,
|
|
73
|
-
TContext extends HookContext = HookContext,
|
|
74
|
-
> = (
|
|
75
|
-
component: MountResultJsx,
|
|
76
|
-
page: TestArgs["page"],
|
|
77
|
-
column: TColumn,
|
|
78
|
-
row: TRow,
|
|
79
|
-
context?: TContext,
|
|
80
|
-
) => Promise<void>;
|
|
81
|
-
|
|
82
|
-
export type TestArgs = Parameters<Parameters<typeof test>[2]>[0];
|
|
83
|
-
|
|
84
|
-
export type HookContext = Record<PropertyKey, unknown>;
|
|
File without changes
|