#!/usr/bin/env bun /** * HTML INVOKER COMMANDS API TEST * ============================== * Tests the new HTML commandfor/command attributes for modals: * - Buttons have commandfor and command attributes * - command="show-modal" opens dialogs * - command="close" closes dialogs * - No onclick handlers for modal operations * * Browser support: Chrome/Edge 135+, Firefox Nightly, Safari TP * @see https://developer.chrome.com/blog/command-and-commandfor */ import { chromium } from 'playwright'; const URL = "http://localhost:1999"; async function testInvokerCommands() { console.log('🎯 HTML INVOKER COMMANDS API TEST\n'); console.log('='.repeat(70)); const browser = await chromium.launch({ headless: process.env.HEADLESS === 'true' }); const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } }); const testResults = []; await page.goto(URL); await page.waitForTimeout(2000); // ======================================================================== // TEST 1: Buttons have commandfor and command attributes // ======================================================================== console.log('\n1️⃣ Testing buttons have commandfor/command attributes...'); const buttonsWithCommand = await page.evaluate(() => { const buttons = document.querySelectorAll('[commandfor]'); return Array.from(buttons).map(btn => ({ id: btn.id || btn.className.split(' ')[0], commandfor: btn.getAttribute('commandfor'), command: btn.getAttribute('command'), hasOnclick: btn.hasAttribute('onclick') })); }); console.log(` Found ${buttonsWithCommand.length} buttons with commandfor attribute:`); buttonsWithCommand.forEach(btn => { console.log(` - ${btn.id}: commandfor="${btn.commandfor}" command="${btn.command}" onclick=${btn.hasOnclick}`); }); const hasCommandButtons = buttonsWithCommand.length >= 6; // At least 6 modal buttons console.log(` ${hasCommandButtons ? '✅ PASS' : '❌ FAIL'} - At least 6 buttons use commandfor`); testResults.push({ test: 'Buttons have commandfor attributes', passed: hasCommandButtons }); // ======================================================================== // TEST 2: No onclick for showModal/close // ======================================================================== console.log('\n2️⃣ Testing no onclick handlers for modal operations...'); const noOnclickForModals = buttonsWithCommand.every(btn => !btn.hasOnclick); console.log(` All command buttons without onclick: ${noOnclickForModals}`); console.log(` ${noOnclickForModals ? '✅ PASS' : '❌ FAIL'} - No onclick handlers on command buttons`); testResults.push({ test: 'No onclick on command buttons', passed: noOnclickForModals }); // ======================================================================== // TEST 3: Info button opens info-modal // ======================================================================== console.log('\n3️⃣ Testing info button opens modal via command attribute...'); const infoButton = await page.$('#info-button'); if (infoButton) { const infoAttrs = await page.$eval('#info-button', el => ({ commandfor: el.getAttribute('commandfor'), command: el.getAttribute('command') })); console.log(` Info button: commandfor="${infoAttrs.commandfor}" command="${infoAttrs.command}"`); // Click the button await infoButton.click(); await page.waitForTimeout(500); const infoModalOpen = await page.evaluate(() => { const modal = document.getElementById('info-modal'); return modal && modal.hasAttribute('open'); }); console.log(` Info modal opened: ${infoModalOpen}`); console.log(` ${infoModalOpen ? '✅ PASS' : '❌ FAIL'} - command="show-modal" works`); testResults.push({ test: 'command="show-modal" opens dialog', passed: infoModalOpen }); // Test close button if (infoModalOpen) { const closeButton = await page.$('#info-modal [command="close"]'); if (closeButton) { await closeButton.click(); await page.waitForTimeout(300); const modalClosed = await page.evaluate(() => { const modal = document.getElementById('info-modal'); return modal && !modal.hasAttribute('open'); }); console.log(` Info modal closed: ${modalClosed}`); console.log(` ${modalClosed ? '✅ PASS' : '❌ FAIL'} - command="close" works`); testResults.push({ test: 'command="close" closes dialog', passed: modalClosed }); } else { console.log(' ⚠️ Close button with command="close" not found'); await page.keyboard.press('Escape'); testResults.push({ test: 'command="close" closes dialog', passed: false }); } } } else { console.log(' ⚠️ Info button not found'); testResults.push({ test: 'command="show-modal" opens dialog', passed: false }); testResults.push({ test: 'command="close" closes dialog', passed: false }); } // ======================================================================== // TEST 4: Contact button opens contact-modal // ======================================================================== console.log('\n4️⃣ Testing contact button opens modal...'); const contactButton = await page.$('#contact-button'); if (contactButton) { await contactButton.click(); await page.waitForTimeout(500); const contactModalOpen = await page.evaluate(() => { const modal = document.getElementById('contact-modal'); return modal && modal.hasAttribute('open'); }); console.log(` Contact modal opened: ${contactModalOpen}`); console.log(` ${contactModalOpen ? '✅ PASS' : '❌ FAIL'} - Contact modal opens`); testResults.push({ test: 'Contact modal opens via command', passed: contactModalOpen }); // Close with ESC await page.keyboard.press('Escape'); await page.waitForTimeout(300); } else { console.log(' ⚠️ Contact button not found'); testResults.push({ test: 'Contact modal opens via command', passed: false }); } // ======================================================================== // TEST 5: Shortcuts button opens shortcuts-modal // ======================================================================== console.log('\n5️⃣ Testing shortcuts button opens modal...'); const shortcutsButton = await page.$('#shortcuts-button'); if (shortcutsButton) { await shortcutsButton.click(); await page.waitForTimeout(500); const shortcutsModalOpen = await page.evaluate(() => { const modal = document.getElementById('shortcuts-modal'); return modal && modal.hasAttribute('open'); }); console.log(` Shortcuts modal opened: ${shortcutsModalOpen}`); console.log(` ${shortcutsModalOpen ? '✅ PASS' : '❌ FAIL'} - Shortcuts modal opens`); testResults.push({ test: 'Shortcuts modal opens via command', passed: shortcutsModalOpen }); // Close with ESC await page.keyboard.press('Escape'); await page.waitForTimeout(300); } else { console.log(' ⚠️ Shortcuts button not found'); testResults.push({ test: 'Shortcuts modal opens via command', passed: false }); } // ======================================================================== // TEST 6: PDF button in action bar opens pdf-modal // ======================================================================== console.log('\n6️⃣ Testing PDF action button opens modal...'); const pdfActionButton = await page.$('#action-bar-pdf-btn'); if (pdfActionButton) { const pdfAttrs = await page.$eval('#action-bar-pdf-btn', el => ({ commandfor: el.getAttribute('commandfor'), command: el.getAttribute('command') })); console.log(` PDF button: commandfor="${pdfAttrs.commandfor}" command="${pdfAttrs.command}"`); await pdfActionButton.click(); await page.waitForTimeout(500); const pdfModalOpen = await page.evaluate(() => { const modal = document.getElementById('pdf-modal'); return modal && modal.hasAttribute('open'); }); console.log(` PDF modal opened: ${pdfModalOpen}`); console.log(` ${pdfModalOpen ? '✅ PASS' : '❌ FAIL'} - PDF modal opens via command`); testResults.push({ test: 'PDF modal opens via command', passed: pdfModalOpen }); // Close with ESC await page.keyboard.press('Escape'); await page.waitForTimeout(300); } else { console.log(' ⚠️ PDF action button not found'); testResults.push({ test: 'PDF modal opens via command', passed: false }); } // ======================================================================== // FINAL SUMMARY // ======================================================================== console.log("\n" + "=".repeat(70)); console.log("📊 TEST SUMMARY\n"); const totalTests = testResults.length; const passedTests = testResults.filter(r => r.passed).length; const failedTests = totalTests - passedTests; testResults.forEach(result => { console.log(` ${result.passed ? '✅' : '❌'} ${result.test}`); }); console.log(`\n Total: ${passedTests}/${totalTests} tests passed`); console.log("=".repeat(70) + "\n"); if (failedTests === 0) { console.log("🎉 ALL HTML INVOKER COMMANDS TESTS PASSED!"); } else { console.log("⚠️ SOME TESTS FAILED - See details above"); console.log(" Note: command/commandfor requires Chrome 135+, Edge 135+, Firefox Nightly, Safari TP"); } // Auto-close after tests if HEADLESS env is set if (process.env.HEADLESS === 'true') { await browser.close(); process.exit(failedTests === 0 ? 0 : 1); } else { console.log("\nBrowser will stay open for manual inspection."); console.log("Press Ctrl+C when done.\n"); await new Promise(() => {}); // Keep browser open } } await testInvokerCommands();