test(deck): click the REAL button and read what it prints
Every other packet/card gate drives the RENDERERS. This one drives the UI: it finds the Card Deck button in the shadow root, clicks it, and captures the document that reaches the print frame. That is the only path proving the wiring — records provider, stock picker, language, shared composable, print frame — is connected end to end. A renderer can be perfect while the button calling it passes the wrong stock, or nothing at all. ⚠ It intercepts the printed HTML rather than reading the iframe back. openPrintFrame calls window.print(), which BLOCKS in headless Chromium, and the frame self-removes on afterprint — reading it back measures a frame that has already gone and reports 0 cards for a correct deck. Measured through the real UI: 116 cards at 2.5in 3.5in with 116 QR codes, and 3.5in 2.5in when the stock picker is switched to landscape. Confirmed to fail on a wrong expected page size. Also updates the share policy: country-posters moves from 'own-button' to 'header'. Its two-tab MFE now owns sharing, exactly as my-upg-100-list's does — the gate caught the bundle briefly carrying two affordances after the MFE landed, which is the regression it was written for.
This commit is contained in:
parent
7f8b75d661
commit
394741bf7a
3 changed files with 101 additions and 1 deletions
99
internal/check-deck-click.mjs
Normal file
99
internal/check-deck-click.mjs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
// check-deck-click — click the REAL Card Deck button and read what it prints.
|
||||
//
|
||||
// Every other packet/card gate drives the RENDERERS directly. This one drives the
|
||||
// UI: it finds the button in the shadow root, clicks it, and captures the document
|
||||
// that reaches the print frame. That is the only path that proves the wiring —
|
||||
// records provider, stock picker, language, shared composable, print frame — is
|
||||
// connected end to end. A renderer can be perfect while the button that calls it
|
||||
// passes the wrong stock, or nothing at all.
|
||||
//
|
||||
// ⚠ IT INTERCEPTS THE PRINTED HTML rather than reading the iframe back.
|
||||
// openPrintFrame calls window.print(), which BLOCKS in headless Chromium, and the
|
||||
// frame self-removes on afterprint — reading it back measures a frame that has
|
||||
// already gone and reports 0 cards for a correct deck.
|
||||
//
|
||||
// Usage: node internal/check-deck-click.mjs <wallUrl> [stockId] [expectedPage]
|
||||
import { chromium } from 'playwright'
|
||||
|
||||
const URL_ = process.argv[2]
|
||||
if (!URL_) { console.error('usage: check-deck-click.mjs <wallUrl> [stock] [expectedPage]'); process.exit(2) }
|
||||
const STOCK = process.argv[3] || ''
|
||||
const EXPECT_PAGE = process.argv[4] || (STOCK === 'pokerLandscape' ? '3.5in 2.5in' : '2.5in 3.5in')
|
||||
|
||||
const b = await chromium.launch({ args: ['--use-gl=swiftshader', '--enable-unsafe-swiftshader'] })
|
||||
const pg = await b.newPage({ viewport: { width: 1280, height: 1000 } })
|
||||
const errs = []
|
||||
pg.on('pageerror', (e) => errs.push(String(e).slice(0, 140)))
|
||||
await pg.goto(URL_, { waitUntil: 'load', timeout: 45000 })
|
||||
await pg.waitForTimeout(9000)
|
||||
|
||||
await pg.evaluate(() => {
|
||||
window.__printed = ''
|
||||
const real = document.createElement.bind(document)
|
||||
document.createElement = (t) => {
|
||||
const el = real(t)
|
||||
if (t === 'iframe') {
|
||||
Object.defineProperty(el, 'contentDocument', {
|
||||
get: () => ({ open() {}, close() {}, write(h) { window.__printed += h } }),
|
||||
})
|
||||
Object.defineProperty(el, 'contentWindow', {
|
||||
get: () => ({ addEventListener() {}, focus() {}, print() {} }),
|
||||
})
|
||||
}
|
||||
return el
|
||||
}
|
||||
})
|
||||
|
||||
const r = await pg.evaluate(async (stock) => {
|
||||
const host = [...document.querySelectorAll('*')]
|
||||
.find((e) => e.shadowRoot && e.shadowRoot.querySelector('.pbtn'))
|
||||
if (!host) return { error: 'no poster wall found on this page' }
|
||||
const sr = host.shadowRoot
|
||||
const btns = [...sr.querySelectorAll('.pbtn')]
|
||||
const deck = btns.find((x) => /card deck/i.test(x.textContent))
|
||||
if (!deck) return { error: 'no Card Deck button', buttons: btns.map((x) => x.textContent.trim()) }
|
||||
if (stock) {
|
||||
const sel = sr.querySelector('.pstock')
|
||||
if (!sel) return { error: 'no card-stock picker to drive' }
|
||||
sel.value = stock
|
||||
sel.dispatchEvent(new Event('change', { bubbles: true }))
|
||||
await new Promise((res) => setTimeout(res, 400))
|
||||
}
|
||||
deck.click()
|
||||
await new Promise((res) => setTimeout(res, 9000))
|
||||
const doc = new DOMParser().parseFromString(window.__printed, 'text/html')
|
||||
return {
|
||||
cards: doc.querySelectorAll('.card').length,
|
||||
qrs: doc.querySelectorAll('.qr svg').length,
|
||||
imgs: doc.querySelectorAll('img.ph').length,
|
||||
page: (window.__printed.match(/@page\{size:([^;}]*)/) || [])[1] || '',
|
||||
}
|
||||
}, STOCK)
|
||||
await b.close()
|
||||
|
||||
if (r.error) {
|
||||
console.error(`DECK-CLICK: FAIL — ${r.error}`
|
||||
+ (r.buttons ? ` (buttons present: ${r.buttons.join(' | ')})` : ''))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const fails = []
|
||||
if (!r.cards) fails.push('the click printed no cards at all — the button is not wired to the renderer')
|
||||
if (r.page.trim() !== EXPECT_PAGE) {
|
||||
fails.push(`@page size is "${r.page.trim()}", expected "${EXPECT_PAGE}"`
|
||||
+ (STOCK ? ' — the stock picker is not reaching the renderer' : ''))
|
||||
}
|
||||
if (r.qrs !== r.cards) fails.push(`${r.qrs} QR code(s) for ${r.cards} cards`)
|
||||
if (errs.length) fails.push(`${errs.length} page error(s): ${errs[0]}`)
|
||||
|
||||
console.log(` stock : ${STOCK || '(default)'}`)
|
||||
console.log(` cards printed : ${r.cards} (${r.imgs} portraits, ${r.qrs} QR codes)`)
|
||||
console.log(` @page size : ${r.page.trim()} (want ${EXPECT_PAGE})`)
|
||||
console.log(` page errors : ${errs.length}`)
|
||||
|
||||
if (fails.length) {
|
||||
console.error(`\nDECK-CLICK: FAIL — ${fails.length} problem(s)`)
|
||||
for (const f of fails) console.error(' - ' + f)
|
||||
process.exit(1)
|
||||
}
|
||||
console.log(`\nDECK-CLICK: PASS — the real button printed ${r.cards} cards at ${r.page.trim()}.`)
|
||||
|
|
@ -45,7 +45,7 @@ const DESIGNS = join(REPO, 'plugin-designs')
|
|||
const POLICY = {
|
||||
'doxa-simple-map': 'own-button', // Prayer Progress — the embed flagship
|
||||
'doxa-research-map': 'own-button',
|
||||
'country-posters': 'own-button',
|
||||
'country-posters': 'header', // country-posters-mfe header owns it
|
||||
'cities-internet': 'own-button',
|
||||
'gospel-resources': 'own-button',
|
||||
'demo-map': 'own-button',
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
|
||||
"check:card-deck": "node internal/check-card-deck.mjs",
|
||||
"check:poster-raster": "node internal/check-poster-raster.mjs",
|
||||
"check:deck-click": "node internal/check-deck-click.mjs",
|
||||
"check:poster-prints": "node internal/check-poster-prints.mjs",
|
||||
"check:provider-switch": "node internal/check-provider-switch.mjs",
|
||||
"check:mfe-two-tab": "node internal/check-mfe-two-tab.mjs",
|
||||
|
|
|
|||
Loading…
Reference in a new issue