Files
Pixhelf/frontend/scripts/visual-check.mjs
T
adminroot e646d6762a Release v0.1.3
添加查看器前的最后加固
2026-08-22 12:57:57 +00:00

662 lines
31 KiB
JavaScript

import { chromium } from "playwright-core";
const executablePath =
process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE ??
"/root/.cache/ms-playwright/chromium-1234/chrome-linux64/chrome";
const baseUrl = process.env.PIXHELF_URL ?? "http://127.0.0.1:3002";
const browser = await chromium.launch({
executablePath,
headless: true,
args: ["--no-sandbox", "--disable-dev-shm-usage"],
});
const results = [];
try {
for (const target of [
{ name: "desktop", width: 1440, height: 1000 },
{ name: "mobile", width: 390, height: 844 },
]) {
const page = await browser.newPage({
viewport: { width: target.width, height: target.height },
deviceScaleFactor: target.name === "mobile" ? 3 : 1,
hasTouch: target.name === "mobile",
});
const browserErrors = [];
page.on("console", (message) => {
if (message.type() === "error") browserErrors.push(message.text());
});
page.on("pageerror", (error) => browserErrors.push(error.message));
await page.goto(baseUrl, { waitUntil: "domcontentloaded", timeout: 60_000 });
await page.waitForSelector("[data-image-id]", { timeout: 60_000 });
await page.waitForFunction(() => {
const visible = [...document.querySelectorAll("[data-image-id] img")].filter((image) => {
const rect = image.getBoundingClientRect();
return rect.bottom > 0 && rect.top < innerHeight;
});
return visible.length >= 8 &&
visible.every((image) => image.complete && image.naturalWidth > 0);
}, undefined, { timeout: 60_000 });
await page.waitForTimeout(250);
await page.screenshot({ path: `/tmp/pixhelf-${target.name}.png` });
const layout = await page.evaluate(() => {
const path = document.querySelector(".desktop-sidebar .sidebar-gallery-path");
const count = document.querySelector(".desktop-sidebar .sidebar-gallery-count");
const search = document.querySelector(".topbar-search");
const topbar = document.querySelector(".topbar");
const sidebarStatus = document.querySelector(".desktop-sidebar .sidebar-status");
const sidebarMeta = document.querySelector(".desktop-sidebar .sidebar-gallery-meta");
const pathRect = path?.getBoundingClientRect();
const countRect = count?.getBoundingClientRect();
const searchRect = search?.getBoundingClientRect();
const exploreRect = document.querySelector(".explore-toggle")?.getBoundingClientRect();
const topbarRect = topbar?.getBoundingClientRect();
const statusRect = sidebarStatus?.getBoundingClientRect();
const metaRect = sidebarMeta?.getBoundingClientRect();
const galleryToggle = document.querySelector(".gallery-sidebar-toggle");
return {
viewport: [innerWidth, innerHeight],
bodyWidth: document.documentElement.scrollWidth,
cards: document.querySelectorAll("[data-image-id]").length,
cardTags: [...document.querySelectorAll("[data-image-id]")]
.map((element) => element.tagName),
masonryColumns: document.querySelectorAll(".masonry-column").length,
loadedCards: document.querySelectorAll("[data-image-id] img.loaded").length,
brokenVisibleImages: [...document.images].filter((image) => {
const rect = image.getBoundingClientRect();
return rect.bottom > 0 && rect.top < innerHeight && image.naturalWidth === 0;
}).length,
horizontalOverflow: document.documentElement.scrollWidth > innerWidth + 1,
dialogs: document.querySelectorAll('[role="dialog"]').length,
topbarPosition: topbar ? getComputedStyle(topbar).position : "",
topbarTop: topbarRect?.top ?? -1,
topbarSearches: document.querySelectorAll(
".topbar-actions > .topbar-search",
).length,
brandHomeLinks: document.querySelectorAll('a.brand[href="/"]').length,
gallerySidebarToggles: document.querySelectorAll(".gallery-sidebar-toggle").length,
galleryToggleOpacity: galleryToggle
? Number.parseFloat(getComputedStyle(galleryToggle).opacity)
: -1,
topbarSidebarToggles: document.querySelectorAll(
".topbar .sidebar-toggle-button",
).length,
contentSearches: document.querySelectorAll(".content .search-field").length,
topbarSearchRightGap: searchRect ? innerWidth - searchRect.right : -1,
exploreButtons: document.querySelectorAll(".topbar-actions > .explore-toggle").length,
exploreSearchGap: exploreRect && searchRect ? searchRect.left - exploreRect.right : -1,
sidebarStatuses: document.querySelectorAll(
".desktop-sidebar > .sidebar-status",
).length,
sidebarGalleryMetas: document.querySelectorAll(".sidebar-gallery-meta").length,
contentInfoRows: document.querySelectorAll(
".content-heading, .content .sidebar-gallery-meta",
).length,
sortControls: document.querySelectorAll(".sort-control").length,
cardNameDisplay: getComputedStyle(document.querySelector(".image-name")).display,
cardNameOpacity: Number.parseFloat(
getComputedStyle(document.querySelector(".image-name")).opacity,
),
cardNameWhiteSpace: getComputedStyle(
document.querySelector(".image-name"),
).whiteSpace,
sidebarMetaBottomGap: metaRect ? innerHeight - metaRect.bottom : -1,
sidebarMetaStatusGap: statusRect && metaRect ? metaRect.top - statusRect.bottom : -1,
galleryPath: path?.textContent ?? "",
galleryCount: count?.textContent ?? "",
galleryMetaRowDelta: pathRect && countRect
? Math.abs(pathRect.top - countRect.top)
: -1,
};
});
await page.locator("[data-image-id]").first().click();
await page.waitForTimeout(200);
const dialogsAfterImageClick = await page.locator('[role="dialog"]').count();
const searchToggleBefore = await page.locator(".search-toggle").boundingBox();
await page.locator(".search-toggle").click();
await page.waitForFunction(() =>
document.querySelector(".search-toggle")?.getAttribute("aria-expanded") === "true"
);
await page.waitForTimeout(220);
const searchToggleOpen = await page.locator(".search-toggle").boundingBox();
const searchPanelOpen = await page.locator("#gallery-search-field").boundingBox();
const searchDisclosure = await page.evaluate(() => {
const leading = document.querySelector(".topbar-leading")?.getBoundingClientRect();
const panel = document.querySelector("#gallery-search-field")?.getBoundingClientRect();
const topbar = document.querySelector(".topbar")?.getBoundingClientRect();
return {
expanded: document.querySelector(".search-toggle")?.getAttribute("aria-expanded"),
focused: document.activeElement?.matches("#gallery-search-field input") ?? false,
panelRole: document.querySelector("#gallery-search-field")?.getAttribute("role"),
panelInert: document.querySelector("#gallery-search-field")?.inert ?? true,
leadingGap: leading && panel ? panel.left - leading.right : -1,
panelTop: panel?.top ?? -1,
topbarTop: topbar?.top ?? -1,
topbarBottom: topbar?.bottom ?? -1,
};
});
searchDisclosure.panelWidth = searchPanelOpen?.width ?? -1;
searchDisclosure.stationaryToggleDelta = searchToggleBefore && searchToggleOpen
? Math.hypot(
searchToggleBefore.x - searchToggleOpen.x,
searchToggleBefore.y - searchToggleOpen.y,
)
: -1;
await page.locator("#gallery-search-field input").fill("x");
await page.waitForFunction(() =>
document.querySelector(".topbar-search")?.classList.contains("has-query")
);
searchDisclosure.hasQueryClass = true;
await page.locator("#gallery-search-field input").fill("");
await page.waitForFunction(() =>
!document.querySelector(".topbar-search")?.classList.contains("has-query")
);
searchDisclosure.queryClassCleared = true;
if (target.name === "mobile") {
await page.evaluate(() => {
document.documentElement.style.setProperty("--visual-viewport-top", "24px");
});
await page.waitForTimeout(50);
const compensatedTopbar = await page.locator(".topbar").boundingBox();
const compensatedPanel = await page.locator("#gallery-search-field").boundingBox();
searchDisclosure.compensatedTopbarDelta = compensatedTopbar
? compensatedTopbar.y - searchDisclosure.topbarTop
: -1;
searchDisclosure.compensatedPanelDelta = compensatedPanel
? compensatedPanel.y - searchDisclosure.panelTop
: -1;
await page.evaluate(() => {
document.documentElement.style.setProperty("--visual-viewport-top", "0px");
});
await page.waitForTimeout(50);
}
await page.locator(".search-toggle").click();
await page.waitForFunction(() =>
document.querySelector(".search-toggle")?.getAttribute("aria-expanded") === "false"
);
searchDisclosure.restored = await page.locator(".search-toggle")
.getAttribute("aria-expanded");
searchDisclosure.panelInertClosed = await page.locator("#gallery-search-field")
.evaluate((panel) => panel.inert);
const firstExploreRequest = page.waitForRequest((request) => {
const url = new URL(request.url());
return url.pathname === "/api/images" &&
url.searchParams.get("sort") === "explore" &&
url.searchParams.get("offset") === "0";
});
await page.locator(".explore-toggle").click();
const firstExplore = await firstExploreRequest;
const firstExploreUrl = new URL(firstExplore.url());
const firstExploreResponse = await firstExplore.response();
await firstExploreResponse?.finished();
await page.waitForFunction(() => !document.querySelector(".skeleton-grid"));
const secondExploreRequest = page.waitForRequest((request) => {
const url = new URL(request.url());
return url.pathname === "/api/images" &&
url.searchParams.get("sort") === "explore" &&
url.searchParams.get("offset") === "0";
});
await page.locator(".explore-toggle").click();
const secondExplore = await secondExploreRequest;
const secondExploreUrl = new URL(secondExplore.url());
const secondExploreResponse = await secondExplore.response();
await secondExploreResponse?.finished();
await page.waitForFunction(() => !document.querySelector(".skeleton-grid"));
const exploration = await page.evaluate(([firstSeed, secondSeed]) => ({
firstSeed,
secondSeed,
exploreMarker: getComputedStyle(
document.querySelector(".explore-toggle"),
"::after",
).content,
searchMarker: getComputedStyle(
document.querySelector(".search-toggle"),
"::after",
).content,
}), [
firstExploreUrl.searchParams.get("seed") ?? "",
secondExploreUrl.searchParams.get("seed") ?? "",
]);
const homeRequest = page.waitForRequest((request) => {
const url = new URL(request.url());
return url.pathname === "/api/images" &&
url.searchParams.get("sort") === "name-asc" &&
url.searchParams.get("offset") === "0";
});
await page.locator("a.brand").click();
const homeResponse = await (await homeRequest).response();
await homeResponse?.finished();
await page.waitForFunction(() => !document.querySelector(".skeleton-grid"));
const homeNavigation = await page.evaluate(() => ({
href: document.querySelector("a.brand")?.getAttribute("href"),
galleryPath: document.querySelector(
".desktop-sidebar .sidebar-gallery-path",
)?.textContent,
allImagesActive: document.querySelector(".desktop-sidebar .album-link")
?.classList.contains("active") ?? false,
searchValue: document.querySelector("#gallery-search-field input")?.value,
}));
let mobileCardNames = null;
if (target.name === "mobile") {
const firstCard = page.locator("[data-image-id]").first();
const secondCard = page.locator("[data-image-id]").nth(1);
await firstCard.dispatchEvent("pointerdown", {
bubbles: true,
cancelable: true,
pointerId: 1,
pointerType: "touch",
isPrimary: true,
});
await page.waitForFunction(() =>
document.querySelector("[data-image-id]")?.getAttribute("data-name-visible") === "true"
);
const shownOnContact = await firstCard.getAttribute("data-name-visible");
await firstCard.dispatchEvent("pointerup", {
bubbles: true,
cancelable: true,
pointerId: 1,
pointerType: "touch",
isPrimary: true,
});
const retainedOnRelease = await firstCard.getAttribute("data-name-visible");
await page.waitForTimeout(1_900);
const retainedWithoutTimeout = await firstCard.getAttribute("data-name-visible");
await secondCard.dispatchEvent("pointerdown", {
bubbles: true,
cancelable: true,
pointerId: 2,
pointerType: "touch",
isPrimary: true,
});
await page.waitForFunction(() =>
document.querySelectorAll('[data-name-visible="true"]').length === 1 &&
document.querySelectorAll("[data-image-id]")[1]
?.getAttribute("data-name-visible") === "true"
);
await page.waitForTimeout(180);
const switchedToSecond = await secondCard.getAttribute("data-name-visible");
const firstHidden = await firstCard.getAttribute("data-name-visible");
const activeCount = await page.locator('[data-name-visible="true"]').count();
const displayedCount = await page.locator('[data-name-visible="true"] .image-name')
.evaluateAll((names) => names.filter((name) =>
Number.parseFloat(getComputedStyle(name).opacity) > 0.99
).length);
await secondCard.dispatchEvent("pointerup", {
bubbles: true,
cancelable: true,
pointerId: 2,
pointerType: "touch",
isPrimary: true,
});
await secondCard.evaluate((card) => {
globalThis.__pixhelfCardClickCount = 0;
card.addEventListener("click", () => {
globalThis.__pixhelfCardClickCount += 1;
}, { once: true });
});
const secondCardBox = await secondCard.boundingBox();
if (secondCardBox) {
await page.touchscreen.tap(
secondCardBox.x + secondCardBox.width / 2,
secondCardBox.y + secondCardBox.height / 2,
);
}
const clickCount = await page.evaluate(() => globalThis.__pixhelfCardClickCount ?? 0);
mobileCardNames = {
shownOnContact,
retainedOnRelease,
retainedWithoutTimeout,
switchedToSecond,
firstHidden,
activeCount,
displayedCount,
clickCount,
};
}
let desktopSidebar = null;
let mobileNavigation = null;
if (target.name === "desktop") {
const contentBefore = await page.locator(".content").boundingBox();
const toggleBefore = await page.locator(".gallery-sidebar-toggle").boundingBox();
const firstAlbumBefore = await page.locator(".desktop-sidebar .album-link").first()
.boundingBox();
const controlSlotBefore = await page.locator(".desktop-sidebar .sidebar-control-slot")
.boundingBox();
const metaBefore = await page.locator(".desktop-sidebar .sidebar-gallery-meta")
.boundingBox();
const galleryBefore = await page.locator(".masonry").boundingBox();
await page.locator(".gallery-sidebar-toggle").click();
await page.waitForFunction(() =>
document.querySelector(".app-shell")?.getAttribute("data-sidebar-collapsed") === "true"
);
await page.waitForTimeout(260);
const contentCollapsed = await page.locator(".content").boundingBox();
const toggleCollapsed = await page.locator(".gallery-sidebar-toggle").boundingBox();
const sidebarCollapsedBox = await page.locator(".desktop-sidebar").boundingBox();
const galleryCollapsed = await page.locator(".masonry").boundingBox();
desktopSidebar = await page.evaluate(() => ({
expanded: document.querySelector(".gallery-sidebar-toggle")?.getAttribute("aria-expanded"),
visibility: getComputedStyle(document.querySelector(".desktop-sidebar")).visibility,
navigationVisibility: getComputedStyle(
document.querySelector(".desktop-sidebar .album-nav"),
).visibility,
statusVisibility: getComputedStyle(
document.querySelector(".desktop-sidebar .sidebar-status"),
).visibility,
metaVisibility: getComputedStyle(
document.querySelector(".desktop-sidebar .sidebar-gallery-meta"),
).visibility,
ariaHidden: document.querySelector(".desktop-sidebar")?.getAttribute("aria-hidden"),
stored: localStorage.getItem("pixhelf.sidebar-collapsed"),
columns: document.querySelectorAll(".masonry-column").length,
redundantHeadings: document.querySelectorAll(".sidebar-heading").length,
pathDetails: document.querySelectorAll(".album-copy small").length,
progressPanels: document.querySelectorAll(".sidebar-progress").length,
inlineToggles: document.querySelectorAll(".desktop-sidebar-inline-toggle").length,
galleryToggles: document.querySelectorAll(".gallery-sidebar-toggle").length,
}));
desktopSidebar.contentBefore = contentBefore?.x ?? -1;
desktopSidebar.contentCollapsed = contentCollapsed?.x ?? -1;
desktopSidebar.expandedToggleWidth = toggleBefore?.width ?? -1;
desktopSidebar.expandedToggleHeight = toggleBefore?.height ?? -1;
desktopSidebar.collapsedToggleWidth = toggleCollapsed?.width ?? -1;
desktopSidebar.collapsedToggleHeight = toggleCollapsed?.height ?? -1;
desktopSidebar.albumClearance = controlSlotBefore && firstAlbumBefore
? firstAlbumBefore.y - (controlSlotBefore.y + controlSlotBefore.height)
: -1;
desktopSidebar.toggleControlInsetX = toggleBefore && controlSlotBefore
? toggleBefore.x - controlSlotBefore.x
: -1;
desktopSidebar.toggleControlInsetY = toggleBefore && controlSlotBefore
? toggleBefore.y - controlSlotBefore.y
: -1;
desktopSidebar.stationaryToggleDelta = toggleBefore && toggleCollapsed
? Math.hypot(toggleBefore.x - toggleCollapsed.x, toggleBefore.y - toggleCollapsed.y)
: -1;
desktopSidebar.metaIsBelowAlbums = metaBefore && firstAlbumBefore
? metaBefore.y > firstAlbumBefore.y + firstAlbumBefore.height
: false;
desktopSidebar.expandedGalleryInsetX = toggleBefore && galleryBefore
? toggleBefore.x - galleryBefore.x
: -1;
desktopSidebar.expandedGalleryInsetY = toggleBefore && galleryBefore
? toggleBefore.y - galleryBefore.y
: -1;
desktopSidebar.collapsedGalleryInsetX = toggleCollapsed && galleryCollapsed
? toggleCollapsed.x - galleryCollapsed.x
: -1;
desktopSidebar.collapsedGalleryInsetY = toggleCollapsed && galleryCollapsed
? toggleCollapsed.y - galleryCollapsed.y
: -1;
desktopSidebar.expandedGalleryOverlap = toggleBefore && galleryBefore
? !(
toggleBefore.x + toggleBefore.width <= galleryBefore.x ||
galleryBefore.x + galleryBefore.width <= toggleBefore.x ||
toggleBefore.y + toggleBefore.height <= galleryBefore.y ||
galleryBefore.y + galleryBefore.height <= toggleBefore.y
)
: false;
desktopSidebar.galleryOverlap = toggleCollapsed && galleryCollapsed
? !(
toggleCollapsed.x + toggleCollapsed.width <= galleryCollapsed.x ||
galleryCollapsed.x + galleryCollapsed.width <= toggleCollapsed.x ||
toggleCollapsed.y + toggleCollapsed.height <= galleryCollapsed.y ||
galleryCollapsed.y + galleryCollapsed.height <= toggleCollapsed.y
)
: true;
desktopSidebar.sidebarRight = sidebarCollapsedBox
? sidebarCollapsedBox.x + sidebarCollapsedBox.width
: -1;
await page.locator(".gallery-sidebar-toggle").click();
await page.waitForFunction(() =>
document.querySelector(".app-shell")?.getAttribute("data-sidebar-collapsed") === "false"
);
await page.waitForTimeout(260);
const toggleRestored = await page.locator(".gallery-sidebar-toggle").boundingBox();
desktopSidebar.restoredToggleDelta = toggleBefore && toggleRestored
? Math.hypot(toggleBefore.x - toggleRestored.x, toggleBefore.y - toggleRestored.y)
: -1;
desktopSidebar.restored = await page.locator(".gallery-sidebar-toggle")
.getAttribute("aria-expanded");
} else {
await page.evaluate(() => window.scrollTo({ top: 320, behavior: "auto" }));
await page.waitForTimeout(100);
const fixedTop = await page.locator(".topbar").boundingBox();
await page.evaluate(() => window.scrollTo({ top: 0, behavior: "auto" }));
const navigationToggleBefore = await page.locator(".gallery-sidebar-toggle").boundingBox();
const galleryBefore = await page.locator(".masonry").boundingBox();
await page.locator(".gallery-sidebar-toggle").click();
await page.waitForSelector(".mobile-nav-layer");
await page.waitForTimeout(240);
const navigationToggleOpen = await page.locator(".gallery-sidebar-toggle").boundingBox();
const navigationLayer = await page.locator(".mobile-nav-layer").boundingBox();
const navigationDrawer = await page.locator(".mobile-sidebar").boundingBox();
const controlSlot = await page.locator(".mobile-sidebar .sidebar-control-slot").boundingBox();
const mobileMeta = await page.locator(".mobile-sidebar .sidebar-gallery-meta").boundingBox();
const mobileStatus = await page.locator(".mobile-sidebar .sidebar-status").boundingBox();
const topbar = await page.locator(".topbar").boundingBox();
mobileNavigation = {
opened: await page.locator(".mobile-nav-layer").count(),
fixedTop: fixedTop?.y ?? -1,
expanded: await page.locator(".gallery-sidebar-toggle").getAttribute("aria-expanded"),
internalToggles: await page.locator(".mobile-sidebar .sidebar-toggle-button").count(),
metaRows: await page.locator(".mobile-sidebar .sidebar-gallery-meta").count(),
galleryPath: await page.locator(".mobile-sidebar .sidebar-gallery-path").textContent(),
galleryCount: await page.locator(".mobile-sidebar .sidebar-gallery-count").textContent(),
layerTopGap: navigationLayer && topbar
? navigationLayer.y - (topbar.y + topbar.height)
: -1,
toggleWidth: navigationToggleBefore?.width ?? -1,
toggleHeight: navigationToggleBefore?.height ?? -1,
expandedToggleWidth: navigationToggleOpen?.width ?? -1,
galleryInsetX: navigationToggleBefore && galleryBefore
? navigationToggleBefore.x - galleryBefore.x
: -1,
galleryInsetY: navigationToggleBefore && galleryBefore
? navigationToggleBefore.y - galleryBefore.y
: -1,
galleryOverlap: navigationToggleBefore && galleryBefore
? !(
navigationToggleBefore.x + navigationToggleBefore.width <= galleryBefore.x ||
galleryBefore.x + galleryBefore.width <= navigationToggleBefore.x ||
navigationToggleBefore.y + navigationToggleBefore.height <= galleryBefore.y ||
galleryBefore.y + galleryBefore.height <= navigationToggleBefore.y
)
: -1,
stationaryToggleDelta: navigationToggleBefore && navigationToggleOpen
? Math.hypot(
navigationToggleBefore.x - navigationToggleOpen.x,
navigationToggleBefore.y - navigationToggleOpen.y,
)
: -1,
toggleControlInsetX: navigationToggleOpen && controlSlot
? navigationToggleOpen.x - controlSlot.x
: -1,
toggleControlInsetY: navigationToggleOpen && controlSlot
? navigationToggleOpen.y - controlSlot.y
: -1,
metaBottomGap: mobileMeta ? target.height - (mobileMeta.y + mobileMeta.height) : -1,
metaStatusGap: mobileMeta && mobileStatus
? mobileMeta.y - (mobileStatus.y + mobileStatus.height)
: -1,
drawerWidth: navigationDrawer?.width ?? -1,
};
await page.locator(".gallery-sidebar-toggle").click();
await page.waitForSelector(".mobile-nav-layer", { state: "detached" });
mobileNavigation.closed = await page.locator(".mobile-nav-layer").count();
mobileNavigation.restored = await page.locator(".gallery-sidebar-toggle")
.getAttribute("aria-expanded");
}
const result = {
name: target.name,
...layout,
dialogsAfterImageClick,
searchDisclosure,
exploration,
homeNavigation,
mobileCardNames,
desktopSidebar,
mobileNavigation,
browserErrors,
};
results.push(result);
console.log(JSON.stringify(result));
const expectedColumns = target.name === "desktop" ? 5 : 2;
if (
layout.horizontalOverflow ||
layout.brokenVisibleImages ||
layout.masonryColumns !== expectedColumns ||
layout.cardTags.some((tag) => tag !== "FIGURE") ||
layout.dialogs !== 0 ||
dialogsAfterImageClick !== 0 ||
searchDisclosure.expanded !== "true" ||
!searchDisclosure.focused ||
searchDisclosure.panelRole !== "group" ||
searchDisclosure.panelInert ||
(target.name === "desktop" && searchDisclosure.leadingGap < 6) ||
searchDisclosure.panelWidth < (target.name === "desktop" ? 250 : 200) ||
searchDisclosure.stationaryToggleDelta < 0 ||
searchDisclosure.stationaryToggleDelta > 0.5 ||
searchDisclosure.restored !== "false" ||
!searchDisclosure.panelInertClosed ||
!searchDisclosure.hasQueryClass ||
!searchDisclosure.queryClassCleared ||
searchDisclosure.panelTop - searchDisclosure.topbarBottom < 6 ||
exploration.firstSeed.length !== 32 ||
exploration.secondSeed.length !== 32 ||
exploration.firstSeed === exploration.secondSeed ||
exploration.exploreMarker !== "none" ||
exploration.searchMarker !== "none" ||
homeNavigation.href !== "/" ||
homeNavigation.galleryPath !== "/" ||
!homeNavigation.allImagesActive ||
homeNavigation.searchValue !== "" ||
layout.topbarSearches !== 1 ||
layout.brandHomeLinks !== 1 ||
layout.gallerySidebarToggles !== 1 ||
layout.galleryToggleOpacity < 0.4 ||
layout.galleryToggleOpacity > 0.75 ||
layout.topbarSidebarToggles !== 0 ||
layout.contentSearches !== 0 ||
layout.contentInfoRows !== 0 ||
layout.sortControls !== 0 ||
layout.topbarSearchRightGap < 0 ||
layout.topbarSearchRightGap > 20 ||
layout.exploreButtons !== 1 ||
layout.exploreSearchGap < 4 ||
layout.exploreSearchGap > 8 ||
layout.sidebarStatuses !== 1 ||
layout.sidebarGalleryMetas !== 1 ||
!layout.galleryPath.startsWith("/") ||
!layout.galleryCount.endsWith("张图片") ||
layout.galleryMetaRowDelta < 0 ||
layout.galleryMetaRowDelta > 2 ||
(target.name === "desktop" && (
!desktopSidebar ||
desktopSidebar.expanded !== "false" ||
desktopSidebar.visibility !== "visible" ||
desktopSidebar.navigationVisibility !== "hidden" ||
desktopSidebar.statusVisibility !== "hidden" ||
desktopSidebar.metaVisibility !== "hidden" ||
desktopSidebar.ariaHidden !== "true" ||
desktopSidebar.stored !== "true" ||
desktopSidebar.columns !== 6 ||
desktopSidebar.redundantHeadings !== 0 ||
desktopSidebar.pathDetails !== 0 ||
desktopSidebar.progressPanels !== 0 ||
desktopSidebar.inlineToggles !== 0 ||
desktopSidebar.galleryToggles !== 1 ||
Math.abs(desktopSidebar.expandedToggleWidth - (desktopSidebar.contentBefore - 20)) > 1 ||
Math.abs(desktopSidebar.expandedToggleHeight - 36) > 1 ||
Math.abs(desktopSidebar.collapsedToggleWidth - 40) > 1 ||
Math.abs(desktopSidebar.collapsedToggleHeight - 36) > 1 ||
desktopSidebar.albumClearance < 8 ||
desktopSidebar.toggleControlInsetX < 8 ||
desktopSidebar.toggleControlInsetX > 12 ||
desktopSidebar.toggleControlInsetY < 4 ||
desktopSidebar.toggleControlInsetY > 8 ||
desktopSidebar.stationaryToggleDelta < 0 ||
desktopSidebar.stationaryToggleDelta > 1 ||
!desktopSidebar.metaIsBelowAlbums ||
desktopSidebar.expandedGalleryOverlap ||
!desktopSidebar.galleryOverlap ||
Math.abs(desktopSidebar.sidebarRight) > 1 ||
desktopSidebar.contentBefore < 200 ||
Math.abs(desktopSidebar.contentCollapsed) > 1 ||
desktopSidebar.restoredToggleDelta < 0 ||
desktopSidebar.restoredToggleDelta > 1 ||
layout.sidebarMetaBottomGap < 0 ||
layout.sidebarMetaBottomGap > 1 ||
Math.abs(layout.sidebarMetaStatusGap) > 1 ||
desktopSidebar.restored !== "true"
)) ||
(target.name === "mobile" && (
layout.topbarPosition !== "fixed" ||
Math.abs(layout.topbarTop) > 1 ||
Math.abs(searchDisclosure.compensatedTopbarDelta - 24) > 1 ||
Math.abs(searchDisclosure.compensatedPanelDelta - 24) > 1 ||
mobileNavigation?.opened !== 1 ||
Math.abs(mobileNavigation.fixedTop) > 1 ||
mobileNavigation.expanded !== "true" ||
mobileNavigation.internalToggles !== 0 ||
mobileNavigation.metaRows !== 1 ||
!mobileNavigation.galleryPath?.startsWith("/") ||
!mobileNavigation.galleryCount?.endsWith("张图片") ||
Math.abs(mobileNavigation.layerTopGap) > 1 ||
Math.abs(mobileNavigation.toggleWidth - 40) > 1 ||
Math.abs(mobileNavigation.toggleHeight - 36) > 1 ||
Math.abs(mobileNavigation.expandedToggleWidth - (mobileNavigation.drawerWidth - 20)) > 1 ||
!mobileNavigation.galleryOverlap ||
mobileNavigation.stationaryToggleDelta < 0 ||
mobileNavigation.stationaryToggleDelta > 1 ||
mobileNavigation.toggleControlInsetX < 8 ||
mobileNavigation.toggleControlInsetX > 12 ||
mobileNavigation.toggleControlInsetY < 4 ||
mobileNavigation.toggleControlInsetY > 8 ||
mobileNavigation.metaBottomGap < 0 ||
mobileNavigation.metaBottomGap > 1 ||
Math.abs(mobileNavigation.metaStatusGap) > 1 ||
mobileNavigation.drawerWidth < 200 ||
mobileNavigation.closed !== 0 ||
mobileNavigation.restored !== "false" ||
mobileCardNames?.shownOnContact !== "true" ||
mobileCardNames.retainedOnRelease !== "true" ||
mobileCardNames.retainedWithoutTimeout !== "true" ||
mobileCardNames.switchedToSecond !== "true" ||
mobileCardNames.firstHidden !== "false" ||
mobileCardNames.activeCount !== 1 ||
mobileCardNames.displayedCount !== 1 ||
mobileCardNames.clickCount !== 1 ||
layout.cardNameDisplay === "none" ||
layout.cardNameOpacity > 0.01 ||
layout.cardNameWhiteSpace !== "nowrap"
)) ||
(target.name === "desktop" && layout.topbarPosition !== "fixed") ||
browserErrors.length
) {
throw new Error(`visual check failed: ${JSON.stringify(result)}`);
}
await page.close();
}
} finally {
await browser.close();
}
console.log(JSON.stringify(results, null, 2));