From 90ebbd6ee571850c666832e5f0ce33f2c76af258 Mon Sep 17 00:00:00 2001 From: Sanjay Rai Date: Wed, 5 Aug 2026 01:56:30 -0700 Subject: [PATCH] fix: feature QA observation captures JSON-LD structured data blocks The observation step extracted only card-level data (title, text, links), so any assertion about non-card DOM, such as an injected script[type=application/ld+json] block, was structurally unverifiable and produced an automatic FAIL even when the tag was present (verified present via e2e and manual DOM inspection on the same build). The observation now also captures up to four ld+json blocks with their parent element, attributes, and content, passes them to the validation judge, and reports the block count in the PR comment. --- .github/qa/feature-review.mjs | 23 ++++++++++++++++++++--- html/e2e/verify-local.html | 14 ++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 html/e2e/verify-local.html diff --git a/.github/qa/feature-review.mjs b/.github/qa/feature-review.mjs index 7da406cf3..d63062c92 100644 --- a/.github/qa/feature-review.mjs +++ b/.github/qa/feature-review.mjs @@ -305,7 +305,19 @@ or {"sourceTest":"...","skipReason":"source search could not prove how the test const observed = await page.evaluate(() => { const grid = document.querySelector('.consonant-CardsGrid'); const cards = grid ? [...grid.querySelectorAll('.consonant-Card')] : [...document.querySelectorAll('.consonant-Card')]; - return cards.slice(0, 12).map((card, index) => { + // Non-visual DOM artifacts (JSON-LD structured data). Card extraction + // alone is blind to script/meta tags, which made any assertion about + // them unverifiable and an automatic FAIL. Capture them explicitly. + const jsonLd = [...document.querySelectorAll('script[type="application/ld+json"]')].slice(0, 4) + .map((scriptEl, index) => ({ + n: index + 1, + parent: scriptEl.parentElement + ? `${scriptEl.parentElement.tagName.toLowerCase()}${scriptEl.parentElement.className ? `.${String(scriptEl.parentElement.className).trim().split(/\s+/)[0]}` : ''}` + : '', + attrs: [...scriptEl.attributes].map((a) => a.name).join(' '), + text: (scriptEl.textContent || '').slice(0, 1500), + })); + const cardData = cards.slice(0, 12).map((card, index) => { const title = card.querySelector('[class*="-title"]'); const links = [...card.querySelectorAll('a,button')].slice(0, 6).map((element) => ({ tag: element.tagName.toLowerCase(), @@ -322,6 +334,7 @@ or {"sourceTest":"...","skipReason":"source search could not prove how the test links, }; }); + return { cards: cardData, jsonLd }; }); console.log('[observed] ' + JSON.stringify(observed)); await page.screenshot({ path: '/tmp/feature-render.png', fullPage: true }).catch(() => {}); @@ -337,7 +350,10 @@ Expected, copied from that test: ${plan.expected} Source mapping evidence: ${JSON.stringify(plan.mappingEvidence)} Rendered first-collection cards (id, title, text, links/buttons): -${JSON.stringify(observed).slice(0, 6000) || '(no cards rendered)'} +${JSON.stringify(observed.cards).slice(0, 6000) || '(no cards rendered)'} + +Structured data blocks on the page (script[type="application/ld+json"], with parent element and content): +${JSON.stringify(observed.jsonLd).slice(0, 6000) || '(none present)'} Does the rendered DOM satisfy ONLY the selected test assertion? Do not introduce new expectations. Respond with ONLY JSON: {"verdict":"PASS"|"FAIL","reason":"one or two sentences citing observed vs expected"}`, 1500); const res = extractJson(check); @@ -352,7 +368,8 @@ Does the rendered DOM satisfy ONLY the selected test assertion? Do not introduce **Fixture cards:** ${plan.cards.length} **Expected:** ${plan.expected} **Rendered (first collection):** -${observed.map((item) => `- ${item.n}. ${item.title || item.text.slice(0, 50)}${item.links.length ? ` [${item.links.map((link) => `${link.testId || link.tag}${link.href ? ` ${link.href}` : ''}`).join(', ')}]` : ''}`).join('\n') || '_(no cards rendered)_'} +${observed.cards.map((item) => `- ${item.n}. ${item.title || item.text.slice(0, 50)}${item.links.length ? ` [${item.links.map((link) => `${link.testId || link.tag}${link.href ? ` ${link.href}` : ''}`).join(', ')}]` : ''}`).join('\n') || '_(no cards rendered)_'} +**Structured data blocks:** ${observed.jsonLd.length}${observed.jsonLd.length ? ` (first: parent \`${observed.jsonLd[0].parent}\`, ${observed.jsonLd[0].text.length} chars)` : ''} **Verdict:** ${res.reason}`); process.exit(0); diff --git a/html/e2e/verify-local.html b/html/e2e/verify-local.html new file mode 100644 index 000000000..a9db6aeac --- /dev/null +++ b/html/e2e/verify-local.html @@ -0,0 +1,14 @@ + +JSON-LD Verify v2 + +

JSON-LD verification v2, real tags and filters

+

1. HASHED payload + plaintext filter panel
+Cards arrive with hashed tags (e.g. 4x24/l1s1). The filter config above is plaintext. +The container hashes the config forward to match. Card titles literally say which +product they are tagged with, so compare each entry's keywords to its name.

+

2. Unhashed smoke.json + matching filters
+Same mechanism without hashing. Expect keywords like Photography, Stamford, CT, Create Now.

+

3. Flag OFF control, tag should be absent.

+

Console command on each page:

+
JSON.parse(document.querySelector('script[data-caas-jsonld]').textContent).itemListElement.map(e => e.item.name + '  =>  ' + (e.item.keywords || 'NO KEYWORDS'))
+ \ No newline at end of file