Skip to content

Commit faecc25

Browse files
committed
test(docs-tests): assert the landing-page house-style surfaces render
PR TrakHound#186 wires the maintainer-confirmed house-style choices from Discussion TrakHound#184 — the brand logo, the #0073e6 accent color, the favicon, the OG / Twitter Card meta block, and the 'Download latest release' hero CTA — but nothing on the test side asserts those surfaces survive a future theme refactor. Add `Landing_Page_Carries_The_House_Style_Surfaces` to the existing RouteCheckTests Playwright fixture. The test navigates the homepage, extracts the theme-color meta, favicon href + type, og:title + og:image, twitter:card + twitter:image, the nav <img> rendered by themeConfig.logo, and the hero CTA href, then pins each one against its expected value. The favicon path is also fetched over HTTP so a stale href that 404s is caught — the meta-tag presence check alone would not surface that. The test reuses the fixture's preview server and browser context, so the only cost is one extra navigation in the existing Category=E2E leg. Failure messages name the missing surface directly rather than forcing the reader into a snapshot diff.
1 parent f310464 commit faecc25

1 file changed

Lines changed: 150 additions & 0 deletions

File tree

tests/MTConnect.NET-Docs-Tests/RouteCheckTests.cs

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,156 @@ public async Task A_Synthetic_Unmapped_Route_Surfaces_As_A_404()
224224
}
225225
}
226226

