import assert from "node:assert/strict"; import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { createServer } from "node:http"; import { chromium } from "playwright"; const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const version = JSON.parse(await fs.readFile(path.join(root,"extension/manifest.json"),"utf8")).version; const artifacts = path.join(root, "test-artifacts"); await fs.mkdir(artifacts, { recursive: true }); const server = createServer(async (request, response) => { try { const url = new URL(request.url, "http://localhost"); if (!url.pathname.startsWith("/YouTubeDL/")) { response.writeHead(404).end(); return; } const relative = decodeURIComponent(url.pathname.slice("/YouTubeDL/".length)) || "index.html"; const file = path.resolve(root,"site",relative); if (!file.startsWith(path.resolve(root,"site") + path.sep)) { response.writeHead(400).end(); return; } const type = {".html":"text/html; charset=utf-8",".js":"text/javascript",".css":"text/css",".png":"image/png",".svg":"image/svg+xml"}[path.extname(file)] || "application/octet-stream"; response.writeHead(200,{"Content-Type":type});response.end(await fs.readFile(file)); } catch { response.writeHead(404).end(); } }); await new Promise(resolve => server.listen(0,"127.0.0.1",resolve)); const local = `http://127.0.0.1:${server.address().port}/YouTubeDL/`; const target = process.env.YTDL_SITE_URL || local; let browser; try { browser = await chromium.launch({ headless:true, executablePath:process.env.YTDL_TEST_BROWSER || "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" }); const context = await browser.newContext({ viewport:{width:1440,height:1050}, reducedMotion:"reduce", colorScheme:"light" }); const page = await context.newPage(), failures=[]; page.on("pageerror",error => failures.push(error.message)); page.on("response",response => { if (response.url().startsWith(target) && response.status() >= 400) failures.push(`${response.status()} ${response.url()}`); }); await page.goto(target,{waitUntil:"networkidle"}); assert.equal(await page.locator("h1").count(),1); await page.locator(".hero .primary").click(); assert.equal(new URL(page.url()).hash,"#install"); assert.ok(await page.locator(".download-grid").isVisible()); assert.equal(await page.locator('[data-download="extension"]').getAttribute("href"),`https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/download/v${version}/YouTubeDL-${version}.zip`); assert.equal(await page.locator('[data-download="helper"]').getAttribute("href"),"https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/download/v1.4.5/YouTubeDL-Helper-Setup.exe"); await page.locator('[data-screenshot="light"]').click(); await page.locator("#menu-screenshot").evaluate(image => image.decode()); assert.match(await page.locator("#menu-screenshot").getAttribute("src"), /menu-light\.png$/); await page.locator('[data-screenshot="dark"]').click(); await page.locator("#menu-screenshot").evaluate(image => image.decode()); await page.locator("#theme-toggle").click(); assert.equal(await page.locator("html").getAttribute("data-theme"),"dark"); await page.reload({waitUntil:"networkidle"}); assert.equal(await page.locator("html").getAttribute("data-theme"),"dark"); await page.locator("#theme-toggle").click(); await page.evaluate(() => scrollTo(0,0)); await page.locator(".hero").screenshot({path:path.join(artifacts,"site-hero.png")}); await page.locator(".download-grid").screenshot({path:path.join(artifacts,"site-downloads.png")}); await page.screenshot({path:path.join(artifacts,"site-desktop.png"),fullPage:true}); for (const width of [320,390,768]) { await page.setViewportSize({width,height:844}); await page.evaluate(() => scrollTo(0,0)); const overflow = await page.evaluate(() => [...document.querySelectorAll("body *")].filter(node => node.getBoundingClientRect().right > innerWidth + 1 && getComputedStyle(node).position !== "absolute").map(node => ({tag:node.tagName,class:node.className,right:Math.round(node.getBoundingClientRect().right)}))); if (await page.evaluate(() => document.documentElement.scrollWidth > innerWidth)) await page.screenshot({path:path.join(artifacts,`site-overflow-${width}.png`),fullPage:true}); assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth),`No horizontal overflow at ${width}px: ${JSON.stringify(overflow)}`); assert.ok(await page.locator("h1").isVisible()); await page.locator('nav a[href="#install"]').click(); assert.equal(new URL(page.url()).hash,"#install"); const summary = page.locator(".faqs summary").first();await summary.click(); assert.equal(await page.locator(".faqs details").first().getAttribute("open"),""); await summary.click(); await page.evaluate(() => scrollTo(0,0)); if(width===390) await page.screenshot({path:path.join(artifacts,"site-mobile.png"),fullPage:true}); } const images=await page.locator("img").evaluateAll(nodes => nodes.map(image=>({src:image.getAttribute("src"),okay:image.complete&&image.naturalWidth>0}))); assert.ok(images.every(image=>image.okay),JSON.stringify(images)); assert.deepEqual(failures,[]); await fs.writeFile(path.join(artifacts,"site-check.json"),JSON.stringify({url:target,desktop:"passed",widths:[320,390,768],themes:"passed",screenshots:"passed",downloadLinks:"passed",faq:"passed",errors:failures},null,2)); console.log(`Site passed at ${target}: desktop/mobile, Pages subpath, download links, theme persistence, screenshots, FAQ and no console/HTTP errors.`); } finally { if(browser)await browser.close();await new Promise(resolve=>server.close(resolve)); }