/** * Diagnostic script: inspect the actual DOM structure of Reddit chat * to understand how media images are rendered. * * Usage: npx tsx scripts/inspect-chat-dom.ts */ import { chromium } from 'playwright'; import path from 'path'; async function main() { const sessionDir = '/tmp/reddit-session-copy'; console.log('Launching browser with session copy...'); const context = await chromium.launchPersistentContext(sessionDir, { headless: false, viewport: { width: 1280, height: 720 }, args: ['--no-sandbox', '--disable-blink-features=AutomationControlled'], }); const page = await context.newPage(); await page.goto('https://chat.reddit.com/', { waitUntil: 'domcontentloaded', timeout: 30000, }); await page.waitForTimeout(5000); console.log('\n=== Finding chat rooms ==='); // Find the first chat room and click into it const firstChatUrl = await page.evaluate(` (function() { var rsApp = document.querySelector('rs-app'); if (!rsApp || !rsApp.shadowRoot) return null; // Look for room links function findLinks(root) { if (!root) return []; var links = Array.from(root.querySelectorAll('a[href*="/room/"]')); var allElements = root.querySelectorAll('*'); for (var i = 0; i < allElements.length; i++) { if (allElements[i].shadowRoot) { links = links.concat(findLinks(allElements[i].shadowRoot)); } } return links; } var links = findLinks(rsApp.shadowRoot); if (links.length === 0) return null; return links[0].href; })() `); console.log('First chat URL:', firstChatUrl); if (firstChatUrl) { await page.goto(firstChatUrl, { waitUntil: 'domcontentloaded', timeout: 15000 }); await page.waitForTimeout(3000); } console.log('\n=== Inspecting message area DOM structure ==='); const domInfo = await page.evaluate(` (function() { var results = { rsApp: false, rsRoom: false, rsRoomShadow: false, customElements: [], allImages: [], messageStructure: [], shadowRootChain: [], }; var rsApp = document.querySelector('rs-app'); if (!rsApp || !rsApp.shadowRoot) { results.error = 'No rs-app shadow root found'; return results; } results.rsApp = true; var rsRoom = rsApp.shadowRoot.querySelector('rs-room'); if (!rsRoom) { results.error = 'No rs-room found'; return results; } results.rsRoom = true; results.rsRoomShadow = !!rsRoom.shadowRoot; // Find ALL custom elements and their shadow roots recursively function mapShadowDom(root, depth, path) { if (!root || depth > 8) return; var allEls = root.querySelectorAll('*'); for (var i = 0; i < allEls.length; i++) { var el = allEls[i]; var tag = el.tagName.toLowerCase(); if (tag.includes('-') || el.shadowRoot) { var entry = { tag: tag, depth: depth, path: path + ' > ' + tag, hasShadow: !!el.shadowRoot, childCount: el.childElementCount, className: (el.className || '').toString().slice(0, 100), }; results.customElements.push(entry); if (el.shadowRoot) { mapShadowDom(el.shadowRoot, depth + 1, path + ' > ' + tag + ' [shadow]'); } } } } var searchRoot = rsRoom.shadowRoot || rsRoom; mapShadowDom(searchRoot, 0, 'rs-room'); // Find ALL images recursively through shadow DOMs function findAllImages(root, depth, path) { if (!root || depth > 8) return; var imgs = root.querySelectorAll('img'); for (var i = 0; i < imgs.length; i++) { var img = imgs[i]; results.allImages.push({ src: (img.getAttribute('src') || '').slice(0, 200), alt: img.getAttribute('alt') || '', width: img.width, height: img.height, naturalWidth: img.naturalWidth, naturalHeight: img.naturalHeight, className: (img.className || '').toString().slice(0, 100), parentTag: img.parentElement ? img.parentElement.tagName.toLowerCase() : 'none', parentClass: img.parentElement ? (img.parentElement.className || '').toString().slice(0, 100) : '', grandparentTag: img.parentElement && img.parentElement.parentElement ? img.parentElement.parentElement.tagName.toLowerCase() : 'none', path: path, depth: depth, }); } var allEls = root.querySelectorAll('*'); for (var j = 0; j < allEls.length; j++) { if (allEls[j].shadowRoot) { findAllImages(allEls[j].shadowRoot, depth + 1, path + ' > ' + allEls[j].tagName.toLowerCase() + '[shadow]'); } } } findAllImages(searchRoot, 0, 'rs-room'); // Try to understand message structure: look for repeated patterns // Walk the shadow DOM and find elements that look like messages function findMessageElements(root, depth, path) { if (!root || depth > 8) return; var allEls = root.querySelectorAll('*'); for (var i = 0; i < allEls.length; i++) { var el = allEls[i]; var tag = el.tagName.toLowerCase(); var cls = (el.className || '').toString().toLowerCase(); var text = (el.textContent || '').trim(); // Look for elements that contain message-like content if (tag.includes('message') || cls.includes('message') || tag.includes('msg') || cls.includes('msg') || tag.includes('chat') || cls.includes('bubble')) { results.messageStructure.push({ tag: tag, className: cls.slice(0, 150), path: path, depth: depth, textSnippet: text.slice(0, 100), childCount: el.childElementCount, hasImages: el.querySelectorAll('img').length, innerHTML: el.innerHTML.slice(0, 300), }); } if (el.shadowRoot) { findMessageElements(el.shadowRoot, depth + 1, path + ' > ' + tag + '[shadow]'); } } } findMessageElements(searchRoot, 0, 'rs-room'); return results; })() `); console.log('\n--- Shadow DOM Structure ---'); console.log('rs-app:', domInfo.rsApp); console.log('rs-room:', domInfo.rsRoom); console.log('rs-room has shadow:', domInfo.rsRoomShadow); if (domInfo.error) console.log('ERROR:', domInfo.error); console.log('\n--- Custom Elements with Shadow Roots ---'); for (const el of domInfo.customElements || []) { console.log(`${' '.repeat(el.depth)}${el.tag} (shadow: ${el.hasShadow}, children: ${el.childCount}) ${el.className ? '[class: ' + el.className + ']' : ''}`); } console.log('\n--- All Images Found ---'); for (const img of domInfo.allImages || []) { console.log(` [depth=${img.depth}] ${img.src}`); console.log(` size: ${img.width}x${img.height} (natural: ${img.naturalWidth}x${img.naturalHeight})`); console.log(` class: ${img.className}`); console.log(` parent: <${img.parentTag} class="${img.parentClass}">`); console.log(` path: ${img.path}`); console.log(''); } console.log('\n--- Message-like Elements ---'); for (const msg of domInfo.messageStructure || []) { console.log(` [depth=${msg.depth}] <${msg.tag}> images=${msg.hasImages}`); console.log(` class: ${msg.className}`); console.log(` text: ${msg.textSnippet}`); console.log(` path: ${msg.path}`); console.log(` html: ${msg.innerHTML}`); console.log(''); } // Also dump a broader view - find what's directly inside rs-room shadow console.log('\n=== Direct children of message area ==='); const directChildren = await page.evaluate(` (function() { var rsApp = document.querySelector('rs-app'); if (!rsApp || !rsApp.shadowRoot) return 'no rs-app'; var rsRoom = rsApp.shadowRoot.querySelector('rs-room'); if (!rsRoom) return 'no rs-room'; var root = rsRoom.shadowRoot || rsRoom; var children = []; for (var i = 0; i < root.children.length; i++) { var child = root.children[i]; children.push({ tag: child.tagName.toLowerCase(), className: (child.className || '').toString().slice(0, 200), id: child.id || '', childCount: child.childElementCount, hasShadow: !!child.shadowRoot, textSnippet: (child.textContent || '').trim().slice(0, 150), }); } return children; })() `); console.log(JSON.stringify(directChildren, null, 2)); console.log('\nDone. Closing browser...'); await context.close(); } main().catch(console.error);