Some checks are pending
build-gate / build (push) Waiting to run
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.
99 lines
4.3 KiB
JavaScript
99 lines
4.3 KiB
JavaScript
// 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()}.`)
|