Integrate PageSpeed Insights audits
This commit is contained in:
301
tests/pagespeed-audit-input.test.ts
Normal file
301
tests/pagespeed-audit-input.test.ts
Normal file
@@ -0,0 +1,301 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import {
|
||||
assertNoPublicPageSpeedScores,
|
||||
buildPageSpeedAuditInputs,
|
||||
type PageSpeedMinimalAuditResult,
|
||||
} from "../lib/pagespeed-audit-input";
|
||||
|
||||
const MOBILE_AND_DESKTOP_FIXTURES: PageSpeedMinimalAuditResult[] = [
|
||||
{
|
||||
strategy: "mobile",
|
||||
status: "succeeded",
|
||||
sourceUrl: "https://example.com",
|
||||
normalized: {
|
||||
metrics: {
|
||||
firstContentfulPaintMs: 3200,
|
||||
largestContentfulPaintMs: 5000,
|
||||
cumulativeLayoutShift: 0.2,
|
||||
},
|
||||
implications: [
|
||||
"Score 0.42: Der erste sichtbare Inhalt erscheint zu langsam.",
|
||||
"Die Seite zeigt das Hauptbild zu langsam.",
|
||||
"Die Inhalte verschieben sich beim Laden.",
|
||||
],
|
||||
opportunities: [
|
||||
"Nicht verwendetes CSS kann entfernt werden.",
|
||||
"Bilder ohne passende Komprimierung koennen verzichtet werden.",
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
strategy: "desktop",
|
||||
status: "succeeded",
|
||||
sourceUrl: "https://example.com",
|
||||
normalized: {
|
||||
metrics: {
|
||||
firstContentfulPaintMs: 1200,
|
||||
largestContentfulPaintMs: 2200,
|
||||
cumulativeLayoutShift: 0.04,
|
||||
},
|
||||
implications: [
|
||||
"Die Seite zeigt das Hauptbild zu langsam.",
|
||||
"Inhalte werden beim Laden sauber angezeigt.",
|
||||
],
|
||||
opportunities: ["Serverantworten sind stabil.", "Inhalte werden gestaffelt geladen."],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test("buildPageSpeedAuditInputs converts normalized implications into German customer impact statements", () => {
|
||||
const actual = buildPageSpeedAuditInputs(MOBILE_AND_DESKTOP_FIXTURES);
|
||||
|
||||
assert.equal(actual.customerImplications.length > 0, true);
|
||||
assert.equal(
|
||||
actual.customerImplications.includes(
|
||||
"Die Seite zeigt das Hauptbild zu langsam.",
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
actual.customerImplications.includes("Die Inhalte verschieben sich beim Laden."),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
actual.customerImplications.some((line) => /mobile/i.test(line)),
|
||||
true,
|
||||
"Customer implications should include a mobile-centric statement.",
|
||||
);
|
||||
assert.equal(
|
||||
assertNoPublicPageSpeedScores(actual.customerImplications),
|
||||
true,
|
||||
"Customer implications must not contain score-like values.",
|
||||
);
|
||||
assert.equal(
|
||||
assertNoPublicPageSpeedScores(actual.technicalSignals),
|
||||
true,
|
||||
"Technical signals must not contain score-like values.",
|
||||
);
|
||||
});
|
||||
|
||||
test("buildPageSpeedAuditInputs detects meaningful mobile performance gaps versus desktop", () => {
|
||||
const actual = buildPageSpeedAuditInputs(MOBILE_AND_DESKTOP_FIXTURES);
|
||||
|
||||
assert.equal(
|
||||
actual.customerImplications.some((line) =>
|
||||
/mobile/i.test(line) &&
|
||||
/deutlich|spurbar|signifikant|langsamer/.test(line) &&
|
||||
/desktop/i.test(line),
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("buildPageSpeedAuditInputs keeps quota/api/unavailable failures in internal notes only", () => {
|
||||
const actual = buildPageSpeedAuditInputs([
|
||||
{
|
||||
strategy: "mobile",
|
||||
status: "failed",
|
||||
sourceUrl: "https://bad.example",
|
||||
errorType: "quota",
|
||||
errorSummary: "API quota has been exceeded for this host.",
|
||||
},
|
||||
{
|
||||
strategy: "desktop",
|
||||
status: "failed",
|
||||
sourceUrl: "https://bad2.example",
|
||||
errorType: "unavailable",
|
||||
errorSummary: "Page not reachable at the moment.",
|
||||
},
|
||||
{
|
||||
strategy: "mobile",
|
||||
status: "failed",
|
||||
sourceUrl: "https://bad3.example",
|
||||
errorType: "api_error",
|
||||
errorSummary: "Lighthouse processing failed due to API timeout.",
|
||||
},
|
||||
{
|
||||
strategy: "desktop",
|
||||
status: "succeeded",
|
||||
sourceUrl: "https://example.com",
|
||||
normalized: {
|
||||
implications: ["Die wichtigste Information wird zu langsam sichtbar."],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(
|
||||
actual.customerImplications.some((line) => /quota|unavailable|timeout|api/i.test(line)),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
actual.technicalSignals.some((line) => /quota|unavailable|timeout|api/i.test(line)),
|
||||
false,
|
||||
);
|
||||
assert.equal(actual.internalNotes.length >= 3, true);
|
||||
assert.equal(actual.internalNotes.some((line) => /quota/i.test(line)), true);
|
||||
assert.equal(actual.internalNotes.some((line) => /not reachable|unreachable|erreich|timeout/i.test(line)), true);
|
||||
assert.equal(actual.internalNotes.some((line) => /api/i.test(line)), true);
|
||||
});
|
||||
|
||||
test("buildPageSpeedAuditInputs strips score-like and raw strings from public outputs", () => {
|
||||
const actual = buildPageSpeedAuditInputs([
|
||||
{
|
||||
strategy: "mobile",
|
||||
status: "succeeded",
|
||||
sourceUrl: "https://example.com",
|
||||
normalized: {
|
||||
implications: [
|
||||
"Score 0.42: FCP is high.",
|
||||
"rawStorageId: file_123",
|
||||
"Lighthouse category performance is present.",
|
||||
"Die Seite laedt in 3.2 Sekunden.",
|
||||
],
|
||||
opportunities: [
|
||||
"Ein { \"score\": 0.91 } kann optimiert werden.",
|
||||
"redundante CSS Dateien.",
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(assertNoPublicPageSpeedScores(actual.customerImplications), true);
|
||||
assert.equal(assertNoPublicPageSpeedScores(actual.technicalSignals), true);
|
||||
assert.equal(
|
||||
actual.customerImplications.every((line) => !/\d/.test(line)),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("buildPageSpeedAuditInputs strips URLs, markup, JSON-like payloads, and machine-like words from public outputs", () => {
|
||||
const actual = buildPageSpeedAuditInputs([
|
||||
{
|
||||
strategy: "mobile",
|
||||
status: "succeeded",
|
||||
sourceUrl: "https://example.com",
|
||||
normalized: {
|
||||
implications: [
|
||||
"Weitere Infos findest du in https://example.com/details",
|
||||
"Das <strong>Element</strong> lädt stabil.",
|
||||
"{ \"pagespeed\": 0.84, \"lighthouseResult\": {} }",
|
||||
"[\"rawStorageId\":\"id-0123456789abcdef0123456789\"]",
|
||||
"rawStorageId: run_2026_0001",
|
||||
"lighthouseResult suggests a bad candidate.",
|
||||
"Die Seite laedt insgesamt spuertbar langsam.",
|
||||
],
|
||||
opportunities: [
|
||||
"Moeglichkeit: <img src=\"x\" />",
|
||||
"Pagespeed Score should not appear.",
|
||||
"[{\"audit\":\"speed\"}]",
|
||||
"Reduziere ungenutzte JavaScript-Dateien.",
|
||||
"A longMachineToken_0123456789abcdef0123456789 to test filtering.",
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(assertNoPublicPageSpeedScores(actual.customerImplications), true);
|
||||
assert.equal(assertNoPublicPageSpeedScores(actual.technicalSignals), true);
|
||||
assert.equal(actual.customerImplications.includes("Die Seite laedt insgesamt spuertbar langsam."), true);
|
||||
assert.equal(
|
||||
actual.technicalSignals.some((line) => /unused|reduziere|javascript/i.test(line)),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
actual.customerImplications.every((line) => !/\bhttps?:\/\/|rawstorageid|lighthouseresult|pagespeed|score|<|>|\\{|\\}|\\[|\\]/i.test(line)),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
actual.technicalSignals.every((line) => !/\bhttps?:\/\/|rawstorageid|lighthouseresult|pagespeed|score|<|>|\\{|\\}|\\[|\\]/i.test(line)),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("buildPageSpeedAuditInputs keeps failure categories in internal notes while removing URLs and JSON fragments", () => {
|
||||
const actual = buildPageSpeedAuditInputs([
|
||||
{
|
||||
strategy: "mobile",
|
||||
status: "failed",
|
||||
sourceUrl: "https://example.com/audit?x=1",
|
||||
errorType: "api_error",
|
||||
errorSummary:
|
||||
"PageSpeed API failed: { \"lighthouseResult\": {\"code\":\"timeout\"}, \"rawStorageId\": \"abc123\" }",
|
||||
},
|
||||
{
|
||||
strategy: "desktop",
|
||||
status: "succeeded",
|
||||
sourceUrl: "https://example.com",
|
||||
normalized: {
|
||||
implications: [
|
||||
"Die Seite laedt spuerbar schneller auf Desktop.",
|
||||
],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
assert.equal(actual.internalNotes.length >= 1, true);
|
||||
assert.equal(
|
||||
actual.internalNotes.every(
|
||||
(line) =>
|
||||
!/https?:\/\//i.test(line) &&
|
||||
!/\{|\}|\[|\]/i.test(line) &&
|
||||
!/rawstorageid|lighthouseresult/i.test(line),
|
||||
),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
actual.internalNotes.some((line) => /api|technisch/i.test(line)),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test("buildPageSpeedAuditInputs deduplicates and caps output lists", () => {
|
||||
const manyImplications = Array.from({ length: 12 }, (_, index) => [
|
||||
"Die Seite ist zu langsam.",
|
||||
"Die Seite ist zu langsam.",
|
||||
`Implication ${index}`,
|
||||
"Wichtige Inhalte sind nicht sofort sichtbar.",
|
||||
"Wichtige Inhalte sind nicht sofort sichtbar.",
|
||||
]).flat();
|
||||
const manyOpportunities = Array.from({ length: 12 }, (_, index) => [
|
||||
"Komprimieren Sie Bilder.",
|
||||
`Opportunity ${index}`,
|
||||
"Komprimieren Sie Bilder.",
|
||||
"Inhalte werden nachgeladen.",
|
||||
]).flat();
|
||||
|
||||
const actual = buildPageSpeedAuditInputs([
|
||||
{
|
||||
strategy: "mobile",
|
||||
status: "succeeded",
|
||||
sourceUrl: "https://example.com",
|
||||
normalized: {
|
||||
implications: manyImplications,
|
||||
opportunities: manyOpportunities,
|
||||
},
|
||||
},
|
||||
...Array.from({ length: 10 }, (_, index) => ({
|
||||
strategy: "desktop" as const,
|
||||
status: "failed" as const,
|
||||
sourceUrl: `https://example.com/${index}`,
|
||||
errorType: "api_error" as const,
|
||||
errorSummary: `Run ${String.fromCharCode(97 + (index % 26))} had internal problem.`,
|
||||
})),
|
||||
]);
|
||||
|
||||
assert.equal(actual.customerImplications.length <= 8, true);
|
||||
assert.equal(actual.technicalSignals.length <= 8, true);
|
||||
assert.equal(actual.customerImplications.length > 0, true);
|
||||
assert.equal(actual.technicalSignals.length > 0, true);
|
||||
assert.equal(actual.internalNotes.length, 6);
|
||||
|
||||
assert.equal(
|
||||
new Set(actual.customerImplications).size,
|
||||
actual.customerImplications.length,
|
||||
);
|
||||
assert.equal(
|
||||
new Set(actual.technicalSignals).size,
|
||||
actual.technicalSignals.length,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user