@brandlab-digital/conformance 3.0.0 → 3.2.0
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/current-release.js +5 -5
- package/dist/current-release.js.map +1 -1
- package/dist/estate.d.ts +38 -2
- package/dist/estate.d.ts.map +1 -1
- package/dist/estate.js +84 -3
- package/dist/estate.js.map +1 -1
- package/package.json +1 -1
package/dist/current-release.js
CHANGED
|
@@ -23,19 +23,19 @@ export const BRANDLAB_CURRENT_RUNTIME_RELEASE = Object.freeze({
|
|
|
23
23
|
"kind": "brandlab-runtime-release-contract",
|
|
24
24
|
"packages": {
|
|
25
25
|
"@brandlab-digital/conformance": {
|
|
26
|
-
"version": "3.
|
|
26
|
+
"version": "3.2.0"
|
|
27
27
|
},
|
|
28
28
|
"@brandlab-digital/eslint-plugin": {
|
|
29
|
-
"version": "3.
|
|
29
|
+
"version": "3.2.0"
|
|
30
30
|
},
|
|
31
31
|
"@brandlab-digital/site-core": {
|
|
32
|
-
"version": "3.
|
|
32
|
+
"version": "3.2.0"
|
|
33
33
|
},
|
|
34
34
|
"@brandlab-digital/site-next": {
|
|
35
|
-
"version": "3.
|
|
35
|
+
"version": "3.2.0"
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
|
-
"releaseVersion": "3.
|
|
38
|
+
"releaseVersion": "3.2.0",
|
|
39
39
|
"runtime": {
|
|
40
40
|
"bundler": "turbopack",
|
|
41
41
|
"cacheComponents": false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"current-release.js","sourceRoot":"","sources":["../src/current-release.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wEAAwE;AACxE,OAAO,EAEL,sCAAsC,GACvC,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC,MAAM,CAC7D;IACE,cAAc,EAAE;QACd,uCAAuC;QACvC,8BAA8B;QAC9B,yBAAyB;QACzB,6BAA6B;QAC7B,0BAA0B;QAC1B,4BAA4B;QAC5B,kCAAkC;QAClC,wCAAwC;QACxC,yBAAyB;KAC1B;IACD,WAAW,EAAE;QACX,WAAW,EAAE,cAAc;QAC3B,cAAc,EAAE,iBAAiB;QACjC,eAAe,EAAE,mBAAmB;QACpC,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,QAAQ;KAChB;IACD,MAAM,EAAE,mCAAmC;IAC3C,UAAU,EAAE;QACV,+BAA+B,EAAE;YAC/B,SAAS,EAAE,OAAO;SACnB;QACD,iCAAiC,EAAE;YACjC,SAAS,EAAE,OAAO;SACnB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,OAAO;SACnB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,OAAO;SACnB;KACF;IACD,gBAAgB,EAAE,OAAO;IACzB,SAAS,EAAE;QACT,SAAS,EAAE,WAAW;QACtB,iBAAiB,EAAE,KAAK;QACxB,MAAM,EAAE,QAAQ;QAChB,mBAAmB,EAAE,OAAO;QAC5B,MAAM,EAAE,MAAM;QACd,gBAAgB,EAAE,aAAa;QAC/B,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,QAAQ;QACjB,YAAY,EAAE,OAAO;KACtB;IACD,eAAe,EAAE,sCAAsC;CACxD,CAC6C,CAAC;AAE/C,sCAAsC,CAAC,gCAAgC,CAAC,CAAC","sourcesContent":["// Generated by standards/scripts/generate-conformance-current-release.mjs.\n// Each exact package release verifies its own current runtime contract.\nimport {\n type BrandlabRuntimeReleaseContract,\n validateBrandlabRuntimeReleaseContract,\n} from \"./release-manifest.js\";\n\nexport const BRANDLAB_CURRENT_RUNTIME_RELEASE = Object.freeze(\n{\n \"capabilities\": [\n \"typed-project-business-and-route-data\",\n \"essential-search-foundations\",\n \"shared-contact-delivery\",\n \"privacy-safe-lead-analytics\",\n \"security-patched-runtime\",\n \"live-google-review-summary\",\n \"high-signal-project-verification\",\n \"packed-positive-and-negative-consumers\",\n \"changesets-oidc-release\"\n ],\n \"contracts\": {\n \"analytics\": \"analytics-v4\",\n \"architecture\": \"architecture-v7\",\n \"configuration\": \"project-config-v7\",\n \"contact\": \"contact-v4\",\n \"seo\": \"seo-v4\"\n },\n \"kind\": \"brandlab-runtime-release-contract\",\n \"packages\": {\n \"@brandlab-digital/conformance\": {\n \"version\": \"3.
|
|
1
|
+
{"version":3,"file":"current-release.js","sourceRoot":"","sources":["../src/current-release.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,wEAAwE;AACxE,OAAO,EAEL,sCAAsC,GACvC,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,gCAAgC,GAAG,MAAM,CAAC,MAAM,CAC7D;IACE,cAAc,EAAE;QACd,uCAAuC;QACvC,8BAA8B;QAC9B,yBAAyB;QACzB,6BAA6B;QAC7B,0BAA0B;QAC1B,4BAA4B;QAC5B,kCAAkC;QAClC,wCAAwC;QACxC,yBAAyB;KAC1B;IACD,WAAW,EAAE;QACX,WAAW,EAAE,cAAc;QAC3B,cAAc,EAAE,iBAAiB;QACjC,eAAe,EAAE,mBAAmB;QACpC,SAAS,EAAE,YAAY;QACvB,KAAK,EAAE,QAAQ;KAChB;IACD,MAAM,EAAE,mCAAmC;IAC3C,UAAU,EAAE;QACV,+BAA+B,EAAE;YAC/B,SAAS,EAAE,OAAO;SACnB;QACD,iCAAiC,EAAE;YACjC,SAAS,EAAE,OAAO;SACnB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,OAAO;SACnB;QACD,6BAA6B,EAAE;YAC7B,SAAS,EAAE,OAAO;SACnB;KACF;IACD,gBAAgB,EAAE,OAAO;IACzB,SAAS,EAAE;QACT,SAAS,EAAE,WAAW;QACtB,iBAAiB,EAAE,KAAK;QACxB,MAAM,EAAE,QAAQ;QAChB,mBAAmB,EAAE,OAAO;QAC5B,MAAM,EAAE,MAAM;QACd,gBAAgB,EAAE,aAAa;QAC/B,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,KAAK;QACf,OAAO,EAAE,QAAQ;QACjB,YAAY,EAAE,OAAO;KACtB;IACD,eAAe,EAAE,sCAAsC;CACxD,CAC6C,CAAC;AAE/C,sCAAsC,CAAC,gCAAgC,CAAC,CAAC","sourcesContent":["// Generated by standards/scripts/generate-conformance-current-release.mjs.\n// Each exact package release verifies its own current runtime contract.\nimport {\n type BrandlabRuntimeReleaseContract,\n validateBrandlabRuntimeReleaseContract,\n} from \"./release-manifest.js\";\n\nexport const BRANDLAB_CURRENT_RUNTIME_RELEASE = Object.freeze(\n{\n \"capabilities\": [\n \"typed-project-business-and-route-data\",\n \"essential-search-foundations\",\n \"shared-contact-delivery\",\n \"privacy-safe-lead-analytics\",\n \"security-patched-runtime\",\n \"live-google-review-summary\",\n \"high-signal-project-verification\",\n \"packed-positive-and-negative-consumers\",\n \"changesets-oidc-release\"\n ],\n \"contracts\": {\n \"analytics\": \"analytics-v4\",\n \"architecture\": \"architecture-v7\",\n \"configuration\": \"project-config-v7\",\n \"contact\": \"contact-v4\",\n \"seo\": \"seo-v4\"\n },\n \"kind\": \"brandlab-runtime-release-contract\",\n \"packages\": {\n \"@brandlab-digital/conformance\": {\n \"version\": \"3.2.0\"\n },\n \"@brandlab-digital/eslint-plugin\": {\n \"version\": \"3.2.0\"\n },\n \"@brandlab-digital/site-core\": {\n \"version\": \"3.2.0\"\n },\n \"@brandlab-digital/site-next\": {\n \"version\": \"3.2.0\"\n }\n },\n \"releaseVersion\": \"3.2.0\",\n \"runtime\": {\n \"bundler\": \"turbopack\",\n \"cacheComponents\": false,\n \"next\": \"16.3.6\",\n \"nextVersionPolicy\": \"exact\",\n \"node\": \"24.x\",\n \"packageManager\": \"npm@11.14.1\",\n \"react\": \"19.2.8\",\n \"reactDom\": \"19.2.8\",\n \"router\": \"app\",\n \"sharp\": \"0.35.4\",\n \"typescript\": \"6.0.3\"\n },\n \"schemaVersion\": \"brandlab.runtime-release-contract.v1\"\n},\n) as unknown as BrandlabRuntimeReleaseContract;\n\nvalidateBrandlabRuntimeReleaseContract(BRANDLAB_CURRENT_RUNTIME_RELEASE);\n"]}
|
package/dist/estate.d.ts
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* defineBrandlabEstateTests({ site, routes, redirects })
|
|
11
11
|
*
|
|
12
12
|
* It asserts the estate and its routes: the sitemap, each route's status,
|
|
13
|
-
* canonical and indexing, internal links, redirects, unknown slugs
|
|
14
|
-
* robots.txt. Page and structured-data
|
|
13
|
+
* canonical and indexing, internal links, redirects, unknown slugs, the page
|
|
14
|
+
* families rendered on request and robots.txt. Page and structured-data
|
|
15
|
+
* quality is reported, never enforced,
|
|
15
16
|
* by defineBrandlabPageQualityTests: a backend release keeps production's pages
|
|
16
17
|
* as they are, which the comparison harness proves. Counts are always derived
|
|
17
18
|
* from the current catalogue, never a historic constant.
|
|
@@ -29,6 +30,19 @@ export interface BrandlabEstateRedirect {
|
|
|
29
30
|
/** Host-scoped alias rules are proved by the comparison harness host probes. */
|
|
30
31
|
host?: string;
|
|
31
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* A page family the site renders on request outside the catalogue: a dynamic
|
|
35
|
+
* route that prerenders nothing and renders every page of its space (every
|
|
36
|
+
* combination of its values) when it is first requested.
|
|
37
|
+
*/
|
|
38
|
+
export interface BrandlabEstateFamily {
|
|
39
|
+
/** The app route, e.g. `/services/[service]/[location]`. */
|
|
40
|
+
pattern: string;
|
|
41
|
+
/** Every page of the family's space; catalogued pages of the route may be among them. */
|
|
42
|
+
paths: readonly string[];
|
|
43
|
+
/** The indexing its pages outside the catalogue render. */
|
|
44
|
+
indexing: BrandlabEstateRoute['indexing'];
|
|
45
|
+
}
|
|
32
46
|
export interface BrandlabEstateTestOptions {
|
|
33
47
|
site: {
|
|
34
48
|
canonicalUrl: string | URL;
|
|
@@ -46,6 +60,15 @@ export interface BrandlabEstateTestOptions {
|
|
|
46
60
|
notFoundPaths?: readonly string[];
|
|
47
61
|
/** Also prove `<source>/` redirects straight to the destination (the shared helper emits these). */
|
|
48
62
|
trailingSlashRedirects?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Page families rendered on request outside the catalogue. Links into a
|
|
65
|
+
* family's space count as live pages; one page of each family (outside the
|
|
66
|
+
* catalogue where it has one) is checked for its status, canonical and
|
|
67
|
+
* indexing; and a path of its pattern outside the space, one unknown value
|
|
68
|
+
* per dynamic segment, must answer 404 with the site's not-found page rendered
|
|
69
|
+
* on the server: the text an unknown top-level path's 404 page shows.
|
|
70
|
+
*/
|
|
71
|
+
families?: readonly BrandlabEstateFamily[];
|
|
49
72
|
concurrency?: number;
|
|
50
73
|
timeout?: number;
|
|
51
74
|
}
|
|
@@ -73,6 +96,19 @@ export declare function defineBrandlabEstateTests(options: BrandlabEstateTestOpt
|
|
|
73
96
|
export declare function readSitemap(request: APIRequestContext, path: string, origin?: string): Promise<string[]>;
|
|
74
97
|
export declare function extractSitemapLocations(xml: string): string[];
|
|
75
98
|
export declare function defaultNotFoundPaths(routes: readonly BrandlabEstateRoute[]): string[];
|
|
99
|
+
/** The page of a family its estate check opens: the first one outside the catalogue, else its first page. */
|
|
100
|
+
export declare function familySamplePath(family: Pick<BrandlabEstateFamily, 'paths'>, catalogue: ReadonlySet<string>): string | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Paths of a family's pattern outside its space: a page of the family with one
|
|
103
|
+
* dynamic segment at a time replaced by an unknown slug.
|
|
104
|
+
*/
|
|
105
|
+
export declare function familyNotFoundPaths(family: Pick<BrandlabEstateFamily, 'pattern' | 'paths'>): string[];
|
|
106
|
+
/**
|
|
107
|
+
* The text a page shows before any script runs: its body without scripts,
|
|
108
|
+
* styles and templates, tags removed and whitespace collapsed. Empty for the
|
|
109
|
+
* document Next sends when it leaves a page to the browser to render.
|
|
110
|
+
*/
|
|
111
|
+
export declare function bodyText(html: string): string;
|
|
76
112
|
/** A Next redirect path with parameters: `:name`, `:name*`, `:name+`, `:name?` or a regular-expression group. */
|
|
77
113
|
export declare function isRedirectPattern(path: string): boolean;
|
|
78
114
|
/**
|
package/dist/estate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"estate.d.ts","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"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"}
|
package/dist/estate.js
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* defineBrandlabEstateTests({ site, routes, redirects })
|
|
11
11
|
*
|
|
12
12
|
* It asserts the estate and its routes: the sitemap, each route's status,
|
|
13
|
-
* canonical and indexing, internal links, redirects, unknown slugs
|
|
14
|
-
* robots.txt. Page and structured-data
|
|
13
|
+
* canonical and indexing, internal links, redirects, unknown slugs, the page
|
|
14
|
+
* families rendered on request and robots.txt. Page and structured-data
|
|
15
|
+
* quality is reported, never enforced,
|
|
15
16
|
* by defineBrandlabPageQualityTests: a backend release keeps production's pages
|
|
16
17
|
* as they are, which the comparison harness proves. Counts are always derived
|
|
17
18
|
* from the current catalogue, never a historic constant.
|
|
@@ -39,6 +40,8 @@ export function defineBrandlabEstateTests(options) {
|
|
|
39
40
|
});
|
|
40
41
|
const findRedirect = (path) => redirectBySource.get(path) ?? patternRedirects.find(({ matcher }) => matcher.test(path))?.rule;
|
|
41
42
|
const timeout = options.timeout ?? 240_000;
|
|
43
|
+
const families = options.families ?? [];
|
|
44
|
+
const familyPaths = new Set(families.flatMap((family) => family.paths.map(normalisePath)));
|
|
42
45
|
test.describe('Brandlab technical estate', () => {
|
|
43
46
|
test('the sitemap lists exactly the indexable catalogue routes', async ({ request }) => {
|
|
44
47
|
test.setTimeout(timeout);
|
|
@@ -94,7 +97,7 @@ export function defineBrandlabEstateTests(options) {
|
|
|
94
97
|
checked.push({ path: route.path, canonical: snapshot.canonical, jsonLd: snapshot.jsonLd.length });
|
|
95
98
|
});
|
|
96
99
|
for (const [path, from] of [...internalLinks].sort()) {
|
|
97
|
-
if (knownPaths.has(path))
|
|
100
|
+
if (knownPaths.has(path) || familyPaths.has(path))
|
|
98
101
|
continue;
|
|
99
102
|
const redirect = findRedirect(path);
|
|
100
103
|
if (redirect && isExternalDestination(redirect.destination))
|
|
@@ -153,6 +156,52 @@ export function defineBrandlabEstateTests(options) {
|
|
|
153
156
|
}
|
|
154
157
|
expect(failures).toEqual([]);
|
|
155
158
|
});
|
|
159
|
+
if (families.length > 0) {
|
|
160
|
+
test('every page family rendered on request serves its pages and answers 404 outside its space', async ({ request }) => {
|
|
161
|
+
test.setTimeout(timeout);
|
|
162
|
+
const failures = [];
|
|
163
|
+
const reference = await request.get(`/${UNKNOWN_SLUG}`, { maxRedirects: 0 });
|
|
164
|
+
const notFoundText = reference.status() === 404 ? bodyText(await reference.text()) : '';
|
|
165
|
+
if (!notFoundText)
|
|
166
|
+
failures.push(`/${UNKNOWN_SLUG}: ${reference.status()} with no server-rendered not-found page to compare with`);
|
|
167
|
+
for (const family of families) {
|
|
168
|
+
const sample = familySamplePath(family, knownPaths);
|
|
169
|
+
if (!sample) {
|
|
170
|
+
failures.push(`${family.pattern}: the family lists no page`);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const response = await request.get(sample, { maxRedirects: 0 });
|
|
174
|
+
if (response.status() !== 200) {
|
|
175
|
+
failures.push(`${sample}: ${response.status()} (expected 200 for a page of ${family.pattern})`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const html = await response.text();
|
|
179
|
+
const snapshot = extractSearchCertificationHtmlSnapshot(html);
|
|
180
|
+
const robots = `${snapshot.robots ?? ''} ${response.headers()['x-robots-tag'] ?? ''}`;
|
|
181
|
+
const expectedCanonical = canonical(sample);
|
|
182
|
+
if (!snapshot.canonical || new URL(snapshot.canonical, origin).href !== expectedCanonical) {
|
|
183
|
+
failures.push(`${sample}: canonical ${snapshot.canonical ?? 'missing'} (expected ${expectedCanonical})`);
|
|
184
|
+
}
|
|
185
|
+
if (family.indexing === 'index' && /noindex/i.test(robots))
|
|
186
|
+
failures.push(`${sample}: the family's pages are indexable, this one is noindex`);
|
|
187
|
+
if (family.indexing !== 'index' && !/noindex/i.test(robots))
|
|
188
|
+
failures.push(`${sample}: the family's pages are ${family.indexing}, this one is indexable`);
|
|
189
|
+
}
|
|
190
|
+
for (const outside of familyNotFoundPaths(family)) {
|
|
191
|
+
const miss = await request.get(outside, { maxRedirects: 0 });
|
|
192
|
+
if (miss.status() !== 404) {
|
|
193
|
+
failures.push(`${outside}: ${miss.status()} (expected 404 outside ${family.pattern}'s space)`);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const text = bodyText(await miss.text());
|
|
197
|
+
if (notFoundText && text !== notFoundText) {
|
|
198
|
+
failures.push(`${outside}: the 404 is not the site's not-found page rendered on the server (${text ? `it shows "${text.slice(0, 80)}"` : 'its body is empty until the browser renders it'})`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
expect(failures).toEqual([]);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
156
205
|
test('unknown slugs answer 404 instead of inventing pages', async ({ request }) => {
|
|
157
206
|
const paths = options.notFoundPaths ?? defaultNotFoundPaths(routes);
|
|
158
207
|
const failures = [];
|
|
@@ -212,6 +261,38 @@ export function defaultNotFoundPaths(routes) {
|
|
|
212
261
|
}
|
|
213
262
|
return [...parents].sort().map((parent) => `${parent}/${UNKNOWN_SLUG}`);
|
|
214
263
|
}
|
|
264
|
+
/** The page of a family its estate check opens: the first one outside the catalogue, else its first page. */
|
|
265
|
+
export function familySamplePath(family, catalogue) {
|
|
266
|
+
return family.paths.find((path) => !catalogue.has(normalisePath(path))) ?? family.paths[0];
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Paths of a family's pattern outside its space: a page of the family with one
|
|
270
|
+
* dynamic segment at a time replaced by an unknown slug.
|
|
271
|
+
*/
|
|
272
|
+
export function familyNotFoundPaths(family) {
|
|
273
|
+
const first = family.paths[0];
|
|
274
|
+
if (!first)
|
|
275
|
+
return [];
|
|
276
|
+
const parts = normalisePath(first).split('/');
|
|
277
|
+
const segments = family.pattern.split('/');
|
|
278
|
+
const paths = segments.flatMap((segment, index) => (/^\[[^.\]]/.test(segment) ? [parts.map((part, position) => (position === index ? UNKNOWN_SLUG : part)).join('/')] : []));
|
|
279
|
+
const space = new Set(family.paths.map(normalisePath));
|
|
280
|
+
return [...new Set(paths)].filter((path) => !space.has(path));
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* The text a page shows before any script runs: its body without scripts,
|
|
284
|
+
* styles and templates, tags removed and whitespace collapsed. Empty for the
|
|
285
|
+
* document Next sends when it leaves a page to the browser to render.
|
|
286
|
+
*/
|
|
287
|
+
export function bodyText(html) {
|
|
288
|
+
const body = /<body\b[^>]*>([\s\S]*?)(?:<\/body>|$)/i.exec(html)?.[1] ?? (/<html\b/i.test(html) ? '' : html);
|
|
289
|
+
return decodeXml(body
|
|
290
|
+
.replace(/<(script|style|template|noscript)\b[\s\S]*?<\/\1>/gi, ' ')
|
|
291
|
+
.replace(/<[^>]+>/g, ' '))
|
|
292
|
+
.replace(/ /g, ' ')
|
|
293
|
+
.replace(/\s+/g, ' ')
|
|
294
|
+
.trim();
|
|
295
|
+
}
|
|
215
296
|
/** A Next redirect path with parameters: `:name`, `:name*`, `:name+`, `:name?` or a regular-expression group. */
|
|
216
297
|
export function isRedirectPattern(path) {
|
|
217
298
|
return /:[A-Za-z_]\w*|\(/.test(path.replace(/^[a-z][a-z0-9+.-]*:\/\//i, ''));
|
package/dist/estate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"estate.js","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared technical-estate journey for a Brandlab production build:\n *\n * // tests/e2e/estate.spec.ts\n * import { defineBrandlabEstateTests } from '@brandlab-digital/conformance/estate'\n * import { site } from '../../src/config/site'\n * import { routes } from '../../src/data/routes'\n * import { redirects } from '../../src/data/redirects'\n *\n * defineBrandlabEstateTests({ site, routes, redirects })\n *\n * It asserts the estate and its routes: the sitemap, each route's status,\n * canonical and indexing, internal links, redirects, unknown slugs and\n * robots.txt. Page and structured-data quality is reported, never enforced,\n * by defineBrandlabPageQualityTests: a backend release keeps production's pages\n * as they are, which the comparison harness proves. Counts are always derived\n * from the current catalogue, never a historic constant.\n */\nimport { expect, test, type APIRequestContext } from '@playwright/test';\nimport { extractSearchCertificationHtmlSnapshot } from './search-certification.js';\n\nexport interface BrandlabEstateRoute {\n path: string;\n canonicalPath?: string;\n indexing: 'index' | 'noindex-follow' | 'noindex-nofollow';\n}\n\nexport interface BrandlabEstateRedirect {\n source: string;\n destination: string;\n status: 301 | 302 | 303 | 307 | 308;\n /** Host-scoped alias rules are proved by the comparison harness host probes. */\n host?: string;\n}\n\nexport interface BrandlabEstateTestOptions {\n site: { canonicalUrl: string | URL };\n /** The route catalogue. A `/404` route is Next's not-found page and must answer 404. */\n routes: readonly BrandlabEstateRoute[];\n /**\n * The redirect table. A source with parameters (`/old/:slug`, `:path*`) is\n * requested with one sample segment per parameter and checked against its\n * destination filled in the same way; a parameter with its own regular\n * expression cannot be sampled and is listed as skipped.\n */\n redirects?: readonly BrandlabEstateRedirect[];\n /** Paths that must answer 404; defaults to one unknown slug under every catalogue parent. */\n notFoundPaths?: readonly string[];\n /** Also prove `<source>/` redirects straight to the destination (the shared helper emits these). */\n trailingSlashRedirects?: boolean;\n concurrency?: number;\n timeout?: number;\n}\n\nexport interface BrandlabPageQualityTestOptions {\n site: { canonicalUrl: string | URL };\n routes: readonly BrandlabEstateRoute[];\n /** The @id every indexable page's graph references; defaults to `<origin>/#business`, `false` skips it. */\n businessId?: string | false;\n concurrency?: number;\n timeout?: number;\n}\n\nexport interface BrandlabPageQualityFinding {\n path: string;\n kind: 'headings' | 'title' | 'description' | 'og-url' | 'implementation-wording' | 'structured-data';\n detail: string;\n}\n\nconst BUSINESS_TYPES = new Set([\n 'LocalBusiness', 'HomeAndConstructionBusiness', 'GeneralContractor', 'Plumber', 'Electrician',\n 'RoofingContractor', 'HousePainter', 'Locksmith', 'MovingCompany', 'HVACBusiness', 'ProfessionalService',\n]);\nconst UNKNOWN_SLUG = 'brandlab-estate-unknown-page';\nconst NOT_FOUND_ROUTE = '/404';\nconst PATTERN_SAMPLE = 'brandlab-estate-sample';\nconst ASSET_PATH = /\\.[a-z0-9]{2,5}$/i;\n\nexport function defineBrandlabEstateTests(options: BrandlabEstateTestOptions) {\n const origin = new URL(String(options.site.canonicalUrl)).origin;\n const canonical = (path: string) => new URL(path, `${origin}/`).href;\n const routes = options.routes;\n const knownPaths = new Set(routes.map((route) => normalisePath(route.path)));\n const redirects = (options.redirects ?? []).filter((rule) => !rule.host);\n const redirectBySource = new Map(\n redirects.filter((rule) => !isRedirectPattern(rule.source)).map((rule) => [normalisePath(rule.source), rule]),\n );\n const patternRedirects = redirects.flatMap((rule) => {\n const matcher = isRedirectPattern(rule.source) ? redirectSourceMatcher(rule.source) : undefined;\n return matcher ? [{ rule, matcher }] : [];\n });\n const findRedirect = (path: string) =>\n redirectBySource.get(path) ?? patternRedirects.find(({ matcher }) => matcher.test(path))?.rule;\n const timeout = options.timeout ?? 240_000;\n\n test.describe('Brandlab technical estate', () => {\n test('the sitemap lists exactly the indexable catalogue routes', async ({ request }) => {\n test.setTimeout(timeout);\n const listed = await readSitemap(request, '/sitemap.xml', origin);\n const expected = routes\n .filter((route) => route.indexing === 'index')\n .map((route) => canonical(route.canonicalPath ?? route.path));\n expect(listed.length, 'the sitemap repeats a URL').toBe(new Set(listed).size);\n expect([...listed].sort()).toEqual([...new Set(expected)].sort());\n });\n\n test('every catalogue route serves its canonical identity and links only to live pages', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const internalLinks = new Map<string, string>();\n const checked: { path: string; canonical: string | null; jsonLd: number }[] = [];\n await eachWithConcurrency(routes, options.concurrency ?? 8, async (route) => {\n const [get, head] = await Promise.all([\n request.get(route.path, { maxRedirects: 0 }),\n request.head(route.path, { maxRedirects: 0 }),\n ]);\n // Next serves its not-found page at /404 with a 404 status.\n const notFound = normalisePath(route.path) === NOT_FOUND_ROUTE;\n const expectedStatus = notFound ? 404 : 200;\n if (get.status() !== expectedStatus || head.status() !== expectedStatus) {\n failures.push(`${route.path}: GET ${get.status()}, HEAD ${head.status()} (expected ${expectedStatus})`);\n return;\n }\n const html = await get.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const robots = `${snapshot.robots ?? ''} ${get.headers()['x-robots-tag'] ?? ''}`;\n if (notFound) {\n if (!/noindex/i.test(robots)) failures.push(`${route.path}: the not-found page is indexable`);\n checked.push({ path: route.path, canonical: snapshot.canonical, jsonLd: snapshot.jsonLd.length });\n return;\n }\n const expectedCanonical = canonical(route.canonicalPath ?? route.path);\n const canonicalTags = html.match(/<link\\b[^>]*\\brel=[\"']canonical[\"'][^>]*>/gi) ?? [];\n if (canonicalTags.length !== 1) failures.push(`${route.path}: ${canonicalTags.length} canonical links`);\n if (!snapshot.canonical || new URL(snapshot.canonical, origin).href !== expectedCanonical) {\n failures.push(`${route.path}: canonical ${snapshot.canonical ?? 'missing'} (expected ${expectedCanonical})`);\n }\n if (route.indexing === 'index' && /noindex/i.test(robots)) failures.push(`${route.path}: indexable route is noindex`);\n if (route.indexing !== 'index' && !/noindex/i.test(robots)) failures.push(`${route.path}: ${route.indexing} route is indexable`);\n for (const href of extractLinks(html)) {\n const path = internalPath(href, expectedCanonical, origin);\n if (path && !internalLinks.has(path)) internalLinks.set(path, route.path);\n }\n checked.push({ path: route.path, canonical: snapshot.canonical, jsonLd: snapshot.jsonLd.length });\n });\n for (const [path, from] of [...internalLinks].sort()) {\n if (knownPaths.has(path)) continue;\n const redirect = findRedirect(path);\n if (redirect && isExternalDestination(redirect.destination)) continue;\n failures.push(redirect\n ? `${from}: links to ${path}, which redirects; link to ${redirect.destination} directly`\n : `${from}: links to ${path}, which is not a catalogue route`);\n }\n await testInfo.attach('technical-estate.json', {\n body: JSON.stringify({ routes: checked.sort((a, b) => a.path.localeCompare(b.path)), internalLinks: [...internalLinks.keys()].sort() }, null, 2),\n contentType: 'application/json',\n });\n expect(failures).toEqual([]);\n });\n\n test('every redirect resolves in one hop and keeps the query string', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const skipped: string[] = [];\n for (const rule of redirects) {\n const probe = redirectProbe(rule);\n if (!probe) {\n skipped.push(`${rule.source} → ${rule.destination}`);\n continue;\n }\n const sources = [probe.source, ...(options.trailingSlashRedirects === false || probe.source === '/' || probe.source.endsWith('/') ? [] : [`${probe.source}/`])];\n for (const source of sources) {\n for (const method of ['GET', 'HEAD'] as const) {\n const response = await request.fetch(`${source}?brandlab_estate=1`, { method, maxRedirects: 0 });\n const location = response.headers().location;\n const expectedStatus: number[] = source === probe.source ? [rule.status] : [301, 308];\n if (!expectedStatus.includes(response.status()) || !location) {\n failures.push(`${method} ${source}: ${response.status()} (expected ${expectedStatus.join(' or ')} to ${probe.destination})`);\n continue;\n }\n const target = new URL(location, origin);\n if (isExternalDestination(probe.destination)) {\n if (!target.href.startsWith(new URL(probe.destination, origin).href.replace(/\\/$/, ''))) failures.push(`${method} ${source}: Location ${location} (expected ${probe.destination})`);\n continue;\n }\n if (target.pathname !== new URL(probe.destination, origin).pathname) failures.push(`${method} ${source}: Location ${location} (expected ${probe.destination})`);\n if (target.searchParams.get('brandlab_estate') !== '1') failures.push(`${method} ${source}: the query string was dropped`);\n }\n }\n // A destination with parameters names many pages; the sample fills it with a page that does not exist.\n if (!isExternalDestination(probe.destination) && !probe.sampled) {\n const destination = await request.get(probe.destination, { maxRedirects: 0 });\n if (destination.status() !== 200) failures.push(`${rule.source}: destination ${probe.destination} answers ${destination.status()} (a chain or a missing page)`);\n }\n }\n if (skipped.length > 0) {\n testInfo.annotations.push({ type: 'skipped-redirects', description: `${skipped.length} redirect(s) with a parameter pattern that cannot be sampled: ${skipped.join('; ')}` });\n }\n expect(failures).toEqual([]);\n });\n\n test('unknown slugs answer 404 instead of inventing pages', async ({ request }) => {\n const paths = options.notFoundPaths ?? defaultNotFoundPaths(routes);\n const failures: string[] = [];\n for (const path of paths) {\n const response = await request.get(path, { maxRedirects: 0 });\n if (response.status() !== 404) failures.push(`${path}: ${response.status()} (expected 404)`);\n }\n expect(failures).toEqual([]);\n });\n\n test('robots.txt allows crawling and names the sitemap', async ({ request }) => {\n const response = await request.get('/robots.txt');\n expect(response.status()).toBe(200);\n const body = await response.text();\n expect(body).toMatch(new RegExp(`^sitemap:\\\\s*${escapeRegExp(`${origin}/sitemap.xml`)}\\\\s*$`, 'im'));\n expect(body).not.toMatch(/^disallow:\\s*\\/\\s*$/im);\n });\n });\n}\n\n/**\n * Every `<loc>` in a sitemap, following one level of sitemap index. Nothing is\n * followed through a redirect, and with `origin` every nested sitemap must be\n * on that origin (it is then fetched from the build under test).\n */\nexport async function readSitemap(request: APIRequestContext, path: string, origin?: string): Promise<string[]> {\n const read = async (target: string) => {\n const response = await request.get(target, { maxRedirects: 0 });\n if (response.status() !== 200) throw new Error(`${target} answers ${response.status()}; a sitemap must answer 200 without redirecting.`);\n return response.text();\n };\n const xml = await read(path);\n const locations = extractSitemapLocations(xml);\n if (!/<sitemapindex\\b/i.test(xml)) return locations;\n const nested: string[] = [];\n for (const location of locations) {\n const url = new URL(location);\n if (origin && url.origin !== new URL(origin).origin) {\n throw new Error(`The sitemap index lists ${location}, which is not on ${new URL(origin).origin}.`);\n }\n nested.push(...extractSitemapLocations(await read(`${url.pathname}${url.search}`)));\n }\n return nested;\n}\n\nexport function extractSitemapLocations(xml: string): string[] {\n return [...xml.matchAll(/<loc>\\s*([^<\\s]+)\\s*<\\/loc>/gi)].map((match) =>\n new URL(decodeXml(match[1]!)).href);\n}\n\nexport function defaultNotFoundPaths(routes: readonly BrandlabEstateRoute[]): string[] {\n const parents = new Set(['']);\n for (const route of routes) {\n const segments = normalisePath(route.path).split('/').filter(Boolean);\n if (segments.length > 1) parents.add(`/${segments.slice(0, -1).join('/')}`);\n if (segments.length === 1) parents.add(`/${segments[0]}`);\n }\n return [...parents].sort().map((parent) => `${parent}/${UNKNOWN_SLUG}`);\n}\n\n/** A Next redirect path with parameters: `:name`, `:name*`, `:name+`, `:name?` or a regular-expression group. */\nexport function isRedirectPattern(path: string): boolean {\n return /:[A-Za-z_]\\w*|\\(/.test(path.replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, ''));\n}\n\n/**\n * The request that proves a redirect rule: a pattern gets one sample segment\n * per parameter, in its source and destination alike. Undefined when a\n * parameter carries its own regular expression, which no sample can satisfy\n * with certainty.\n */\nexport function redirectProbe(rule: Pick<BrandlabEstateRedirect, 'source' | 'destination'>): { source: string; destination: string; sampled: boolean } | undefined {\n const sample = (path: string) => {\n if (!isRedirectPattern(path)) return path;\n // Keep an external destination's scheme and host; parameters live in the path.\n const origin = /^[a-z][a-z0-9+.-]*:\\/\\/[^/]*/i.exec(path)?.[0] ?? '';\n const rest = path.slice(origin.length);\n if (rest.includes('(')) return undefined;\n return `${origin}${rest.replace(/:[A-Za-z_]\\w*[*+?]?/g, PATTERN_SAMPLE)}`;\n };\n const source = sample(rule.source);\n const destination = sample(rule.destination);\n if (source === undefined || destination === undefined) return undefined;\n return { source, destination, sampled: isRedirectPattern(rule.destination) };\n}\n\n/** A matcher for paths a pattern source redirects, for internal-link checks. */\nfunction redirectSourceMatcher(source: string): RegExp | undefined {\n if (source.includes('(')) return undefined;\n const pattern = normalisePath(source).split(/(:[A-Za-z_]\\w*[*+?]?)/).map((part) => {\n const parameter = /^:[A-Za-z_]\\w*([*+?]?)$/.exec(part);\n if (!parameter) return escapeRegExp(part);\n return parameter[1] === '*' || parameter[1] === '+' ? '.+' : parameter[1] === '?' ? '[^/]*' : '[^/]+';\n }).join('');\n return new RegExp(`^${pattern}$`);\n}\n\n/**\n * The advisory page-quality journey: headings, title, description, og:url,\n * implementation wording and structured-data identity for every catalogue\n * page. It reports what it finds (page-quality.json and an annotation) and\n * never fails, because a backend release keeps production's pages as they\n * are; the comparison harness proves them unchanged.\n *\n * // tests/e2e/page-quality.spec.ts\n * defineBrandlabPageQualityTests({ site, routes })\n */\nexport function defineBrandlabPageQualityTests(options: BrandlabPageQualityTestOptions) {\n const origin = new URL(String(options.site.canonicalUrl)).origin;\n const canonical = (path: string) => new URL(path, `${origin}/`).href;\n const businessId = options.businessId ?? `${origin}/#business`;\n const timeout = options.timeout ?? 240_000;\n const routes = options.routes.filter((route) => normalisePath(route.path) !== NOT_FOUND_ROUTE);\n\n test.describe('Brandlab page quality (advisory)', () => {\n test('reports page and structured-data quality without failing', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const findings: BrandlabPageQualityFinding[] = [];\n await eachWithConcurrency(routes, options.concurrency ?? 8, async (route) => {\n const response = await request.get(route.path, { maxRedirects: 0 });\n // The estate journey owns status codes; quality is read from the pages that load.\n if (response.status() !== 200) return;\n const html = await response.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const add = (kind: BrandlabPageQualityFinding['kind'], detail: string) => findings.push({ path: route.path, kind, detail });\n const headings = html.match(/<h1(?:\\s|>)/gi)?.length ?? 0;\n if (headings !== 1) add('headings', `${headings} h1 headings`);\n if (!snapshot.title) add('title', 'no title');\n if (!snapshot.description) add('description', 'no meta description');\n const expectedCanonical = canonical(route.canonicalPath ?? route.path);\n const ogUrl = snapshot.openGraph['og:url'];\n if (ogUrl && new URL(ogUrl, origin).href !== expectedCanonical) add('og-url', `og:url ${ogUrl} is not the canonical URL`);\n if (snapshot.implementationCopyMatches.length > 0) add('implementation-wording', snapshot.implementationCopyMatches.join(', '));\n for (const failure of inspectStructuredData(route, snapshot.jsonLd, businessId)) {\n add('structured-data', failure.slice(route.path.length + 2));\n }\n });\n findings.sort((a, b) => a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind));\n const byKind = Object.fromEntries(\n [...new Set(findings.map((finding) => finding.kind))].sort().map((kind) => [kind, findings.filter((finding) => finding.kind === kind).length]),\n );\n await testInfo.attach('page-quality.json', {\n body: JSON.stringify({ pages: routes.length, findings: findings.length, byKind, details: findings }, null, 2),\n contentType: 'application/json',\n });\n testInfo.annotations.push({\n type: 'advisory',\n description: `${findings.length} page-quality finding(s) across ${routes.length} page(s)${findings.length ? `: ${Object.entries(byKind).map(([kind, count]) => `${kind} ${count}`).join(', ')}` : ''}; see page-quality.json.`,\n });\n });\n });\n}\n\nfunction inspectStructuredData(route: BrandlabEstateRoute, payloads: readonly unknown[], businessId: string | false): string[] {\n const failures: string[] = [];\n if (payloads.some((payload) => (payload as { parseError?: boolean }).parseError)) failures.push(`${route.path}: invalid JSON-LD`);\n if (route.indexing === 'index' && payloads.length === 0) failures.push(`${route.path}: no JSON-LD`);\n const ids: string[] = [];\n let referencesBusiness = false;\n const visit = (value: unknown) => {\n if (!value || typeof value !== 'object') return;\n if (Array.isArray(value)) { value.forEach(visit); return; }\n const node = value as Record<string, unknown>;\n const types = ([] as unknown[]).concat(node['@type'] ?? []).map(String);\n if (typeof node['@id'] === 'string') {\n if (node['@id'] === businessId) referencesBusiness = true;\n if (types.length > 0) ids.push(node['@id']);\n }\n if (businessId && types.some((type) => BUSINESS_TYPES.has(type)) && node['@id'] !== businessId) {\n failures.push(`${route.path}: business node @id ${String(node['@id'] ?? 'missing')} (expected ${businessId})`);\n }\n Object.values(node).forEach(visit);\n };\n payloads.forEach(visit);\n const duplicate = ids.find((id, index) => ids.indexOf(id) !== index);\n if (duplicate) failures.push(`${route.path}: JSON-LD repeats node ${duplicate}`);\n if (businessId && route.indexing === 'index' && payloads.length > 0 && !referencesBusiness) failures.push(`${route.path}: JSON-LD does not reference ${businessId}`);\n return failures;\n}\n\nfunction extractLinks(html: string): string[] {\n return [...html.matchAll(/<a\\b[^>]*\\bhref=[\"']([^\"']+)[\"']/gi)].map((match) => decodeXml(match[1]!));\n}\n\nfunction internalPath(href: string, base: string, origin: string): string | null {\n if (/^(?:mailto|tel|sms|javascript):/i.test(href) || href.startsWith('#')) return null;\n let url: URL;\n try { url = new URL(href, base); } catch { return null; }\n if (url.origin !== origin && !['127.0.0.1', 'localhost'].includes(url.hostname)) return null;\n if (ASSET_PATH.test(url.pathname) || url.pathname.startsWith('/api/') || url.pathname.startsWith('/_next/')) return null;\n return normalisePath(url.pathname);\n}\n\nfunction normalisePath(path: string): string {\n const pathname = path.split(/[?#]/)[0] || '/';\n return pathname.length > 1 ? pathname.replace(/\\/+$/, '') : '/';\n}\n\n/**\n * Absolute (`https://host`) and protocol-relative (`//host`) destinations leave\n * the site; browsers also read `/\\host` and `\\\\host` as protocol-relative.\n */\nexport function isExternalDestination(destination: string): boolean {\n return /^(?:[a-z][a-z0-9+.-]*:)?[\\\\/]{2}/i.test(destination.trim());\n}\n\nfunction decodeXml(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '\"').replace(/'/g, \"'\");\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nasync function eachWithConcurrency<T>(items: readonly T[], limit: number, task: (item: T) => Promise<void>) {\n const queue = [...items];\n await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, queue.length)) }, async () => {\n for (let item = queue.shift(); item !== undefined; item = queue.shift()) await task(item);\n }));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"estate.js","sourceRoot":"","sources":["../src/estate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The shared technical-estate journey for a Brandlab production build:\n *\n * // tests/e2e/estate.spec.ts\n * import { defineBrandlabEstateTests } from '@brandlab-digital/conformance/estate'\n * import { site } from '../../src/config/site'\n * import { routes } from '../../src/data/routes'\n * import { redirects } from '../../src/data/redirects'\n *\n * defineBrandlabEstateTests({ site, routes, redirects })\n *\n * It asserts the estate and its routes: the sitemap, each route's status,\n * canonical and indexing, internal links, redirects, unknown slugs, the page\n * families rendered on request and robots.txt. Page and structured-data\n * quality is reported, never enforced,\n * by defineBrandlabPageQualityTests: a backend release keeps production's pages\n * as they are, which the comparison harness proves. Counts are always derived\n * from the current catalogue, never a historic constant.\n */\nimport { expect, test, type APIRequestContext } from '@playwright/test';\nimport { extractSearchCertificationHtmlSnapshot } from './search-certification.js';\n\nexport interface BrandlabEstateRoute {\n path: string;\n canonicalPath?: string;\n indexing: 'index' | 'noindex-follow' | 'noindex-nofollow';\n}\n\nexport interface BrandlabEstateRedirect {\n source: string;\n destination: string;\n status: 301 | 302 | 303 | 307 | 308;\n /** Host-scoped alias rules are proved by the comparison harness host probes. */\n host?: string;\n}\n\n/**\n * A page family the site renders on request outside the catalogue: a dynamic\n * route that prerenders nothing and renders every page of its space (every\n * combination of its values) when it is first requested.\n */\nexport interface BrandlabEstateFamily {\n /** The app route, e.g. `/services/[service]/[location]`. */\n pattern: string;\n /** Every page of the family's space; catalogued pages of the route may be among them. */\n paths: readonly string[];\n /** The indexing its pages outside the catalogue render. */\n indexing: BrandlabEstateRoute['indexing'];\n}\n\nexport interface BrandlabEstateTestOptions {\n site: { canonicalUrl: string | URL };\n /** The route catalogue. A `/404` route is Next's not-found page and must answer 404. */\n routes: readonly BrandlabEstateRoute[];\n /**\n * The redirect table. A source with parameters (`/old/:slug`, `:path*`) is\n * requested with one sample segment per parameter and checked against its\n * destination filled in the same way; a parameter with its own regular\n * expression cannot be sampled and is listed as skipped.\n */\n redirects?: readonly BrandlabEstateRedirect[];\n /** Paths that must answer 404; defaults to one unknown slug under every catalogue parent. */\n notFoundPaths?: readonly string[];\n /** Also prove `<source>/` redirects straight to the destination (the shared helper emits these). */\n trailingSlashRedirects?: boolean;\n /**\n * Page families rendered on request outside the catalogue. Links into a\n * family's space count as live pages; one page of each family (outside the\n * catalogue where it has one) is checked for its status, canonical and\n * indexing; and a path of its pattern outside the space, one unknown value\n * per dynamic segment, must answer 404 with the site's not-found page rendered\n * on the server: the text an unknown top-level path's 404 page shows.\n */\n families?: readonly BrandlabEstateFamily[];\n concurrency?: number;\n timeout?: number;\n}\n\nexport interface BrandlabPageQualityTestOptions {\n site: { canonicalUrl: string | URL };\n routes: readonly BrandlabEstateRoute[];\n /** The @id every indexable page's graph references; defaults to `<origin>/#business`, `false` skips it. */\n businessId?: string | false;\n concurrency?: number;\n timeout?: number;\n}\n\nexport interface BrandlabPageQualityFinding {\n path: string;\n kind: 'headings' | 'title' | 'description' | 'og-url' | 'implementation-wording' | 'structured-data';\n detail: string;\n}\n\nconst BUSINESS_TYPES = new Set([\n 'LocalBusiness', 'HomeAndConstructionBusiness', 'GeneralContractor', 'Plumber', 'Electrician',\n 'RoofingContractor', 'HousePainter', 'Locksmith', 'MovingCompany', 'HVACBusiness', 'ProfessionalService',\n]);\nconst UNKNOWN_SLUG = 'brandlab-estate-unknown-page';\nconst NOT_FOUND_ROUTE = '/404';\nconst PATTERN_SAMPLE = 'brandlab-estate-sample';\nconst ASSET_PATH = /\\.[a-z0-9]{2,5}$/i;\n\nexport function defineBrandlabEstateTests(options: BrandlabEstateTestOptions) {\n const origin = new URL(String(options.site.canonicalUrl)).origin;\n const canonical = (path: string) => new URL(path, `${origin}/`).href;\n const routes = options.routes;\n const knownPaths = new Set(routes.map((route) => normalisePath(route.path)));\n const redirects = (options.redirects ?? []).filter((rule) => !rule.host);\n const redirectBySource = new Map(\n redirects.filter((rule) => !isRedirectPattern(rule.source)).map((rule) => [normalisePath(rule.source), rule]),\n );\n const patternRedirects = redirects.flatMap((rule) => {\n const matcher = isRedirectPattern(rule.source) ? redirectSourceMatcher(rule.source) : undefined;\n return matcher ? [{ rule, matcher }] : [];\n });\n const findRedirect = (path: string) =>\n redirectBySource.get(path) ?? patternRedirects.find(({ matcher }) => matcher.test(path))?.rule;\n const timeout = options.timeout ?? 240_000;\n const families = options.families ?? [];\n const familyPaths = new Set(families.flatMap((family) => family.paths.map(normalisePath)));\n\n test.describe('Brandlab technical estate', () => {\n test('the sitemap lists exactly the indexable catalogue routes', async ({ request }) => {\n test.setTimeout(timeout);\n const listed = await readSitemap(request, '/sitemap.xml', origin);\n const expected = routes\n .filter((route) => route.indexing === 'index')\n .map((route) => canonical(route.canonicalPath ?? route.path));\n expect(listed.length, 'the sitemap repeats a URL').toBe(new Set(listed).size);\n expect([...listed].sort()).toEqual([...new Set(expected)].sort());\n });\n\n test('every catalogue route serves its canonical identity and links only to live pages', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const internalLinks = new Map<string, string>();\n const checked: { path: string; canonical: string | null; jsonLd: number }[] = [];\n await eachWithConcurrency(routes, options.concurrency ?? 8, async (route) => {\n const [get, head] = await Promise.all([\n request.get(route.path, { maxRedirects: 0 }),\n request.head(route.path, { maxRedirects: 0 }),\n ]);\n // Next serves its not-found page at /404 with a 404 status.\n const notFound = normalisePath(route.path) === NOT_FOUND_ROUTE;\n const expectedStatus = notFound ? 404 : 200;\n if (get.status() !== expectedStatus || head.status() !== expectedStatus) {\n failures.push(`${route.path}: GET ${get.status()}, HEAD ${head.status()} (expected ${expectedStatus})`);\n return;\n }\n const html = await get.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const robots = `${snapshot.robots ?? ''} ${get.headers()['x-robots-tag'] ?? ''}`;\n if (notFound) {\n if (!/noindex/i.test(robots)) failures.push(`${route.path}: the not-found page is indexable`);\n checked.push({ path: route.path, canonical: snapshot.canonical, jsonLd: snapshot.jsonLd.length });\n return;\n }\n const expectedCanonical = canonical(route.canonicalPath ?? route.path);\n const canonicalTags = html.match(/<link\\b[^>]*\\brel=[\"']canonical[\"'][^>]*>/gi) ?? [];\n if (canonicalTags.length !== 1) failures.push(`${route.path}: ${canonicalTags.length} canonical links`);\n if (!snapshot.canonical || new URL(snapshot.canonical, origin).href !== expectedCanonical) {\n failures.push(`${route.path}: canonical ${snapshot.canonical ?? 'missing'} (expected ${expectedCanonical})`);\n }\n if (route.indexing === 'index' && /noindex/i.test(robots)) failures.push(`${route.path}: indexable route is noindex`);\n if (route.indexing !== 'index' && !/noindex/i.test(robots)) failures.push(`${route.path}: ${route.indexing} route is indexable`);\n for (const href of extractLinks(html)) {\n const path = internalPath(href, expectedCanonical, origin);\n if (path && !internalLinks.has(path)) internalLinks.set(path, route.path);\n }\n checked.push({ path: route.path, canonical: snapshot.canonical, jsonLd: snapshot.jsonLd.length });\n });\n for (const [path, from] of [...internalLinks].sort()) {\n if (knownPaths.has(path) || familyPaths.has(path)) continue;\n const redirect = findRedirect(path);\n if (redirect && isExternalDestination(redirect.destination)) continue;\n failures.push(redirect\n ? `${from}: links to ${path}, which redirects; link to ${redirect.destination} directly`\n : `${from}: links to ${path}, which is not a catalogue route`);\n }\n await testInfo.attach('technical-estate.json', {\n body: JSON.stringify({ routes: checked.sort((a, b) => a.path.localeCompare(b.path)), internalLinks: [...internalLinks.keys()].sort() }, null, 2),\n contentType: 'application/json',\n });\n expect(failures).toEqual([]);\n });\n\n test('every redirect resolves in one hop and keeps the query string', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const skipped: string[] = [];\n for (const rule of redirects) {\n const probe = redirectProbe(rule);\n if (!probe) {\n skipped.push(`${rule.source} → ${rule.destination}`);\n continue;\n }\n const sources = [probe.source, ...(options.trailingSlashRedirects === false || probe.source === '/' || probe.source.endsWith('/') ? [] : [`${probe.source}/`])];\n for (const source of sources) {\n for (const method of ['GET', 'HEAD'] as const) {\n const response = await request.fetch(`${source}?brandlab_estate=1`, { method, maxRedirects: 0 });\n const location = response.headers().location;\n const expectedStatus: number[] = source === probe.source ? [rule.status] : [301, 308];\n if (!expectedStatus.includes(response.status()) || !location) {\n failures.push(`${method} ${source}: ${response.status()} (expected ${expectedStatus.join(' or ')} to ${probe.destination})`);\n continue;\n }\n const target = new URL(location, origin);\n if (isExternalDestination(probe.destination)) {\n if (!target.href.startsWith(new URL(probe.destination, origin).href.replace(/\\/$/, ''))) failures.push(`${method} ${source}: Location ${location} (expected ${probe.destination})`);\n continue;\n }\n if (target.pathname !== new URL(probe.destination, origin).pathname) failures.push(`${method} ${source}: Location ${location} (expected ${probe.destination})`);\n if (target.searchParams.get('brandlab_estate') !== '1') failures.push(`${method} ${source}: the query string was dropped`);\n }\n }\n // A destination with parameters names many pages; the sample fills it with a page that does not exist.\n if (!isExternalDestination(probe.destination) && !probe.sampled) {\n const destination = await request.get(probe.destination, { maxRedirects: 0 });\n if (destination.status() !== 200) failures.push(`${rule.source}: destination ${probe.destination} answers ${destination.status()} (a chain or a missing page)`);\n }\n }\n if (skipped.length > 0) {\n testInfo.annotations.push({ type: 'skipped-redirects', description: `${skipped.length} redirect(s) with a parameter pattern that cannot be sampled: ${skipped.join('; ')}` });\n }\n expect(failures).toEqual([]);\n });\n\n if (families.length > 0) {\n test('every page family rendered on request serves its pages and answers 404 outside its space', async ({ request }) => {\n test.setTimeout(timeout);\n const failures: string[] = [];\n const reference = await request.get(`/${UNKNOWN_SLUG}`, { maxRedirects: 0 });\n const notFoundText = reference.status() === 404 ? bodyText(await reference.text()) : '';\n if (!notFoundText) failures.push(`/${UNKNOWN_SLUG}: ${reference.status()} with no server-rendered not-found page to compare with`);\n for (const family of families) {\n const sample = familySamplePath(family, knownPaths);\n if (!sample) {\n failures.push(`${family.pattern}: the family lists no page`);\n continue;\n }\n const response = await request.get(sample, { maxRedirects: 0 });\n if (response.status() !== 200) {\n failures.push(`${sample}: ${response.status()} (expected 200 for a page of ${family.pattern})`);\n } else {\n const html = await response.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const robots = `${snapshot.robots ?? ''} ${response.headers()['x-robots-tag'] ?? ''}`;\n const expectedCanonical = canonical(sample);\n if (!snapshot.canonical || new URL(snapshot.canonical, origin).href !== expectedCanonical) {\n failures.push(`${sample}: canonical ${snapshot.canonical ?? 'missing'} (expected ${expectedCanonical})`);\n }\n if (family.indexing === 'index' && /noindex/i.test(robots)) failures.push(`${sample}: the family's pages are indexable, this one is noindex`);\n if (family.indexing !== 'index' && !/noindex/i.test(robots)) failures.push(`${sample}: the family's pages are ${family.indexing}, this one is indexable`);\n }\n for (const outside of familyNotFoundPaths(family)) {\n const miss = await request.get(outside, { maxRedirects: 0 });\n if (miss.status() !== 404) {\n failures.push(`${outside}: ${miss.status()} (expected 404 outside ${family.pattern}'s space)`);\n continue;\n }\n const text = bodyText(await miss.text());\n if (notFoundText && text !== notFoundText) {\n failures.push(`${outside}: the 404 is not the site's not-found page rendered on the server (${text ? `it shows \"${text.slice(0, 80)}\"` : 'its body is empty until the browser renders it'})`);\n }\n }\n }\n expect(failures).toEqual([]);\n });\n }\n\n test('unknown slugs answer 404 instead of inventing pages', async ({ request }) => {\n const paths = options.notFoundPaths ?? defaultNotFoundPaths(routes);\n const failures: string[] = [];\n for (const path of paths) {\n const response = await request.get(path, { maxRedirects: 0 });\n if (response.status() !== 404) failures.push(`${path}: ${response.status()} (expected 404)`);\n }\n expect(failures).toEqual([]);\n });\n\n test('robots.txt allows crawling and names the sitemap', async ({ request }) => {\n const response = await request.get('/robots.txt');\n expect(response.status()).toBe(200);\n const body = await response.text();\n expect(body).toMatch(new RegExp(`^sitemap:\\\\s*${escapeRegExp(`${origin}/sitemap.xml`)}\\\\s*$`, 'im'));\n expect(body).not.toMatch(/^disallow:\\s*\\/\\s*$/im);\n });\n });\n}\n\n/**\n * Every `<loc>` in a sitemap, following one level of sitemap index. Nothing is\n * followed through a redirect, and with `origin` every nested sitemap must be\n * on that origin (it is then fetched from the build under test).\n */\nexport async function readSitemap(request: APIRequestContext, path: string, origin?: string): Promise<string[]> {\n const read = async (target: string) => {\n const response = await request.get(target, { maxRedirects: 0 });\n if (response.status() !== 200) throw new Error(`${target} answers ${response.status()}; a sitemap must answer 200 without redirecting.`);\n return response.text();\n };\n const xml = await read(path);\n const locations = extractSitemapLocations(xml);\n if (!/<sitemapindex\\b/i.test(xml)) return locations;\n const nested: string[] = [];\n for (const location of locations) {\n const url = new URL(location);\n if (origin && url.origin !== new URL(origin).origin) {\n throw new Error(`The sitemap index lists ${location}, which is not on ${new URL(origin).origin}.`);\n }\n nested.push(...extractSitemapLocations(await read(`${url.pathname}${url.search}`)));\n }\n return nested;\n}\n\nexport function extractSitemapLocations(xml: string): string[] {\n return [...xml.matchAll(/<loc>\\s*([^<\\s]+)\\s*<\\/loc>/gi)].map((match) =>\n new URL(decodeXml(match[1]!)).href);\n}\n\nexport function defaultNotFoundPaths(routes: readonly BrandlabEstateRoute[]): string[] {\n const parents = new Set(['']);\n for (const route of routes) {\n const segments = normalisePath(route.path).split('/').filter(Boolean);\n if (segments.length > 1) parents.add(`/${segments.slice(0, -1).join('/')}`);\n if (segments.length === 1) parents.add(`/${segments[0]}`);\n }\n return [...parents].sort().map((parent) => `${parent}/${UNKNOWN_SLUG}`);\n}\n\n/** The page of a family its estate check opens: the first one outside the catalogue, else its first page. */\nexport function familySamplePath(family: Pick<BrandlabEstateFamily, 'paths'>, catalogue: ReadonlySet<string>): string | undefined {\n return family.paths.find((path) => !catalogue.has(normalisePath(path))) ?? family.paths[0];\n}\n\n/**\n * Paths of a family's pattern outside its space: a page of the family with one\n * dynamic segment at a time replaced by an unknown slug.\n */\nexport function familyNotFoundPaths(family: Pick<BrandlabEstateFamily, 'pattern' | 'paths'>): string[] {\n const first = family.paths[0];\n if (!first) return [];\n const parts = normalisePath(first).split('/');\n const segments = family.pattern.split('/');\n const paths = segments.flatMap((segment, index) => (/^\\[[^.\\]]/.test(segment) ? [parts.map((part, position) => (position === index ? UNKNOWN_SLUG : part)).join('/')] : []));\n const space = new Set(family.paths.map(normalisePath));\n return [...new Set(paths)].filter((path) => !space.has(path));\n}\n\n/**\n * The text a page shows before any script runs: its body without scripts,\n * styles and templates, tags removed and whitespace collapsed. Empty for the\n * document Next sends when it leaves a page to the browser to render.\n */\nexport function bodyText(html: string): string {\n const body = /<body\\b[^>]*>([\\s\\S]*?)(?:<\\/body>|$)/i.exec(html)?.[1] ?? (/<html\\b/i.test(html) ? '' : html);\n return decodeXml(body\n .replace(/<(script|style|template|noscript)\\b[\\s\\S]*?<\\/\\1>/gi, ' ')\n .replace(/<[^>]+>/g, ' '))\n .replace(/ /g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** A Next redirect path with parameters: `:name`, `:name*`, `:name+`, `:name?` or a regular-expression group. */\nexport function isRedirectPattern(path: string): boolean {\n return /:[A-Za-z_]\\w*|\\(/.test(path.replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, ''));\n}\n\n/**\n * The request that proves a redirect rule: a pattern gets one sample segment\n * per parameter, in its source and destination alike. Undefined when a\n * parameter carries its own regular expression, which no sample can satisfy\n * with certainty.\n */\nexport function redirectProbe(rule: Pick<BrandlabEstateRedirect, 'source' | 'destination'>): { source: string; destination: string; sampled: boolean } | undefined {\n const sample = (path: string) => {\n if (!isRedirectPattern(path)) return path;\n // Keep an external destination's scheme and host; parameters live in the path.\n const origin = /^[a-z][a-z0-9+.-]*:\\/\\/[^/]*/i.exec(path)?.[0] ?? '';\n const rest = path.slice(origin.length);\n if (rest.includes('(')) return undefined;\n return `${origin}${rest.replace(/:[A-Za-z_]\\w*[*+?]?/g, PATTERN_SAMPLE)}`;\n };\n const source = sample(rule.source);\n const destination = sample(rule.destination);\n if (source === undefined || destination === undefined) return undefined;\n return { source, destination, sampled: isRedirectPattern(rule.destination) };\n}\n\n/** A matcher for paths a pattern source redirects, for internal-link checks. */\nfunction redirectSourceMatcher(source: string): RegExp | undefined {\n if (source.includes('(')) return undefined;\n const pattern = normalisePath(source).split(/(:[A-Za-z_]\\w*[*+?]?)/).map((part) => {\n const parameter = /^:[A-Za-z_]\\w*([*+?]?)$/.exec(part);\n if (!parameter) return escapeRegExp(part);\n return parameter[1] === '*' || parameter[1] === '+' ? '.+' : parameter[1] === '?' ? '[^/]*' : '[^/]+';\n }).join('');\n return new RegExp(`^${pattern}$`);\n}\n\n/**\n * The advisory page-quality journey: headings, title, description, og:url,\n * implementation wording and structured-data identity for every catalogue\n * page. It reports what it finds (page-quality.json and an annotation) and\n * never fails, because a backend release keeps production's pages as they\n * are; the comparison harness proves them unchanged.\n *\n * // tests/e2e/page-quality.spec.ts\n * defineBrandlabPageQualityTests({ site, routes })\n */\nexport function defineBrandlabPageQualityTests(options: BrandlabPageQualityTestOptions) {\n const origin = new URL(String(options.site.canonicalUrl)).origin;\n const canonical = (path: string) => new URL(path, `${origin}/`).href;\n const businessId = options.businessId ?? `${origin}/#business`;\n const timeout = options.timeout ?? 240_000;\n const routes = options.routes.filter((route) => normalisePath(route.path) !== NOT_FOUND_ROUTE);\n\n test.describe('Brandlab page quality (advisory)', () => {\n test('reports page and structured-data quality without failing', async ({ request }, testInfo) => {\n test.setTimeout(timeout);\n const findings: BrandlabPageQualityFinding[] = [];\n await eachWithConcurrency(routes, options.concurrency ?? 8, async (route) => {\n const response = await request.get(route.path, { maxRedirects: 0 });\n // The estate journey owns status codes; quality is read from the pages that load.\n if (response.status() !== 200) return;\n const html = await response.text();\n const snapshot = extractSearchCertificationHtmlSnapshot(html);\n const add = (kind: BrandlabPageQualityFinding['kind'], detail: string) => findings.push({ path: route.path, kind, detail });\n const headings = html.match(/<h1(?:\\s|>)/gi)?.length ?? 0;\n if (headings !== 1) add('headings', `${headings} h1 headings`);\n if (!snapshot.title) add('title', 'no title');\n if (!snapshot.description) add('description', 'no meta description');\n const expectedCanonical = canonical(route.canonicalPath ?? route.path);\n const ogUrl = snapshot.openGraph['og:url'];\n if (ogUrl && new URL(ogUrl, origin).href !== expectedCanonical) add('og-url', `og:url ${ogUrl} is not the canonical URL`);\n if (snapshot.implementationCopyMatches.length > 0) add('implementation-wording', snapshot.implementationCopyMatches.join(', '));\n for (const failure of inspectStructuredData(route, snapshot.jsonLd, businessId)) {\n add('structured-data', failure.slice(route.path.length + 2));\n }\n });\n findings.sort((a, b) => a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind));\n const byKind = Object.fromEntries(\n [...new Set(findings.map((finding) => finding.kind))].sort().map((kind) => [kind, findings.filter((finding) => finding.kind === kind).length]),\n );\n await testInfo.attach('page-quality.json', {\n body: JSON.stringify({ pages: routes.length, findings: findings.length, byKind, details: findings }, null, 2),\n contentType: 'application/json',\n });\n testInfo.annotations.push({\n type: 'advisory',\n description: `${findings.length} page-quality finding(s) across ${routes.length} page(s)${findings.length ? `: ${Object.entries(byKind).map(([kind, count]) => `${kind} ${count}`).join(', ')}` : ''}; see page-quality.json.`,\n });\n });\n });\n}\n\nfunction inspectStructuredData(route: BrandlabEstateRoute, payloads: readonly unknown[], businessId: string | false): string[] {\n const failures: string[] = [];\n if (payloads.some((payload) => (payload as { parseError?: boolean }).parseError)) failures.push(`${route.path}: invalid JSON-LD`);\n if (route.indexing === 'index' && payloads.length === 0) failures.push(`${route.path}: no JSON-LD`);\n const ids: string[] = [];\n let referencesBusiness = false;\n const visit = (value: unknown) => {\n if (!value || typeof value !== 'object') return;\n if (Array.isArray(value)) { value.forEach(visit); return; }\n const node = value as Record<string, unknown>;\n const types = ([] as unknown[]).concat(node['@type'] ?? []).map(String);\n if (typeof node['@id'] === 'string') {\n if (node['@id'] === businessId) referencesBusiness = true;\n if (types.length > 0) ids.push(node['@id']);\n }\n if (businessId && types.some((type) => BUSINESS_TYPES.has(type)) && node['@id'] !== businessId) {\n failures.push(`${route.path}: business node @id ${String(node['@id'] ?? 'missing')} (expected ${businessId})`);\n }\n Object.values(node).forEach(visit);\n };\n payloads.forEach(visit);\n const duplicate = ids.find((id, index) => ids.indexOf(id) !== index);\n if (duplicate) failures.push(`${route.path}: JSON-LD repeats node ${duplicate}`);\n if (businessId && route.indexing === 'index' && payloads.length > 0 && !referencesBusiness) failures.push(`${route.path}: JSON-LD does not reference ${businessId}`);\n return failures;\n}\n\nfunction extractLinks(html: string): string[] {\n return [...html.matchAll(/<a\\b[^>]*\\bhref=[\"']([^\"']+)[\"']/gi)].map((match) => decodeXml(match[1]!));\n}\n\nfunction internalPath(href: string, base: string, origin: string): string | null {\n if (/^(?:mailto|tel|sms|javascript):/i.test(href) || href.startsWith('#')) return null;\n let url: URL;\n try { url = new URL(href, base); } catch { return null; }\n if (url.origin !== origin && !['127.0.0.1', 'localhost'].includes(url.hostname)) return null;\n if (ASSET_PATH.test(url.pathname) || url.pathname.startsWith('/api/') || url.pathname.startsWith('/_next/')) return null;\n return normalisePath(url.pathname);\n}\n\nfunction normalisePath(path: string): string {\n const pathname = path.split(/[?#]/)[0] || '/';\n return pathname.length > 1 ? pathname.replace(/\\/+$/, '') : '/';\n}\n\n/**\n * Absolute (`https://host`) and protocol-relative (`//host`) destinations leave\n * the site; browsers also read `/\\host` and `\\\\host` as protocol-relative.\n */\nexport function isExternalDestination(destination: string): boolean {\n return /^(?:[a-z][a-z0-9+.-]*:)?[\\\\/]{2}/i.test(destination.trim());\n}\n\nfunction decodeXml(value: string): string {\n return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '\"').replace(/'/g, \"'\");\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nasync function eachWithConcurrency<T>(items: readonly T[], limit: number, task: (item: T) => Promise<void>) {\n const queue = [...items];\n await Promise.all(Array.from({ length: Math.max(1, Math.min(limit, queue.length)) }, async () => {\n for (let item = queue.shift(); item !== undefined; item = queue.shift()) await task(item);\n }));\n}\n"]}
|