Files
YouTubeDL/tests/live-shorts.test.mjs
T
DepressedCat 5f2aad00f4
Check extension and helper / check (push) Canceled after 0s
Publish YouTubeDL 1.4.5 and website on Gitea
2026-10-10 23:53:17 +03:00

90 lines
6.8 KiB
JavaScript

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});
}