227+
/// <summary>
228+
/// Pins the docs-site house-style surfaces the maintainer signed off
229+
/// on in Discussion #184 (see <c>docs/development/house-style.md</c>):
230+
/// the brand logo wired through <c>themeConfig.logo</c>, the favicon
231+
/// link, the Open Graph + Twitter Card meta block, the
232+
/// <c>theme-color</c> meta, and the 'Download latest release' hero
233+
/// CTA pointing at the GitHub releases page.
234+
/// </summary>
235+
/// <remarks>
236+
/// Each check is a focused property assertion on the rendered HTML
237+
/// so the failure message names the missing surface directly
238+
/// ('og:image meta tag missing', 'theme-color meta is #ffffff,
239+
/// expected #0073e6'). A single bundled assertion would surface
240+
/// 'page does not match expected snapshot' and force the reader
241+
/// into a diff — the structured form is the §10a positive-+-negative
242+
/// pin per surface, all in one test.
243+
///
244+
/// The favicon asset is also fetched over HTTP to guarantee the
245+
/// <c>href</c> resolves — a stale path that 404s would still
246+
/// satisfy the meta-tag presence check, so the fetch closes the
247+
/// gap between 'tag exists' and 'tag works'.
248+
/// </remarks>
249+
[Test]
250+
public async Task Landing_Page_Carries_The_House_Style_Surfaces()
251+
{
252+
Assert.That(_browser, Is.Not.Null, "browser was not initialised");
253+
254+
var context = await _browser!.NewContextAsync();
255+
try
256+
{
257+
var page = await context.NewPageAsync();
258+
var response = await page.GotoAsync(_baseUrl + "/", new PageGotoOptions
259+
{
260+
WaitUntil = WaitUntilState.Load,
261+
Timeout = PageNavigationTimeoutMs,
262+
});
263+
Assert.That(response, Is.Not.Null, "homepage navigation returned no response");
264+
Assert.That(response!.Ok, Is.True, $"homepage returned HTTP {response.Status}");
265+
266+
var probes = await page.EvaluateAsync<HouseStyleProbes>(@"() => ({
267+
themeColor: document.querySelector('meta[name=""theme-color""]')?.getAttribute('content') ?? null,
268+
faviconHref: document.querySelector('link[rel=""icon""]')?.getAttribute('href') ?? null,
269+
faviconType: document.querySelector('link[rel=""icon""]')?.getAttribute('type') ?? null,
270+
ogTitle: document.querySelector('meta[property=""og:title""]')?.getAttribute('content') ?? null,
271+
ogImage: document.querySelector('meta[property=""og:image""]')?.getAttribute('content') ?? null,
272+
twitterCard: document.querySelector('meta[name=""twitter:card""]')?.getAttribute('content') ?? null,
273+
twitterImage: document.querySelector('meta[name=""twitter:image""]')?.getAttribute('content') ?? null,
274+
heroLogoSrc: document.querySelector('.VPNavBarTitle img.logo')?.getAttribute('src') ?? null,
275+
downloadCtaHref: (() => {
276+
const link = Array.from(document.querySelectorAll('.VPHero a, a'))
277+
.find(a => /Download latest release/i.test(a.textContent ?? ''));
278+
return link ? link.getAttribute('href') : null;
279+
})()
280+
})");
281+
282+
// theme-color — brand accent per Discussion #184.
283+
Assert.That(probes.ThemeColor, Is.EqualTo("#0073e6"),
284+
"theme-color meta does not match the maintainer-confirmed brand accent");
285+
286+
// Favicon — present, PNG, points at /logo.png (base-prefixed
287+
// when DOCS_BASE is set, so endsWith() is the stable match).
288+
Assert.That(probes.FaviconHref, Is.Not.Null.And.Not.Empty, "favicon <link rel=icon> missing");
289+
Assert.That(probes.FaviconHref, Does.EndWith("/logo.png"),
290+
$"favicon href does not point at /logo.png — got '{probes.FaviconHref}'");
291+
Assert.That(probes.FaviconType, Is.EqualTo("image/png"),
292+
"favicon type attribute is not 'image/png'");
293+
294+
// Open Graph — title + image surface so social previews render.
295+
Assert.That(probes.OgTitle, Is.EqualTo("MTConnect.NET"),
296+
"og:title meta does not match the expected site title");
297+
Assert.That(probes.OgImage, Is.Not.Null.And.Not.Empty, "og:image meta missing");
298+
Assert.That(probes.OgImage, Does.EndWith("/logo.png"),
299+
$"og:image does not point at /logo.png — got '{probes.OgImage}'");
300+
301+
// Twitter Card — large summary card with the same image.
302+
Assert.That(probes.TwitterCard, Is.EqualTo("summary_large_image"),
303+
"twitter:card meta is not 'summary_large_image'");
304+
Assert.That(probes.TwitterImage, Is.Not.Null.And.Not.Empty, "twitter:image meta missing");
305+
Assert.That(probes.TwitterImage, Does.EndWith("/logo.png"),
306+
$"twitter:image does not point at /logo.png — got '{probes.TwitterImage}'");
307+
308+
// themeConfig.logo — VitePress renders the logo as an <img>
309+
// inside .VPNavBarTitle when themeConfig.logo is set. The
310+
// text site title is hidden (themeConfig.siteTitle: false)
311+
// because the logo PNG already carries the wordmark.
312+
Assert.That(probes.HeroLogoSrc, Is.Not.Null.And.Not.Empty,
313+
"no <img> rendered inside .VPNavBarTitle — themeConfig.logo did not take effect");
314+
Assert.That(probes.HeroLogoSrc, Does.EndWith("/logo.png"),
315+
$"nav logo src does not point at /logo.png — got '{probes.HeroLogoSrc}'");
316+
317+
// Hero 'Download latest release' CTA — text + canonical link.
318+
Assert.That(probes.DownloadCtaHref, Is.EqualTo(
319+
"https://github.com/TrakHound/MTConnect.NET/releases/latest"),
320+
"'Download latest release' hero CTA missing or points elsewhere");
321+
322+
// Closing the gap between 'tag present' and 'tag works':
323+
// fetch the favicon over HTTP and assert it returns 200.
324+
// A stale logo path (e.g. an old `/favicon.ico` reference
325+
// after a rename) would satisfy the meta-tag check above
326+
// but break the favicon for end users.
327+
var faviconUrl = probes.FaviconHref!.StartsWith("http", StringComparison.Ordinal)
328+
? probes.FaviconHref
329+
: _baseUrl + probes.FaviconHref;
330+
var faviconResponse = await page.Context.APIRequest.GetAsync(faviconUrl);
331+
Assert.That(faviconResponse.Status, Is.EqualTo(200),
332+
$"favicon at {faviconUrl} returned HTTP {faviconResponse.Status}");
333+
334+
await page.CloseAsync();
335+
}
336+
finally
337+
{
338+
await context.CloseAsync();
339+
}
340+
}
341+
342+
// The JS payload returned by the house-style probe uses camelCase
343+
// keys; pin them explicitly so a future Playwright upgrade that
344+
// tightens case-insensitive deserialisation cannot silently turn
345+
// every probe into a null pass-through (which would hide a
346+
// regression in the rendered meta surface).
347+
private sealed class HouseStyleProbes
348+
{
349+
[JsonPropertyName("themeColor")]
350+
public string? ThemeColor { get; set; }
351+
352+
[JsonPropertyName("faviconHref")]
353+
public string? FaviconHref { get; set; }
354+
355+
[JsonPropertyName("faviconType")]
356+
public string? FaviconType { get; set; }
357+
358+
[JsonPropertyName("ogTitle")]
359+
public string? OgTitle { get; set; }
360+
361+
[JsonPropertyName("ogImage")]
362+
public string? OgImage { get; set; }
363+
364+
[JsonPropertyName("twitterCard")]
365+
public string? TwitterCard { get; set; }
366+
367+
[JsonPropertyName("twitterImage")]
368+
public string? TwitterImage { get; set; }
369+
370+
[JsonPropertyName("heroLogoSrc")]
371+
public string? HeroLogoSrc { get; set; }
372+
373+
[JsonPropertyName("downloadCtaHref")]
374+
public string? DownloadCtaHref { get; set; }
375+
}
376+
227377
/// <summary>
228378
/// Walks every markdown-backed route the docs/ tree implies and
229379
/// asserts none rendered a 404. Failures are collected across all

0 commit comments

Comments
 (0)