From 8a96736bb0a065e9f4bd6715e6f3215669526211 Mon Sep 17 00:00:00 2001
From: Abdulfatah Bahbouh <60443686+abooodbah@users.noreply.github.com>
Date: Sat, 7 Nov 2026 23:06:05 -0600
Subject: [PATCH 1/6] Test the site's structured data, install lines and task
page
The site test now requires sameAs listings instead of codeRepository,
a screenshot and image, the author's page, no ratings, the releases
feed link, the Scoop and Homebrew commands, and a task page at
open-md-files-on-windows/ that is linked and in the sitemap. The
browser smoke test audits that page at the same three sizes.
These checks fail until the site changes land in the next commit.
---
tests/Invoke-SiteBrowserSmoke.mjs | 99 ++++++++++++++++++-------------
tests/Test-Site.ps1 | 98 +++++++++++++++++++++++++-----
2 files changed, 141 insertions(+), 56 deletions(-)
diff --git a/tests/Invoke-SiteBrowserSmoke.mjs b/tests/Invoke-SiteBrowserSmoke.mjs
index eb1b726..b47f704 100644
--- a/tests/Invoke-SiteBrowserSmoke.mjs
+++ b/tests/Invoke-SiteBrowserSmoke.mjs
@@ -70,8 +70,9 @@ async function startStaticServer(siteRoot) {
const requestUrl = new URL(request.url || "/", "http://127.0.0.1/");
let pathname = decodeURIComponent(requestUrl.pathname);
- if (pathname === "/") {
- pathname = "/index.html";
+ // A folder URL is served as that folder's index.html, as on Pages.
+ if (pathname.endsWith("/")) {
+ pathname += "index.html";
}
const candidate = resolve(siteRoot, "." + pathname.split("/").join(sep));
if (candidate !== siteRoot && !candidate.startsWith(rootPrefix)) {
@@ -536,51 +537,65 @@ async function main() {
{ name: "tablet", width: 820, height: 1000, scheme: "light" },
{ name: "mobile", width: 390, height: 844, scheme: "dark" },
];
+ // The landing page and the task page share the header, buttons, and
+ // stylesheet, so the same audit applies to both.
+ const pages = [
+ { name: "", path: "" },
+ { name: "task page ", path: "open-md-files-on-windows/" },
+ ];
- for (const viewport of viewports) {
- await client.send("Emulation.setDeviceMetricsOverride", {
- width: viewport.width,
- height: viewport.height,
- deviceScaleFactor: 1,
- mobile: viewport.width <= 390,
- screenWidth: viewport.width,
- screenHeight: viewport.height,
- });
- await client.send("Emulation.setEmulatedMedia", {
- features: [{
- name: "prefers-color-scheme",
- value: viewport.scheme,
- }],
- });
- await client.send("Page.navigate", {
- url: staticServer.url + "?viewport=" + viewport.name,
- });
- await waitForDocument(client);
- const audit = await evaluate(client, PAGE_AUDIT_EXPRESSION);
- assertAudit(viewport.name, viewport, audit);
- if (captureRoot) {
- const screenshot = await client.send("Page.captureScreenshot", {
- captureBeyondViewport: true,
- format: "png",
- fromSurface: true,
+ for (const page of pages) {
+ for (const viewport of viewports) {
+ await client.send("Emulation.setDeviceMetricsOverride", {
+ width: viewport.width,
+ height: viewport.height,
+ deviceScaleFactor: 1,
+ mobile: viewport.width <= 390,
+ screenWidth: viewport.width,
+ screenHeight: viewport.height,
});
- await fs.writeFile(
- join(captureRoot, "site-" + viewport.name + ".png"),
- Buffer.from(screenshot.data, "base64"),
- );
+ await client.send("Emulation.setEmulatedMedia", {
+ features: [{
+ name: "prefers-color-scheme",
+ value: viewport.scheme,
+ }],
+ });
+ await client.send("Page.navigate", {
+ url: staticServer.url + page.path + "?viewport=" + viewport.name,
+ });
+ await waitForDocument(client);
+ const audit = await evaluate(client, PAGE_AUDIT_EXPRESSION);
+ assertAudit(page.name + viewport.name, viewport, audit);
+ if (captureRoot) {
+ const screenshot = await client.send("Page.captureScreenshot", {
+ captureBeyondViewport: true,
+ format: "png",
+ fromSurface: true,
+ });
+ await fs.writeFile(
+ join(
+ captureRoot,
+ "site-" + (page.path ? "task-" : "") + viewport.name + ".png",
+ ),
+ Buffer.from(screenshot.data, "base64"),
+ );
+ }
}
- }
- const accessibility = await client.send("Accessibility.getFullAXTree");
- const interactiveWithoutName = accessibility.nodes.filter((node) => {
- const role = node.role && node.role.value;
- const name = node.name && node.name.value;
- return (role === "link" || role === "button") && !String(name || "").trim();
- });
- if (interactiveWithoutName.length > 0) {
- fail("The accessibility tree contains an unnamed link or button.");
+ const accessibility = await client.send("Accessibility.getFullAXTree");
+ const interactiveWithoutName = accessibility.nodes.filter((node) => {
+ const role = node.role && node.role.value;
+ const name = node.name && node.name.value;
+ return (role === "link" || role === "button") && !String(name || "").trim();
+ });
+ if (interactiveWithoutName.length > 0) {
+ fail("The " + (page.name || "landing page ") +
+ "accessibility tree contains an unnamed link or button.");
+ }
+ console.log(
+ "PASS " + page.name + "accessibility tree exposes names for every link and button",
+ );
}
- console.log("PASS accessibility tree exposes names for every link and button");
if (browserErrors.length > 0) {
fail("Browser console or resource errors: " + browserErrors.join(" | "));
diff --git a/tests/Test-Site.ps1 b/tests/Test-Site.ps1
index 90b284e..6ec0ab8 100644
--- a/tests/Test-Site.ps1
+++ b/tests/Test-Site.ps1
@@ -102,8 +102,59 @@ Assert-Equal ([string]$structured.'@type') 'SoftwareApplication' 'JSON-LD must d
Assert-Equal ([string]$structured.softwareVersion) $version 'JSON-LD softwareVersion must match package.json.'
Assert-Equal ([string]$structured.offers.price) '0' 'JSON-LD must offer LeanMark at price 0.'
Assert-Equal ([string]$structured.author.name) 'Abdulfatah Bahbouh' 'JSON-LD must name the author.'
+function Get-JsonProperty {
+ param($Object, [string]$Name)
+ $property = $Object.PSObject.Properties[$Name]
+ if ($null -eq $property) { return $null }
+ $property.Value
+}
+Assert-Equal ([string](Get-JsonProperty $structured.author 'url')) 'https://abooodbah.github.io/' 'JSON-LD author must link the Lean Apps page.'
+# codeRepository belongs to SoftwareSourceCode, not SoftwareApplication; the
+# repository and the package listings go in sameAs instead.
+Assert-True ($null -eq $structured.PSObject.Properties['codeRepository']) 'JSON-LD must not use codeRepository on a SoftwareApplication.'
+# There are no real ratings or reviews; never claim any.
+Assert-NotMatch $jsonLd.Groups[1].Value '(?i)aggregateRating|"review' 'JSON-LD must not carry ratings or reviews.'
+$sameAs = @(Get-JsonProperty $structured 'sameAs' | ForEach-Object { [string]$_ })
+foreach ($listing in @(
+ 'https://github.com/abooodbah/leanmark',
+ 'https://apps.microsoft.com/detail/9MSS4WKJ22ZV',
+ 'https://github.com/microsoft/winget-pkgs/tree/master/manifests/a/abooodbah/LeanMark',
+ 'https://community.chocolatey.org/packages/leanmark',
+ 'https://github.com/abooodbah/homebrew-tap')) {
+ Assert-True ($sameAs -contains $listing) "JSON-LD sameAs is missing $listing"
+}
+Assert-Equal ([string](Get-JsonProperty $structured 'screenshot')) 'https://abooodbah.github.io/leanmark/assets/leanmark-window.png' 'JSON-LD screenshot must be the real product screenshot on the page.'
+Assert-Equal ([string](Get-JsonProperty $structured 'image')) 'https://abooodbah.github.io/leanmark/assets/social-preview.png' 'JSON-LD image must be the social preview.'
+Assert-Match $html ' ' 'The page must point feed readers to the releases feed.'
+Assert-Match $html 'scoop install lean/leanmark' 'The install section must give the Scoop command.'
+Assert-Match $html 'brew install --cask abooodbah/tap/leanmark' 'The install section must give the Homebrew command.'
+Assert-Match $html 'href="open-md-files-on-windows/"' 'The landing page must link the task page.'
Write-TestPass 'semantic metadata, release CTA, and script-free markup'
+# The task page: one plain-HTML page that answers "how do I open .md files on
+# Windows" with every real option, LeanMark being one of them.
+$taskRelative = 'open-md-files-on-windows/index.html'
+$taskPath = Join-Path $resolvedSite $taskRelative
+Assert-True (Test-Path -LiteralPath $taskPath -PathType Leaf) "Task page is missing: $taskRelative"
+$taskHtml = Read-TextFile $taskPath
+Assert-Match $taskHtml '' 'The task page language must be explicit.'
+Assert-Equal ([regex]::Matches($taskHtml, '(?is)