import assert from 'node:assert/strict'; import fs from 'node:fs/promises'; import path from 'node:path'; import os from 'node:os'; import { fileURLToPath } from 'node:url'; import { chromium } from 'playwright'; // This test loads the real YouTube website. No routes or page fixtures. const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const artifacts = path.join(root, 'test-artifacts'); await fs.mkdir(artifacts,{recursive:true}); const profile = await fs.mkdtemp(path.join(os.tmpdir(),'ytdl-live-shorts-')); const context = await chromium.launchPersistentContext(profile,{ headless:true, viewport:{width:2160,height:1100}, executablePath:'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe', ignoreDefaultArgs:['--disable-extensions'], args:['--enable-unsafe-extension-debugging','--mute-audio'], }); const cards = 'ytm-shorts-lockup-view-model, yt-shorts-lockup-view-model, ytd-reel-item-renderer'; try { const cdp = await context.browser().newBrowserCDPSession(); const {id} = await cdp.send('Extensions.loadUnpacked',{path:path.join(root,'extension')}); const worker = context.serviceWorkers()[0] || await context.waitForEvent('serviceworker'); await worker.evaluate(() => chrome.storage.local.set({onboarding:{state:'complete',version:1}})); const page = await context.newPage(); const errors=[]; page.on('pageerror',error => errors.push(error.message)); console.log('Loading real YouTube search in Edge with the actual extension.'); await page.goto(process.env.YTDL_LIVE_URL || 'https://www.youtube.com/results?search_query=subnautica+shorts',{waitUntil:'domcontentloaded',timeout:60000}); console.log('YouTube document loaded:',page.url()); const consent=page.getByRole('button',{name:/Reject all|Отклонить все/i}).first(); if(await consent.isVisible()) await consent.click(); await page.locator(cards).first().waitFor({timeout:60000}); const first=page.locator(cards).first(); await first.scrollIntoViewIfNeeded(); await page.waitForTimeout(1000); const snapshot=()=>page.evaluate(selector => { const box=node=>node ? {...node.getBoundingClientRect().toJSON()} : null; const inspect=node=>({tag:node.tagName,class:node.className,display:getComputedStyle(node).display, overflowX:getComputedStyle(node).overflowX,overflowY:getComputedStyle(node).overflowY,rect:box(node),offsetWidth:node.offsetWidth}); return {url:location.href,viewport:{width:innerWidth,height:innerHeight},cards:[...document.querySelectorAll(selector)].slice(0,6).map(card=>{ const host=document.querySelector(`[data-ytdl-preview-for="${card.dataset.ytdlPreview}"]`); let ancestors=[];for(let element=card; element;element=element.parentElement)ancestors.push(inspect(element)); return {html:card.outerHTML.slice(0,16000),media:[...card.querySelectorAll('img,video')].map(inspect),ancestors,control:host ? {style:host.getAttribute('style'),rect:box(host),buttons:[...host.shadowRoot.querySelectorAll('button')].map(inspect)} : null}; }),videos:[...document.querySelectorAll('video')].map(video=>({time:video.currentTime,paused:video.paused,rect:box(video)}))}; },cards); const before=await snapshot(); await fs.writeFile(path.join(artifacts,'live-shorts-before.json'),JSON.stringify(before,null,2)); await page.screenshot({path:path.join(artifacts,'live-shorts-before.png')}); console.log('Actual Shorts cards:',before.cards.length,'Controls:',before.cards.map(card=>card.control?.style || 'missing')); if(process.env.YTDL_LIVE_DIAGNOSTIC==='1') { console.log('Live DOM diagnostics saved.'); } else { const key=await first.getAttribute('data-ytdl-preview'); assert.ok(key,'Real Shorts card must have extension controls'); const host=page.locator(`[data-ytdl-preview-for="${key}"]`); await host.locator('.download').waitFor({state:'visible',timeout:15000}); const thumbnail=first.locator('a').filter({has:first.locator('img')}).first(); const target=await thumbnail.count() ? thumbnail : first; await target.hover(); await page.waitForFunction(() => [...document.querySelectorAll('video')].some(video => !video.paused && video.currentTime > 1 && video.getBoundingClientRect().width > 0),{},{timeout:20000}); const after=await snapshot(); await fs.writeFile(path.join(artifacts,'live-shorts-hover.json'),JSON.stringify(after,null,2)); await page.screenshot({path:path.join(artifacts,'live-shorts-hover.png')}); const shelf=first.locator('xpath=ancestor::*[self::grid-shelf-view-model or self::ytd-rich-shelf-renderer or self::ytd-reel-shelf-renderer][1]'); if(await shelf.count()) await shelf.screenshot({path:path.join(artifacts,'live-shorts-proof.png')}); assert.ok(after.videos.some(video=>!video.paused && video.time>1 && video.rect.width>0),'Real YouTube preview video must actually play'); await host.locator('.download').waitFor({state:'visible'}); assert.equal(await host.locator('.settings').isVisible(),true); const hit=await host.evaluate(node=>{const r=node.getBoundingClientRect();return document.elementFromPoint(r.left+17,r.top+17)===node;}); assert.equal(hit,true,'Real hover player must not cover the download button'); await host.locator('.settings').click(); await page.locator('#ytdl-menu iframe').waitFor({state:'visible'}); const menu=page.frameLocator('#ytdl-menu iframe'); await menu.locator('#helper-panel:not([data-state="checking"]):not([data-state="idle"])').waitFor({timeout:90000}); const helperStatus=await menu.locator('#helper-panel').getAttribute('data-state'); if(helperStatus==='running') assert.equal(await menu.locator('.dependency-row[data-status="installed"]').count(),5); else assert.equal(await menu.locator('.dependency-row[data-status="installed"]').count(),0,'Unavailable helper must not retain ready component indicators'); assert.ok(!/disconnected port object/i.test(await menu.locator('#helper-message').textContent())); console.log('Embedded menu actual helper state:',helperStatus,await menu.locator('#helper-message').textContent()); await fs.writeFile(path.join(artifacts,'live-shorts-result.json'),JSON.stringify({ url:page.url(),extensionId:id,version:JSON.parse(await fs.readFile(path.join(root,'extension','manifest.json'))).version, actualPreviewPlaying:true,controlsVisible:true,downloadHitTest:true,settingsOpened:true,embeddedConnectionModuleLoaded:true,embeddedHelperStatus:helperStatus, },null,2)); console.log('Live Shorts passed: actual preview playback, visible controls before/after hover, hit testing, settings click and actual helper state in the embedded menu.'); } await fs.writeFile(path.join(artifacts,'live-shorts-errors.json'),JSON.stringify(errors,null,2)); } finally { await context.close(); if(path.dirname(path.resolve(profile))===path.resolve(os.tmpdir()) && path.basename(profile).startsWith('ytdl-live-shorts-')) await fs.rm(profile,{recursive:true,force:true}); }