@futdevpro/dynamo-e2e 1.18.38 → 1.18.40
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/CHANGELOG.md +49 -0
- package/README.md +30 -0
- package/build/_modules/visual-gate/_collections/blocking-overlays.util.d.ts +63 -0
- package/build/_modules/visual-gate/_collections/blocking-overlays.util.d.ts.map +1 -0
- package/build/_modules/visual-gate/_collections/blocking-overlays.util.js +104 -0
- package/build/_modules/visual-gate/_collections/blocking-overlays.util.js.map +1 -0
- package/build/_modules/visual-gate/_collections/visual-gate-defaults.const.d.ts +23 -0
- package/build/_modules/visual-gate/_collections/visual-gate-defaults.const.d.ts.map +1 -0
- package/build/_modules/visual-gate/_collections/visual-gate-defaults.const.js +49 -0
- package/build/_modules/visual-gate/_collections/visual-gate-defaults.const.js.map +1 -0
- package/build/_modules/visual-gate/_collections/visual-gate-look.util.d.ts +51 -0
- package/build/_modules/visual-gate/_collections/visual-gate-look.util.d.ts.map +1 -0
- package/build/_modules/visual-gate/_collections/visual-gate-look.util.js +241 -0
- package/build/_modules/visual-gate/_collections/visual-gate-look.util.js.map +1 -0
- package/build/_modules/visual-gate/_collections/visual-gate.util.d.ts +84 -0
- package/build/_modules/visual-gate/_collections/visual-gate.util.d.ts.map +1 -0
- package/build/_modules/visual-gate/_collections/visual-gate.util.js +209 -0
- package/build/_modules/visual-gate/_collections/visual-gate.util.js.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-case.interface.d.ts +14 -0
- package/build/_modules/visual-gate/_models/visual-gate-case.interface.d.ts.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-case.interface.js +3 -0
- package/build/_modules/visual-gate/_models/visual-gate-case.interface.js.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-finding.interface.d.ts +16 -0
- package/build/_modules/visual-gate/_models/visual-gate-finding.interface.d.ts.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-finding.interface.js +3 -0
- package/build/_modules/visual-gate/_models/visual-gate-finding.interface.js.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-language.interface.d.ts +8 -0
- package/build/_modules/visual-gate/_models/visual-gate-language.interface.d.ts.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-language.interface.js +3 -0
- package/build/_modules/visual-gate/_models/visual-gate-language.interface.js.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-layout.interface.d.ts +14 -0
- package/build/_modules/visual-gate/_models/visual-gate-layout.interface.d.ts.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-layout.interface.js +3 -0
- package/build/_modules/visual-gate/_models/visual-gate-layout.interface.js.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-look-options.interface.d.ts +33 -0
- package/build/_modules/visual-gate/_models/visual-gate-look-options.interface.d.ts.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-look-options.interface.js +3 -0
- package/build/_modules/visual-gate/_models/visual-gate-look-options.interface.js.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-options.interface.d.ts +32 -0
- package/build/_modules/visual-gate/_models/visual-gate-options.interface.d.ts.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-options.interface.js +3 -0
- package/build/_modules/visual-gate/_models/visual-gate-options.interface.js.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-page.interface.d.ts +15 -0
- package/build/_modules/visual-gate/_models/visual-gate-page.interface.d.ts.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-page.interface.js +3 -0
- package/build/_modules/visual-gate/_models/visual-gate-page.interface.js.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-viewport.interface.d.ts +8 -0
- package/build/_modules/visual-gate/_models/visual-gate-viewport.interface.d.ts.map +1 -0
- package/build/_modules/visual-gate/_models/visual-gate-viewport.interface.js +3 -0
- package/build/_modules/visual-gate/_models/visual-gate-viewport.interface.js.map +1 -0
- package/build/_modules/visual-gate/index.d.ts +19 -0
- package/build/_modules/visual-gate/index.d.ts.map +1 -0
- package/build/_modules/visual-gate/index.js +35 -0
- package/build/_modules/visual-gate/index.js.map +1 -0
- package/build/index.js +1 -1
- package/package.json +10 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DyE2E_VisualGateLook_Util = void 0;
|
|
4
|
+
const visual_gate_defaults_const_1 = require("./visual-gate-defaults.const");
|
|
5
|
+
/**
|
|
6
|
+
* The look checks of the visual gate (BFR-FUTDEVPRO-081, owner 2026-11-02 21:43: "we are going backwards while we
|
|
7
|
+
* prepare for the release"): four defects reached the futdevpro TEST that no functional test noticed — blue / green
|
|
8
|
+
* frames instead of the neutral grey, a page "not found", tiles fallen apart, an admin list in one line.
|
|
9
|
+
*
|
|
10
|
+
* @description Ported from futdevpro's own gate (`e2e/tests/ui-check/visual-regression-gate-feature.spec.ts`, 20 red
|
|
11
|
+
* on TEST 839, green on the fixed build), parameterised: the content area, the "not found" screen, the shared
|
|
12
|
+
* components skipped and the loud hue range are options with futdevpro's values as defaults. Every check is a
|
|
13
|
+
* browser-side script (DOM APIs only), so it measures the rendered page, not the source.
|
|
14
|
+
*/
|
|
15
|
+
class DyE2E_VisualGateLook_Util {
|
|
16
|
+
/**
|
|
17
|
+
* ① the content area: `{ isNotFound, isContentMissing, contentHeight }`. Argument: `{ contentSelector,
|
|
18
|
+
* notFoundSelector }`.
|
|
19
|
+
*/
|
|
20
|
+
static contentScript = `(set) => {
|
|
21
|
+
const notFound = set.notFoundSelector ? document.querySelector(set.notFoundSelector) : null;
|
|
22
|
+
const content = document.querySelector(set.contentSelector);
|
|
23
|
+
return {
|
|
24
|
+
isNotFound: !!notFound && notFound.getBoundingClientRect().height > 0,
|
|
25
|
+
isContentMissing: !content,
|
|
26
|
+
contentHeight: content ? Math.round(content.getBoundingClientRect().height) : 0,
|
|
27
|
+
};
|
|
28
|
+
}`;
|
|
29
|
+
/**
|
|
30
|
+
* ③ the neutral look: the boxes of the content area with a saturated frame, background or gradient in the loud
|
|
31
|
+
* hue range. Argument: the resolved look options. Returns `<tag.class> frame|background|gradient <colour>` lines.
|
|
32
|
+
*/
|
|
33
|
+
static loudColorsScript = `(set) => {
|
|
34
|
+
const root = document.querySelector(set.contentSelector);
|
|
35
|
+
const canvas = document.createElement('canvas');
|
|
36
|
+
canvas.width = 1;
|
|
37
|
+
canvas.height = 1;
|
|
38
|
+
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
39
|
+
// rgb() / rgba() directly; any other colour space (Tailwind 4 reports oklch) by its rendered sRGB pixel
|
|
40
|
+
const toRgba = (color) => {
|
|
41
|
+
const match = color.match(/^rgba?\\(([^)]+)\\)$/);
|
|
42
|
+
if (match) {
|
|
43
|
+
const parts = match[1].split(/[ ,/]+/).filter(Boolean).map(Number);
|
|
44
|
+
return [ parts[0], parts[1], parts[2], parts.length > 3 ? parts[3] : 1 ];
|
|
45
|
+
}
|
|
46
|
+
if (!ctx) { return null; }
|
|
47
|
+
ctx.clearRect(0, 0, 1, 1);
|
|
48
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
|
|
49
|
+
ctx.fillStyle = color;
|
|
50
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
51
|
+
const pixel = ctx.getImageData(0, 0, 1, 1).data;
|
|
52
|
+
return [ pixel[0], pixel[1], pixel[2], pixel[3] / 255 ];
|
|
53
|
+
};
|
|
54
|
+
const parse = (color) => {
|
|
55
|
+
const rgba = toRgba(color);
|
|
56
|
+
if (!rgba) { return null; }
|
|
57
|
+
const r = rgba[0] / 255;
|
|
58
|
+
const g = rgba[1] / 255;
|
|
59
|
+
const b = rgba[2] / 255;
|
|
60
|
+
const a = rgba[3];
|
|
61
|
+
const max = Math.max(r, g, b);
|
|
62
|
+
const min = Math.min(r, g, b);
|
|
63
|
+
const l = (max + min) / 2;
|
|
64
|
+
const d = max - min;
|
|
65
|
+
const s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
|
|
66
|
+
let h = 0;
|
|
67
|
+
if (d !== 0) {
|
|
68
|
+
h = max === r ? 60 * (((g - b) / d) % 6) : max === g ? 60 * ((b - r) / d + 2) : 60 * ((r - g) / d + 4);
|
|
69
|
+
}
|
|
70
|
+
return { h: (h + 360) % 360, s: s, a: a };
|
|
71
|
+
};
|
|
72
|
+
const isLoud = (color) => {
|
|
73
|
+
const c = parse(color);
|
|
74
|
+
return !!c && c.a > set.loudMinAlpha && c.s > set.loudMinSaturation && c.h >= set.loudHueFrom
|
|
75
|
+
&& c.h <= set.loudHueTo;
|
|
76
|
+
};
|
|
77
|
+
const found = [];
|
|
78
|
+
if (!root) { return found; }
|
|
79
|
+
for (const el of [ root ].concat(Array.from(root.querySelectorAll('*')))) {
|
|
80
|
+
if (el.closest(set.neutralSkipSelector)) { continue; }
|
|
81
|
+
const style = getComputedStyle(el);
|
|
82
|
+
const rect = el.getBoundingClientRect();
|
|
83
|
+
if (rect.width < 4 || rect.height < 4 || style.visibility === 'hidden' || style.display === 'none') {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const cls = typeof el.className === 'string' ? el.className.trim().split(/\\s+/).slice(0, 2).join('.') : '';
|
|
87
|
+
const name = el.tagName.toLowerCase() + (cls ? '.' + cls : '');
|
|
88
|
+
if (parseFloat(style.borderTopWidth) > 0 && style.borderTopStyle !== 'none' && isLoud(style.borderTopColor)) {
|
|
89
|
+
found.push(name + ' frame ' + style.borderTopColor);
|
|
90
|
+
}
|
|
91
|
+
if (isLoud(style.backgroundColor)) {
|
|
92
|
+
found.push(name + ' background ' + style.backgroundColor);
|
|
93
|
+
}
|
|
94
|
+
const isGradientAllowed = !!set.gradientAllowSelector && !!el.closest(set.gradientAllowSelector);
|
|
95
|
+
if (style.backgroundImage && style.backgroundImage !== 'none' && !isGradientAllowed) {
|
|
96
|
+
const colors = style.backgroundImage.match(/(?:rgba?|hsla?|oklch|oklab|lab|lch|color)\\([^)]+\\)/g) || [];
|
|
97
|
+
const loud = colors.find((color) => isLoud(color));
|
|
98
|
+
if (loud) {
|
|
99
|
+
found.push(name + ' gradient ' + loud);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return Array.from(new Set(found)).slice(0, set.maxFindings);
|
|
104
|
+
}`;
|
|
105
|
+
/**
|
|
106
|
+
* ⑤ ⑥ ⑦ on the whole page: a visible text off the screen, an icon showing its ligature word, a placeholder text.
|
|
107
|
+
* Argument: `{ placeholderPattern, maxFindings }`. Returns `{ kind, message }` findings.
|
|
108
|
+
*/
|
|
109
|
+
static textProblemsScript = `(set) => {
|
|
110
|
+
const found = [];
|
|
111
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
112
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
113
|
+
const text = String(node.textContent).trim();
|
|
114
|
+
const parent = node.parentElement;
|
|
115
|
+
if (!text || !parent || parent.closest('mat-icon, [aria-hidden="true"], .cdk-visually-hidden')
|
|
116
|
+
|| !parent.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const range = document.createRange();
|
|
120
|
+
range.selectNodeContents(node);
|
|
121
|
+
// a honeypot field stands far off the screen on purpose (-10000 px)
|
|
122
|
+
const out = Array.from(range.getClientRects()).find((rect) => rect.width > 0 && rect.left > -5000
|
|
123
|
+
&& (rect.left < -1 || rect.right > window.innerWidth + 1));
|
|
124
|
+
if (out) {
|
|
125
|
+
found.push({ kind: 'text-off-screen', message: '"' + text.slice(0, 40) + '" <' + parent.tagName.toLowerCase()
|
|
126
|
+
+ '> [' + Math.round(out.left) + ' … ' + Math.round(out.right) + ' px] on a ' + window.innerWidth
|
|
127
|
+
+ ' px screen' });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const icon of Array.from(document.querySelectorAll('mat-icon'))) {
|
|
131
|
+
const word = String(icon.textContent).trim();
|
|
132
|
+
if (word.length < 3 || icon.querySelector('svg') || !icon.checkVisibility()) { continue; }
|
|
133
|
+
const range = document.createRange();
|
|
134
|
+
range.selectNodeContents(icon);
|
|
135
|
+
if (range.getBoundingClientRect().width > 2 * parseFloat(getComputedStyle(icon).fontSize)) {
|
|
136
|
+
found.push({ kind: 'raw-icon-word', message: 'the icon shows its word: "' + word + '"' });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (set.placeholderPattern) {
|
|
140
|
+
const pattern = new RegExp('[^\\\\n]{0,30}' + set.placeholderPattern + '[^\\\\n]{0,30}', 'gi');
|
|
141
|
+
for (const line of document.body.innerText.match(pattern) || []) {
|
|
142
|
+
found.push({ kind: 'placeholder-text', message: 'visible placeholder: "' + line.trim() + '"' });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const seen = new Set();
|
|
146
|
+
return found.filter((finding) => !seen.has(finding.message) && seen.add(finding.message))
|
|
147
|
+
.slice(0, set.maxFindings);
|
|
148
|
+
}`;
|
|
149
|
+
/** ④ the font sizes of the headings and paragraphs of the content area. Argument: `{ contentSelector, tags }`. */
|
|
150
|
+
static fontSizesScript = `(set) => {
|
|
151
|
+
const root = document.querySelector(set.contentSelector);
|
|
152
|
+
const out = {};
|
|
153
|
+
for (const tag of set.tags) {
|
|
154
|
+
const sizes = Array.from(root ? root.querySelectorAll(tag) : [])
|
|
155
|
+
.filter((el) => el.getBoundingClientRect().height > 0)
|
|
156
|
+
.map((el) => getComputedStyle(el).fontSize);
|
|
157
|
+
out[tag] = Array.from(new Set(sizes)).sort();
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}`;
|
|
161
|
+
/** The options with every default filled in. */
|
|
162
|
+
static resolve(options = {}) {
|
|
163
|
+
return {
|
|
164
|
+
contentSelector: options.contentSelector ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.contentSelector,
|
|
165
|
+
notFoundSelector: options.notFoundSelector ?? '',
|
|
166
|
+
minContentHeightPx: options.minContentHeightPx ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.minContentHeightPx,
|
|
167
|
+
neutralSkipSelector: options.neutralSkipSelector ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.neutralSkipSelector,
|
|
168
|
+
gradientAllowSelector: options.gradientAllowSelector ?? '',
|
|
169
|
+
loudHueFrom: options.loudHueFrom ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.loudHueFrom,
|
|
170
|
+
loudHueTo: options.loudHueTo ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.loudHueTo,
|
|
171
|
+
loudMinSaturation: options.loudMinSaturation ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.loudMinSaturation,
|
|
172
|
+
loudMinAlpha: options.loudMinAlpha ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.loudMinAlpha,
|
|
173
|
+
placeholderPattern: options.placeholderPattern === undefined
|
|
174
|
+
? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.placeholderPattern : options.placeholderPattern,
|
|
175
|
+
isNeutralLookSkipped: options.isNeutralLookSkipped ?? false,
|
|
176
|
+
isTextCheckSkipped: options.isTextCheckSkipped ?? false,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* The look checks of one page: ① the "not found" screen and the content area (when the product names them),
|
|
181
|
+
* ③ the neutral look, ⑤ ⑥ ⑦ the text.
|
|
182
|
+
*
|
|
183
|
+
* @returns the findings; empty when the page looks as it should
|
|
184
|
+
*/
|
|
185
|
+
static async inspect(page, options = {}) {
|
|
186
|
+
const set = this.resolve(options);
|
|
187
|
+
const maxFindings = visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.maxFindingsPerCheck;
|
|
188
|
+
const findings = [];
|
|
189
|
+
const content = await page.evaluate(`(${this.contentScript})(${JSON.stringify(set)})`);
|
|
190
|
+
if (content.isNotFound) {
|
|
191
|
+
findings.push({ kind: 'not-found', message: `the "not found" screen is shown (${set.notFoundSelector})` });
|
|
192
|
+
}
|
|
193
|
+
if (options.contentSelector && content.isContentMissing) {
|
|
194
|
+
findings.push({ kind: 'content-empty', message: `the content area is missing (${set.contentSelector})` });
|
|
195
|
+
}
|
|
196
|
+
else if (options.contentSelector && content.contentHeight < set.minContentHeightPx) {
|
|
197
|
+
findings.push({
|
|
198
|
+
kind: 'content-empty',
|
|
199
|
+
message: `the content area is ${content.contentHeight} px high (at least ${set.minContentHeightPx})`,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (!set.isNeutralLookSkipped) {
|
|
203
|
+
const loud = await page.evaluate(`(${this.loudColorsScript})(${JSON.stringify({ ...set, maxFindings: maxFindings })})`);
|
|
204
|
+
findings.push(...loud.map((line) => ({
|
|
205
|
+
kind: 'loud-color', message: `${line} instead of the neutral grey`,
|
|
206
|
+
})));
|
|
207
|
+
}
|
|
208
|
+
if (!set.isTextCheckSkipped) {
|
|
209
|
+
findings.push(...await page.evaluate(`(${this.textProblemsScript})(${JSON.stringify({ placeholderPattern: set.placeholderPattern, maxFindings: maxFindings })})`));
|
|
210
|
+
}
|
|
211
|
+
return findings;
|
|
212
|
+
}
|
|
213
|
+
/** ④ the font sizes of the headings and paragraphs in the content area (after the fonts are loaded). */
|
|
214
|
+
static async measureFontSizes(page, contentSelector) {
|
|
215
|
+
await page.evaluate('document.fonts ? document.fonts.ready.then(() => true) : true');
|
|
216
|
+
return page.evaluate(`(${this.fontSizesScript})(${JSON.stringify({
|
|
217
|
+
contentSelector: contentSelector ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.contentSelector,
|
|
218
|
+
tags: visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.fontSizeTags,
|
|
219
|
+
})})`);
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* ④ judges the font sizes against the reference: a size the reference uses for a tag, missing here, is a finding
|
|
223
|
+
* ("the old font size dropped"). A size only here is not (a new heading may have its own).
|
|
224
|
+
*/
|
|
225
|
+
static compareFontSizes(ours, reference) {
|
|
226
|
+
const findings = [];
|
|
227
|
+
for (const tag of Object.keys(reference)) {
|
|
228
|
+
const own = ours[tag] ?? [];
|
|
229
|
+
const missing = reference[tag].filter((size) => !own.includes(size));
|
|
230
|
+
if (missing.length) {
|
|
231
|
+
findings.push({
|
|
232
|
+
kind: 'font-size-differs',
|
|
233
|
+
message: `<${tag}> on the reference ${reference[tag].join(', ')}, here ${own.join(', ') || 'none'}`,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return findings;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
exports.DyE2E_VisualGateLook_Util = DyE2E_VisualGateLook_Util;
|
|
241
|
+
//# sourceMappingURL=visual-gate-look.util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-gate-look.util.js","sourceRoot":"","sources":["../../../../src/_modules/visual-gate/_collections/visual-gate-look.util.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Page } from '@playwright/test';
|
|
2
|
+
import { DyE2E_VisualGateCase_Interface } from '../_models/visual-gate-case.interface';
|
|
3
|
+
import { DyE2E_VisualGateFinding_Interface } from '../_models/visual-gate-finding.interface';
|
|
4
|
+
import { DyE2E_VisualGateLanguage_Interface } from '../_models/visual-gate-language.interface';
|
|
5
|
+
import { DyE2E_VisualGateLayout_Interface } from '../_models/visual-gate-layout.interface';
|
|
6
|
+
import { DyE2E_VisualGateOptions_Interface } from '../_models/visual-gate-options.interface';
|
|
7
|
+
import { DyE2E_VisualGatePage_Interface } from '../_models/visual-gate-page.interface';
|
|
8
|
+
import { DyE2E_VisualGateViewport_Interface } from '../_models/visual-gate-viewport.interface';
|
|
9
|
+
/**
|
|
10
|
+
* The visual regression gate (R1-72 d; owner, 2026-11-02 21:39: the credit login "badly out of shape", "a review tool
|
|
11
|
+
* is needed"): the key pages, HU + EN, 1440 + 390, red in the CI when a page falls apart or disappears.
|
|
12
|
+
*
|
|
13
|
+
* @description The cheap and certain checks first: (1) a layout check in the browser — the TEST warning or the
|
|
14
|
+
* cookie bar still covers the page, the page shows (almost) nothing, or it is wider than the screen; (2) the look
|
|
15
|
+
* (BFR-FUTDEVPRO-081, `DyE2E_VisualGateLook_Util`) — the "not found" screen, an empty content area, green / cyan /
|
|
16
|
+
* blue instead of the neutral grey, a text off the screen, a raw icon word, a placeholder; (3) with a `referenceUrl`,
|
|
17
|
+
* the heading and paragraph sizes of production; then (4) a screenshot comparison against the product's committed
|
|
18
|
+
* baseline (Playwright `toHaveScreenshot`; the first run, or `--update-snapshots`, writes it; it can be skipped).
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { DyE2E_BlockingOverlays_Util, DyE2E_VisualGate_Util } from '@futdevpro/dynamo-e2e/visual-gate';
|
|
22
|
+
*
|
|
23
|
+
* for (const gateCase of DyE2E_VisualGate_Util.cases({ pages: [ { name: 'home', path: '/' } ] })) {
|
|
24
|
+
* test(`| visual: ${gateCase.id}`, async ({ browser }) => {
|
|
25
|
+
* const { storageState } = await DyE2E_BlockingOverlays_Util.prepareStorageState({
|
|
26
|
+
* browser, baseUrl: BASE_URL, locale: gateCase.language.locale,
|
|
27
|
+
* });
|
|
28
|
+
* const context = await browser.newContext({ storageState, locale: gateCase.language.locale,
|
|
29
|
+
* viewport: { width: gateCase.viewport.width, height: gateCase.viewport.height } });
|
|
30
|
+
* const page = await context.newPage();
|
|
31
|
+
*
|
|
32
|
+
* await page.goto(BASE_URL + gateCase.page.path);
|
|
33
|
+
* await DyE2E_VisualGate_Util.assertPage(page, gateCase, {
|
|
34
|
+
* contentSelector: 'main', referenceUrl: 'https://example.hu', testInfo: test.info(),
|
|
35
|
+
* });
|
|
36
|
+
* });
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare class DyE2E_VisualGate_Util {
|
|
41
|
+
/** Browser-side layout measurement: a function expression, evaluated through `page.evaluate` (DOM APIs only). */
|
|
42
|
+
static readonly measureLayoutScript: string;
|
|
43
|
+
/**
|
|
44
|
+
* The check matrix: every page in every language at every screen size.
|
|
45
|
+
*
|
|
46
|
+
* @returns one case per page × language × viewport, ids `<page>-<language>-<viewport>`
|
|
47
|
+
*/
|
|
48
|
+
static cases(set: {
|
|
49
|
+
pages: DyE2E_VisualGatePage_Interface[];
|
|
50
|
+
languages?: DyE2E_VisualGateLanguage_Interface[];
|
|
51
|
+
viewports?: DyE2E_VisualGateViewport_Interface[];
|
|
52
|
+
}): DyE2E_VisualGateCase_Interface[];
|
|
53
|
+
/** Measures what the page shows (after the fonts are loaded). */
|
|
54
|
+
static measureLayout(page: Page): Promise<DyE2E_VisualGateLayout_Interface>;
|
|
55
|
+
/**
|
|
56
|
+
* Judges a measured layout.
|
|
57
|
+
*
|
|
58
|
+
* @returns `page-empty` (it disappeared) and / or `horizontal-overflow` (it fell apart); empty when healthy
|
|
59
|
+
*/
|
|
60
|
+
static checkLayout(layout: DyE2E_VisualGateLayout_Interface, limits?: {
|
|
61
|
+
minVisibleTextLength?: number;
|
|
62
|
+
minVisibleElementCount?: number;
|
|
63
|
+
overflowTolerancePx?: number;
|
|
64
|
+
}): DyE2E_VisualGateFinding_Interface[];
|
|
65
|
+
/** The cheap checks of one page: overlays still covering it, the layout and the look. */
|
|
66
|
+
static inspect(page: Page, options?: DyE2E_VisualGateOptions_Interface): Promise<DyE2E_VisualGateFinding_Interface[]>;
|
|
67
|
+
/**
|
|
68
|
+
* ④ the same page on the reference deployment (production, read only), at the same screen size and in the same
|
|
69
|
+
* language: its heading and paragraph sizes must be here too.
|
|
70
|
+
*
|
|
71
|
+
* @returns the `font-size-differs` findings; with `testInfo`, the reference's full-page screenshot is attached
|
|
72
|
+
*/
|
|
73
|
+
static compareWithReference(page: Page, gateCase: DyE2E_VisualGateCase_Interface, options: DyE2E_VisualGateOptions_Interface & {
|
|
74
|
+
referenceUrl: string;
|
|
75
|
+
}): Promise<DyE2E_VisualGateFinding_Interface[]>;
|
|
76
|
+
/**
|
|
77
|
+
* The gate for one case, inside a Playwright test: the cheap checks first, then (with a `referenceUrl`, on a page
|
|
78
|
+
* marked `isOnReference`) the font sizes of the reference — every finding fails the test with its reason —, then
|
|
79
|
+
* the screenshot comparison against the baseline `<case id>.png` (unless skipped). With `testInfo`, a full-page
|
|
80
|
+
* screenshot of the page goes into the report first, so a human sees it even when the case is red.
|
|
81
|
+
*/
|
|
82
|
+
static assertPage(page: Page, gateCase: DyE2E_VisualGateCase_Interface, options?: DyE2E_VisualGateOptions_Interface): Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=visual-gate.util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-gate.util.d.ts","sourceRoot":"","sources":["../../../../src/_modules/visual-gate/_collections/visual-gate.util.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAElF,OAAO,EAAE,8BAA8B,EAAE,MAAM,uCAAuC,CAAC;AACvF,OAAO,EAAE,iCAAiC,EAAE,MAAM,0CAA0C,CAAC;AAC7F,OAAO,EAAE,kCAAkC,EAAE,MAAM,2CAA2C,CAAC;AAC/F,OAAO,EAAE,gCAAgC,EAAE,MAAM,yCAAyC,CAAC;AAC3F,OAAO,EAAE,iCAAiC,EAAE,MAAM,0CAA0C,CAAC;AAC7F,OAAO,EAAE,8BAA8B,EAAE,MAAM,uCAAuC,CAAC;AACvF,OAAO,EAAE,kCAAkC,EAAE,MAAM,2CAA2C,CAAC;AAK/F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,qBAAqB;IAEhC,iHAAiH;IACjH,MAAM,CAAC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAoCxC;IAEH;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;QAChB,KAAK,EAAE,8BAA8B,EAAE,CAAC;QACxC,SAAS,CAAC,EAAE,kCAAkC,EAAE,CAAC;QACjD,SAAS,CAAC,EAAE,kCAAkC,EAAE,CAAC;KAClD,GAAG,8BAA8B,EAAE;IAoBpC,iEAAiE;WACpD,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,gCAAgC,CAAC;IAMjF;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAChB,MAAM,EAAE,gCAAgC,EACxC,MAAM,GAAE;QAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;QAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;KAAO,GAC5G,iCAAiC,EAAE;IAwBtC,yFAAyF;WAC5E,OAAO,CAClB,IAAI,EAAE,IAAI,EACV,OAAO,GAAE,iCAAsC,GAC9C,OAAO,CAAC,iCAAiC,EAAE,CAAC;IAY/C;;;;;OAKG;WACU,oBAAoB,CAC/B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,8BAA8B,EACxC,OAAO,EAAE,iCAAiC,GAAG;QAAE,YAAY,EAAE,MAAM,CAAA;KAAE,GACpE,OAAO,CAAC,iCAAiC,EAAE,CAAC;IAuC/C;;;;;OAKG;WACU,UAAU,CACrB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,8BAA8B,EACxC,OAAO,GAAE,iCAAsC,GAC9C,OAAO,CAAC,IAAI,CAAC;CA6BjB"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DyE2E_VisualGate_Util = void 0;
|
|
4
|
+
const test_1 = require("@playwright/test");
|
|
5
|
+
const blocking_overlays_util_1 = require("./blocking-overlays.util");
|
|
6
|
+
const visual_gate_defaults_const_1 = require("./visual-gate-defaults.const");
|
|
7
|
+
const visual_gate_look_util_1 = require("./visual-gate-look.util");
|
|
8
|
+
/**
|
|
9
|
+
* The visual regression gate (R1-72 d; owner, 2026-11-02 21:39: the credit login "badly out of shape", "a review tool
|
|
10
|
+
* is needed"): the key pages, HU + EN, 1440 + 390, red in the CI when a page falls apart or disappears.
|
|
11
|
+
*
|
|
12
|
+
* @description The cheap and certain checks first: (1) a layout check in the browser — the TEST warning or the
|
|
13
|
+
* cookie bar still covers the page, the page shows (almost) nothing, or it is wider than the screen; (2) the look
|
|
14
|
+
* (BFR-FUTDEVPRO-081, `DyE2E_VisualGateLook_Util`) — the "not found" screen, an empty content area, green / cyan /
|
|
15
|
+
* blue instead of the neutral grey, a text off the screen, a raw icon word, a placeholder; (3) with a `referenceUrl`,
|
|
16
|
+
* the heading and paragraph sizes of production; then (4) a screenshot comparison against the product's committed
|
|
17
|
+
* baseline (Playwright `toHaveScreenshot`; the first run, or `--update-snapshots`, writes it; it can be skipped).
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { DyE2E_BlockingOverlays_Util, DyE2E_VisualGate_Util } from '@futdevpro/dynamo-e2e/visual-gate';
|
|
21
|
+
*
|
|
22
|
+
* for (const gateCase of DyE2E_VisualGate_Util.cases({ pages: [ { name: 'home', path: '/' } ] })) {
|
|
23
|
+
* test(`| visual: ${gateCase.id}`, async ({ browser }) => {
|
|
24
|
+
* const { storageState } = await DyE2E_BlockingOverlays_Util.prepareStorageState({
|
|
25
|
+
* browser, baseUrl: BASE_URL, locale: gateCase.language.locale,
|
|
26
|
+
* });
|
|
27
|
+
* const context = await browser.newContext({ storageState, locale: gateCase.language.locale,
|
|
28
|
+
* viewport: { width: gateCase.viewport.width, height: gateCase.viewport.height } });
|
|
29
|
+
* const page = await context.newPage();
|
|
30
|
+
*
|
|
31
|
+
* await page.goto(BASE_URL + gateCase.page.path);
|
|
32
|
+
* await DyE2E_VisualGate_Util.assertPage(page, gateCase, {
|
|
33
|
+
* contentSelector: 'main', referenceUrl: 'https://example.hu', testInfo: test.info(),
|
|
34
|
+
* });
|
|
35
|
+
* });
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
class DyE2E_VisualGate_Util {
|
|
40
|
+
/** Browser-side layout measurement: a function expression, evaluated through `page.evaluate` (DOM APIs only). */
|
|
41
|
+
static measureLayoutScript = `() => {
|
|
42
|
+
const root = document.documentElement;
|
|
43
|
+
const viewportWidth = root.clientWidth;
|
|
44
|
+
let visibleTextLength = 0;
|
|
45
|
+
let visibleElementCount = 0;
|
|
46
|
+
let widestOverflow = null;
|
|
47
|
+
let widestRight = viewportWidth;
|
|
48
|
+
for (const el of Array.from(document.body ? document.body.querySelectorAll('*') : [])) {
|
|
49
|
+
const style = getComputedStyle(el);
|
|
50
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const rect = el.getBoundingClientRect();
|
|
54
|
+
if (rect.width < 1 || rect.height < 1) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
visibleElementCount++;
|
|
58
|
+
for (const node of Array.from(el.childNodes)) {
|
|
59
|
+
if (node.nodeType === 3) {
|
|
60
|
+
visibleTextLength += node.textContent.trim().length;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const right = rect.right + window.scrollX;
|
|
64
|
+
if (right > widestRight + 1 && style.position !== 'fixed') {
|
|
65
|
+
widestRight = right;
|
|
66
|
+
const cls = typeof el.className === 'string' ? el.className.trim().split(/\\s+/).slice(0, 2).join('.') : '';
|
|
67
|
+
widestOverflow = el.tagName.toLowerCase() + (cls ? '.' + cls : '') + ' (' + Math.round(right) + ' px)';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
viewportWidth: viewportWidth,
|
|
72
|
+
scrollWidth: root.scrollWidth,
|
|
73
|
+
widestOverflow: widestOverflow,
|
|
74
|
+
visibleTextLength: visibleTextLength,
|
|
75
|
+
visibleElementCount: visibleElementCount,
|
|
76
|
+
};
|
|
77
|
+
}`;
|
|
78
|
+
/**
|
|
79
|
+
* The check matrix: every page in every language at every screen size.
|
|
80
|
+
*
|
|
81
|
+
* @returns one case per page × language × viewport, ids `<page>-<language>-<viewport>`
|
|
82
|
+
*/
|
|
83
|
+
static cases(set) {
|
|
84
|
+
const gateCases = [];
|
|
85
|
+
for (const page of set.pages) {
|
|
86
|
+
for (const language of set.languages ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.languages) {
|
|
87
|
+
for (const viewport of set.viewports ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.viewports) {
|
|
88
|
+
const id = `${page.name}-${language.code}-${viewport.name}`;
|
|
89
|
+
gateCases.push({ id: id, page: page, language: language, viewport: viewport, snapshotName: `${id}.png` });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (new Set(gateCases.map((gateCase) => gateCase.id)).size
|
|
94
|
+
!== gateCases.length) {
|
|
95
|
+
throw new Error('[dynamo-e2e visual-gate] two pages, languages or viewports share a name');
|
|
96
|
+
}
|
|
97
|
+
return gateCases;
|
|
98
|
+
}
|
|
99
|
+
/** Measures what the page shows (after the fonts are loaded). */
|
|
100
|
+
static async measureLayout(page) {
|
|
101
|
+
await page.evaluate('document.fonts ? document.fonts.ready.then(() => true) : true');
|
|
102
|
+
return page.evaluate(`(${this.measureLayoutScript})()`);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Judges a measured layout.
|
|
106
|
+
*
|
|
107
|
+
* @returns `page-empty` (it disappeared) and / or `horizontal-overflow` (it fell apart); empty when healthy
|
|
108
|
+
*/
|
|
109
|
+
static checkLayout(layout, limits = {}) {
|
|
110
|
+
const findings = [];
|
|
111
|
+
const minText = limits.minVisibleTextLength ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.minVisibleTextLength;
|
|
112
|
+
const minElements = limits.minVisibleElementCount ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.minVisibleElementCount;
|
|
113
|
+
const tolerance = limits.overflowTolerancePx ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.overflowTolerancePx;
|
|
114
|
+
if (layout.visibleTextLength < minText || layout.visibleElementCount < minElements) {
|
|
115
|
+
findings.push({
|
|
116
|
+
kind: 'page-empty',
|
|
117
|
+
message: `the page shows almost nothing: ${layout.visibleTextLength} characters, `
|
|
118
|
+
+ `${layout.visibleElementCount} elements (at least ${minText} / ${minElements})`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (layout.scrollWidth > layout.viewportWidth + tolerance) {
|
|
122
|
+
findings.push({
|
|
123
|
+
kind: 'horizontal-overflow',
|
|
124
|
+
message: `the page is ${layout.scrollWidth} px wide on a ${layout.viewportWidth} px screen`
|
|
125
|
+
+ (layout.widestOverflow ? `; widest: ${layout.widestOverflow}` : ''),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return findings;
|
|
129
|
+
}
|
|
130
|
+
/** The cheap checks of one page: overlays still covering it, the layout and the look. */
|
|
131
|
+
static async inspect(page, options = {}) {
|
|
132
|
+
const findings = (await blocking_overlays_util_1.DyE2E_BlockingOverlays_Util.findVisible(page))
|
|
133
|
+
.map((name) => ({
|
|
134
|
+
kind: 'blocking-overlay', message: `${name} covers the page (close it first: DyE2E_BlockingOverlays_Util)`,
|
|
135
|
+
}));
|
|
136
|
+
return findings.concat(this.checkLayout(await this.measureLayout(page), options), await visual_gate_look_util_1.DyE2E_VisualGateLook_Util.inspect(page, options));
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* ④ the same page on the reference deployment (production, read only), at the same screen size and in the same
|
|
140
|
+
* language: its heading and paragraph sizes must be here too.
|
|
141
|
+
*
|
|
142
|
+
* @returns the `font-size-differs` findings; with `testInfo`, the reference's full-page screenshot is attached
|
|
143
|
+
*/
|
|
144
|
+
static async compareWithReference(page, gateCase, options) {
|
|
145
|
+
const browser = page.context().browser();
|
|
146
|
+
if (!browser) {
|
|
147
|
+
throw new Error('[dynamo-e2e visual-gate] the reference needs a browser (not a persistent context)');
|
|
148
|
+
}
|
|
149
|
+
const context = await browser.newContext({
|
|
150
|
+
ignoreHTTPSErrors: true,
|
|
151
|
+
locale: gateCase.language.locale,
|
|
152
|
+
viewport: { width: gateCase.viewport.width, height: gateCase.viewport.height },
|
|
153
|
+
storageState: options.referenceStorageState,
|
|
154
|
+
});
|
|
155
|
+
try {
|
|
156
|
+
const reference = await context.newPage();
|
|
157
|
+
await reference.goto(options.referenceUrl.replace(/\/$/u, '') + gateCase.page.path, { waitUntil: 'domcontentloaded', timeout: 45_000 });
|
|
158
|
+
if (!options.referenceStorageState) {
|
|
159
|
+
await blocking_overlays_util_1.DyE2E_BlockingOverlays_Util.acknowledge(reference);
|
|
160
|
+
}
|
|
161
|
+
await reference.waitForLoadState('networkidle', { timeout: 20_000 }).catch(() => undefined);
|
|
162
|
+
const theirs = await visual_gate_look_util_1.DyE2E_VisualGateLook_Util.measureFontSizes(reference, options.contentSelector);
|
|
163
|
+
if (options.testInfo) {
|
|
164
|
+
await options.testInfo.attach(`reference-${gateCase.id}.png`, {
|
|
165
|
+
body: await reference.screenshot({ fullPage: true }), contentType: 'image/png',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return visual_gate_look_util_1.DyE2E_VisualGateLook_Util.compareFontSizes(await visual_gate_look_util_1.DyE2E_VisualGateLook_Util.measureFontSizes(page, options.contentSelector), theirs);
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
await context.close();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The gate for one case, inside a Playwright test: the cheap checks first, then (with a `referenceUrl`, on a page
|
|
176
|
+
* marked `isOnReference`) the font sizes of the reference — every finding fails the test with its reason —, then
|
|
177
|
+
* the screenshot comparison against the baseline `<case id>.png` (unless skipped). With `testInfo`, a full-page
|
|
178
|
+
* screenshot of the page goes into the report first, so a human sees it even when the case is red.
|
|
179
|
+
*/
|
|
180
|
+
static async assertPage(page, gateCase, options = {}) {
|
|
181
|
+
if (options.testInfo) {
|
|
182
|
+
await options.testInfo.attach(`test-${gateCase.id}.png`, {
|
|
183
|
+
body: await page.screenshot({ fullPage: true }), contentType: 'image/png',
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
const findings = await this.inspect(page, options);
|
|
187
|
+
if (options.referenceUrl && gateCase.page.isOnReference) {
|
|
188
|
+
findings.push(...await this.compareWithReference(page, gateCase, {
|
|
189
|
+
...options, referenceUrl: options.referenceUrl,
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
if (findings.length) {
|
|
193
|
+
throw new Error(`[dynamo-e2e visual-gate] ${gateCase.id} (${page.url()}):\n`
|
|
194
|
+
+ findings.map((finding) => `- ${finding.kind}: ${finding.message}`).join('\n'));
|
|
195
|
+
}
|
|
196
|
+
if (options.isScreenshotComparisonSkipped) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
await (0, test_1.expect)(page).toHaveScreenshot(gateCase.snapshotName, {
|
|
200
|
+
fullPage: true,
|
|
201
|
+
animations: 'disabled',
|
|
202
|
+
caret: 'hide',
|
|
203
|
+
maxDiffPixelRatio: options.maxDiffPixelRatio ?? visual_gate_defaults_const_1.DyE2E_VisualGateDefaults_Const.maxDiffPixelRatio,
|
|
204
|
+
mask: (gateCase.page.mask ?? []).map((selector) => page.locator(selector)),
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
exports.DyE2E_VisualGate_Util = DyE2E_VisualGate_Util;
|
|
209
|
+
//# sourceMappingURL=visual-gate.util.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-gate.util.js","sourceRoot":"","sources":["../../../../src/_modules/visual-gate/_collections/visual-gate.util.ts"],"names":[],"mappings":";;;AAAA,2CAAkF;AASlF,qEAAuE;AACvE,6EAA8E;AAC9E,mEAAoG;AAEpG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAa,qBAAqB;IAEhC,iHAAiH;IACjH,MAAM,CAAU,mBAAmB,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoC5C,CAAC;IAEH;;;;OAIG;IACH,MAAM,CAAC,KAAK,CAAC,GAIZ;QACC,MAAM,SAAS,GAAqC,EAAE,CAAC;QAEvD,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC7B,KAAK,MAAM,QAAQ,IAAI,GAAG,CAAC,SAAS,IAAI,2DAA8B,CAAC,SAAS,EAAE,CAAC;gBACjF,KAAK,MAAM,QAAQ,IAAI,GAAG,CAAC,SAAS,IAAI,2DAA8B,CAAC,SAAS,EAAE,CAAC;oBACjF,MAAM,EAAE,GAAW,GAAG,IAAI,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC;oBAEpE,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;gBAC5G,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,QAAwC,EAAU,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI;gBAC5F,SAAS,CAAC,MAAM,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;QAC7F,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,iEAAiE;IACjE,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,IAAU;QACnC,MAAM,IAAI,CAAC,QAAQ,CAAC,+DAA+D,CAAC,CAAC;QAErF,OAAO,IAAI,CAAC,QAAQ,CAAmC,IAAI,IAAI,CAAC,mBAAmB,KAAK,CAAC,CAAC;IAC5F,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,WAAW,CAChB,MAAwC,EACxC,SAA2G,EAAE;QAE7G,MAAM,QAAQ,GAAwC,EAAE,CAAC;QACzD,MAAM,OAAO,GAAW,MAAM,CAAC,oBAAoB,IAAI,2DAA8B,CAAC,oBAAoB,CAAC;QAC3G,MAAM,WAAW,GAAW,MAAM,CAAC,sBAAsB,IAAI,2DAA8B,CAAC,sBAAsB,CAAC;QACnH,MAAM,SAAS,GAAW,MAAM,CAAC,mBAAmB,IAAI,2DAA8B,CAAC,mBAAmB,CAAC;QAE3G,IAAI,MAAM,CAAC,iBAAiB,GAAG,OAAO,IAAI,MAAM,CAAC,mBAAmB,GAAG,WAAW,EAAE,CAAC;YACnF,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,YAAY;gBAClB,OAAO,EAAE,kCAAkC,MAAM,CAAC,iBAAiB,eAAe;sBAC9E,GAAG,MAAM,CAAC,mBAAmB,uBAAuB,OAAO,MAAM,WAAW,GAAG;aACpF,CAAC,CAAC;QACL,CAAC;QACD,IAAI,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,aAAa,GAAG,SAAS,EAAE,CAAC;YAC1D,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,qBAAqB;gBAC3B,OAAO,EAAE,eAAe,MAAM,CAAC,WAAW,iBAAiB,MAAM,CAAC,aAAa,YAAY;sBACvF,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,aAAa,MAAM,CAAC,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE,CAAC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,yFAAyF;IACzF,MAAM,CAAC,KAAK,CAAC,OAAO,CAClB,IAAU,EACV,UAA6C,EAAE;QAE/C,MAAM,QAAQ,GAAwC,CAAC,MAAM,oDAA2B,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;aACxG,GAAG,CAAC,CAAC,IAAY,EAAqC,EAAE,CAAC,CAAC;YACzD,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,GAAG,IAAI,gEAAgE;SAC3G,CAAC,CAAC,CAAC;QAEN,OAAO,QAAQ,CAAC,MAAM,CACpB,IAAI,CAAC,WAAW,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EACzD,MAAM,iDAAyB,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CACvD,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAC/B,IAAU,EACV,QAAwC,EACxC,OAAqE;QAErE,MAAM,OAAO,GAAmB,IAAI,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC;QAEzD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,mFAAmF,CAAC,CAAC;QACvG,CAAC;QACD,MAAM,OAAO,GAAmB,MAAM,OAAO,CAAC,UAAU,CAAC;YACvD,iBAAiB,EAAE,IAAI;YACvB,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM;YAChC,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,EAAE;YAC9E,YAAY,EAAE,OAAO,CAAC,qBAAqB;SAC5C,CAAC,CAAC;QAEH,IAAI,CAAC;YACH,MAAM,SAAS,GAAS,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;YAEhD,MAAM,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,EAChF,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACtD,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC;gBACnC,MAAM,oDAA2B,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAC3D,CAAC;YACD,MAAM,SAAS,CAAC,gBAAgB,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,GAAS,EAAE,CAAC,SAAS,CAAC,CAAC;YAClG,MAAM,MAAM,GACV,MAAM,iDAAyB,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;YAEvF,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;gBACrB,MAAM,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,aAAa,QAAQ,CAAC,EAAE,MAAM,EAAE;oBAC5D,IAAI,EAAE,MAAM,SAAS,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,WAAW;iBAC/E,CAAC,CAAC;YACL,CAAC;YAED,OAAO,iDAAyB,CAAC,gBAAgB,CAC/C,MAAM,iDAAyB,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,CACxF,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,UAAU,CACrB,IAAU,EACV,QAAwC,EACxC,UAA6C,EAAE;QAE/C,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,QAAQ,CAAC,EAAE,MAAM,EAAE;gBACvD,IAAI,EAAE,MAAM,IAAI,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,WAAW;aAC1E,CAAC,CAAC;QACL,CAAC;QACD,MAAM,QAAQ,GAAwC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAExF,IAAI,OAAO,CAAC,YAAY,IAAI,QAAQ,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC;YACxD,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,QAAQ,EAAE;gBAC/D,GAAG,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY;aAC/C,CAAC,CAAC,CAAC;QACN,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,EAAE,MAAM;kBACxE,QAAQ,CAAC,GAAG,CAAC,CAAC,OAA0C,EAAU,EAAE,CACpE,KAAK,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI,OAAO,CAAC,6BAA6B,EAAE,CAAC;YAC1C,OAAO;QACT,CAAC;QACD,MAAM,IAAA,aAAM,EAAC,IAAI,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,YAAY,EAAE;YACzD,QAAQ,EAAE,IAAI;YACd,UAAU,EAAE,UAAU;YACtB,KAAK,EAAE,MAAM;YACb,iBAAiB,EAAE,OAAO,CAAC,iBAAiB,IAAI,2DAA8B,CAAC,iBAAiB;YAChG,IAAI,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,QAAgB,EAAW,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;SAC5F,CAAC,CAAC;IACL,CAAC;;AApNH,sDAqNC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { DyE2E_VisualGateLanguage_Interface } from './visual-gate-language.interface';
|
|
2
|
+
import { DyE2E_VisualGatePage_Interface } from './visual-gate-page.interface';
|
|
3
|
+
import { DyE2E_VisualGateViewport_Interface } from './visual-gate-viewport.interface';
|
|
4
|
+
/** One check of the visual gate: a page, in one language, at one screen size. */
|
|
5
|
+
export interface DyE2E_VisualGateCase_Interface {
|
|
6
|
+
/** `<page>-<language>-<viewport>`, unique within the matrix. */
|
|
7
|
+
id: string;
|
|
8
|
+
page: DyE2E_VisualGatePage_Interface;
|
|
9
|
+
language: DyE2E_VisualGateLanguage_Interface;
|
|
10
|
+
viewport: DyE2E_VisualGateViewport_Interface;
|
|
11
|
+
/** The baseline file name of the screenshot comparison (`<id>.png`). */
|
|
12
|
+
snapshotName: string;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=visual-gate-case.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-gate-case.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/visual-gate/_models/visual-gate-case.interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kCAAkC,EAAE,MAAM,kCAAkC,CAAC;AACtF,OAAO,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,kCAAkC,EAAE,MAAM,kCAAkC,CAAC;AAEtF,iFAAiF;AACjF,MAAM,WAAW,8BAA8B;IAC7C,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,8BAA8B,CAAC;IACrC,QAAQ,EAAE,kCAAkC,CAAC;IAC7C,QAAQ,EAAE,kCAAkC,CAAC;IAC7C,wEAAwE;IACxE,YAAY,EAAE,MAAM,CAAC;CACtB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-gate-case.interface.js","sourceRoot":"","sources":["../../../../src/_modules/visual-gate/_models/visual-gate-case.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** A reason the visual gate fails a page before (or instead of) the screenshot comparison. */
|
|
2
|
+
export interface DyE2E_VisualGateFinding_Interface {
|
|
3
|
+
/**
|
|
4
|
+
* `blocking-overlay`: the TEST warning or the cookie bar covers the page (the screenshot would show them);
|
|
5
|
+
* `page-empty`: the page shows (almost) nothing — it disappeared; `horizontal-overflow`: the page is wider than the
|
|
6
|
+
* screen — it fell apart; `not-found`: the product's "not found" screen is shown; `content-empty`: the content area
|
|
7
|
+
* is missing or (almost) empty; `loud-color`: a box of the content area has a saturated green / cyan / blue frame,
|
|
8
|
+
* background or gradient instead of the systems' neutral grey; `font-size-differs`: a heading or paragraph size of
|
|
9
|
+
* the reference (production) is gone; `text-off-screen`: a visible text sticks out of the screen; `raw-icon-word`:
|
|
10
|
+
* an icon shows its ligature word instead of the sign; `placeholder-text`: "coming soon", "lorem ipsum", `TODO` …
|
|
11
|
+
* is visible.
|
|
12
|
+
*/
|
|
13
|
+
kind: 'blocking-overlay' | 'page-empty' | 'horizontal-overflow' | 'not-found' | 'content-empty' | 'loud-color' | 'font-size-differs' | 'text-off-screen' | 'raw-icon-word' | 'placeholder-text';
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=visual-gate-finding.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-gate-finding.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/visual-gate/_models/visual-gate-finding.interface.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,MAAM,WAAW,iCAAiC;IAChD;;;;;;;;;OASG;IACH,IAAI,EAAE,kBAAkB,GAAG,YAAY,GAAG,qBAAqB,GAAG,WAAW,GAAG,eAAe,GAAG,YAAY,GAC1G,mBAAmB,GAAG,iBAAiB,GAAG,eAAe,GAAG,kBAAkB,CAAC;IACnF,OAAO,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-gate-finding.interface.js","sourceRoot":"","sources":["../../../../src/_modules/visual-gate/_models/visual-gate-finding.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** A language of the visual gate: the page is opened in a browser of this locale. */
|
|
2
|
+
export interface DyE2E_VisualGateLanguage_Interface {
|
|
3
|
+
/** Short code (`hu`, `en`) — part of the snapshot name. */
|
|
4
|
+
code: string;
|
|
5
|
+
/** The browser locale (`hu-HU`, `en-US`): the FDP language service follows it when nothing is stored. */
|
|
6
|
+
locale: string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=visual-gate-language.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-gate-language.interface.d.ts","sourceRoot":"","sources":["../../../../src/_modules/visual-gate/_models/visual-gate-language.interface.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,MAAM,WAAW,kCAAkC;IACjD,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,yGAAyG;IACzG,MAAM,EAAE,MAAM,CAAC;CAChB"}
|