From bb1a9f4844d7f937ba5e3771c122eb91861cf5a6 Mon Sep 17 00:00:00 2001 From: corescope-bot Date: Fri, 29 May 2026 14:18:34 +0000 Subject: [PATCH] =?UTF-8?q?test(#1487):=20failing=20E2E=20for=20BYOP=20mod?= =?UTF-8?q?al=20layout=20=E2=80=94=20header=20too=20tall,=20body=20occlude?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Captures the bug reported by @EldoonNemar: on mobile the .byop-header swells to ~73px because position:sticky + negative margin assumes desktop padding (24px) while .modal switches to 16px padding on mobile, and the close-button box inflates the header further. The description paragraph then begins inside the sticky-header band and is occluded. Asserts on mobile (390x844) AND desktop (1280x800): - .byop-header height <= 56px - .text-muted description top >= .byop-header bottom (no occlusion) - textarea / Decode button do not overflow the modal client rect Wired into deploy.yml e2e-test job. Expected red: header is 73px today and description starts inside the sticky header. --- .github/workflows/deploy.yml | 1 + test-issue-1487-byop-modal-layout-e2e.js | 127 +++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 test-issue-1487-byop-modal-layout-e2e.js diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3275d9f4..452d5705 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -341,6 +341,7 @@ jobs: CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-channels-add-modal-e2e.js 2>&1 | tee -a e2e-output.txt CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-channels-share-color-e2e.js 2>&1 | tee -a e2e-output.txt CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-channels-ws-batch-e2e.js 2>&1 | tee -a e2e-output.txt + CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-issue-1487-byop-modal-layout-e2e.js 2>&1 | tee -a e2e-output.txt - name: Collect frontend coverage (parallel) if: success() && github.event_name == 'push' diff --git a/test-issue-1487-byop-modal-layout-e2e.js b/test-issue-1487-byop-modal-layout-e2e.js new file mode 100644 index 00000000..ddad3aa9 --- /dev/null +++ b/test-issue-1487-byop-modal-layout-e2e.js @@ -0,0 +1,127 @@ +/** + * E2E (#1487): BYOP modal must render with a usable layout β€” the title bar + * must NOT consume most of the dialog height and the body controls + * (description, textarea, Decode button) must be visible and not occluded + * by the sticky header. + * + * Reporter: @EldoonNemar β€” "The dialog text can't be seen due to the title + * bar being massive." + * + * Repro: + * 1. Open /#/packets on mobile (390x844). + * 2. Click the πŸ“¦ BYOP toolbar button. + * 3. Observe the modal: the .byop-header swells (~73px tall) and the + * next-sibling description paragraph (`.text-muted`) starts INSIDE + * the sticky-header band, getting visually clipped/occluded. + * + * Root cause: `.byop-header` uses `position: sticky` + a negative + * `margin: -24px -24px 12px` that assumes desktop `.modal` padding of + * 24px β€” but `.modal` switches to 16px padding on mobile. The close + * button's box (border + padding) further inflates the header. The + * description paragraph then begins at topβ‰ˆ85 inside a header that + * spans 24–97, hiding the text. + * + * Fix expectation: + * - Header height is bounded (<= 56px is a reasonable target). + * - The description paragraph's top edge is BELOW the sticky-header + * bottom edge β€” i.e. no visual occlusion. + * - The textarea and Decode button are fully within the modal client rect. + * + * Usage: BASE_URL=http://localhost:13581 node test-issue-1487-byop-modal-layout-e2e.js + */ +'use strict'; +const { chromium } = require('playwright'); + +const BASE = process.env.BASE_URL || 'http://localhost:13581'; + +let passed = 0, failed = 0; +async function step(name, fn) { + try { await fn(); passed++; console.log(' βœ“ ' + name); } + catch (e) { failed++; console.error(' βœ— ' + name + ': ' + e.message); } +} +function assert(c, m) { if (!c) throw new Error(m || 'assertion failed'); } + +async function probeModal(page) { + return page.evaluate(() => { + const m = document.querySelector('.byop-modal'); + if (!m) return { err: 'no modal' }; + const hdr = m.querySelector('.byop-header'); + const desc = m.querySelector('.text-muted'); + const ta = m.querySelector('.byop-input'); + const btn = m.querySelector('#byopDecode'); + const mr = m.getBoundingClientRect(); + const hr = hdr.getBoundingClientRect(); + const dr = desc.getBoundingClientRect(); + const tr = ta.getBoundingClientRect(); + const br = btn.getBoundingClientRect(); + return { + modalH: Math.round(mr.height), + hdrH: Math.round(hr.height), + hdrBottom: Math.round(hr.bottom), + descTop: Math.round(dr.top), + taBottom: Math.round(tr.bottom), + btnBottom: Math.round(br.bottom), + modalBottom: Math.round(mr.bottom), + }; + }); +} + +async function openBYOP(page) { + await page.waitForSelector('[data-action="pkt-byop"]', { timeout: 8000 }); + await page.evaluate(() => { + document.querySelectorAll('.byop-overlay').forEach(o => o.remove()); + document.querySelector('[data-action="pkt-byop"]').click(); + }); + await page.waitForSelector('.byop-modal', { timeout: 5000 }); + await page.waitForTimeout(200); +} + +async function runViewport(browser, label, viewport) { + const ctx = await browser.newContext({ viewport }); + const page = await ctx.newPage(); + page.setDefaultTimeout(10000); + page.on('pageerror', (e) => console.error('[pageerror]', e.message)); + + console.log(`\n--- viewport ${label} (${viewport.width}x${viewport.height}) ---`); + + await step('navigate to /packets and open BYOP', async () => { + await page.goto(BASE + '/#/packets', { waitUntil: 'domcontentloaded' }); + await openBYOP(page); + }); + + await step('header height is bounded (<= 56px)', async () => { + const p = await probeModal(page); + assert(p.hdrH <= 56, `header height ${p.hdrH}px > 56px cap (modal=${p.modalH}px)`); + }); + + await step('description paragraph is NOT occluded by sticky header', async () => { + const p = await probeModal(page); + assert(p.descTop >= p.hdrBottom, + `description top (${p.descTop}) starts INSIDE sticky header band (header bottom=${p.hdrBottom})`); + }); + + await step('textarea and Decode button do not overflow modal client rect', async () => { + const p = await probeModal(page); + assert(p.taBottom <= p.modalBottom + 1, `textarea bottom (${p.taBottom}) overflows modal (${p.modalBottom})`); + assert(p.btnBottom <= p.modalBottom + 1, `Decode button bottom (${p.btnBottom}) overflows modal`); + }); + + await ctx.close(); +} + +(async () => { + const browser = await chromium.launch({ + headless: true, + executablePath: process.env.CHROMIUM_PATH || undefined, + args: ['--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'], + }); + + console.log(`\n=== #1487 BYOP modal layout E2E against ${BASE} ===`); + + await runViewport(browser, 'mobile', { width: 390, height: 844 }); + await runViewport(browser, 'desktop', { width: 1280, height: 800 }); + + await browser.close(); + console.log(`\n${passed} passed, ${failed} failed`); + process.exit(failed === 0 ? 0 : 1); +})().catch(e => { console.error(e); process.exit(1); });