107 lines
6.9 KiB
JavaScript
107 lines
6.9 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 { execFile } from "node:child_process";
|
|
import { promisify } from "node:util";
|
|
import { fileURLToPath } from "node:url";
|
|
import { chromium } from "playwright";
|
|
import { installedHelperRoot } from "./installed-helper.mjs";
|
|
|
|
// Uses a fresh browser profile and the registered native host. No browser accounts.
|
|
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
const extension = path.join(root, "extension");
|
|
const artifacts = path.join(root, "test-artifacts");
|
|
const folder = path.join(artifacts, "media");
|
|
const videoId = process.env.YTDL_TEST_VIDEO_ID || "jNQXAC9IVRw";
|
|
assert.match(videoId, /^[\w-]{11}$/);
|
|
await fs.mkdir(folder, { recursive: true });
|
|
const profile = await fs.mkdtemp(path.join(os.tmpdir(), "ytdl-browser-test-"));
|
|
const context = await chromium.launchPersistentContext(profile, {
|
|
headless: true, viewport: { width: 1280, height: 900 },
|
|
executablePath: process.env.YTDL_TEST_BROWSER || "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe",
|
|
ignoreDefaultArgs: ["--disable-extensions"], args: ["--enable-unsafe-extension-debugging"],
|
|
});
|
|
try {
|
|
const cdp = await context.browser().newBrowserCDPSession();
|
|
const installed = await cdp.send("Extensions.loadUnpacked", { path: extension });
|
|
assert.equal(installed.id, "ndnoeiaecgmjnjneekaeapeecmmjheli");
|
|
const worker = context.serviceWorkers()[0] || await context.waitForEvent("serviceworker", { timeout: 30000 });
|
|
assert.equal(new URL(worker.url()).hostname, "ndnoeiaecgmjnjneekaeapeecmmjheli");
|
|
await worker.evaluate(() => chrome.storage.local.set({ onboarding: { state: "complete", version: 1 } }));
|
|
// Only the browser page is a fixture; inspection, streams and NVENC are real.
|
|
await context.route("https://www.youtube.com/**", route => route.fulfill({
|
|
contentType: "text/html", body: '<!doctype html><html dark><head><meta charset="utf-8"></head><body style="margin:30px;background:#0f0f0f;color:white;font:14px Segoe UI"><div style="height:340px;background:#182837;border-radius:15px"></div><h1>Me at the zoo</h1><ytd-watch-metadata><div id="owner" style="display:flex;align-items:center"><div id="subscribe-button"><button style="border:0;border-radius:22px;padding:12px 18px">Подписаться</button></div></div></ytd-watch-metadata></body></html>',
|
|
}));
|
|
const page = await context.newPage();
|
|
const errors = [];
|
|
page.on("pageerror", error => errors.push(error.message));
|
|
await page.goto(`https://www.youtube.com/watch?v=${videoId}`);
|
|
await page.locator("#ytdl-subscribe-download .settings").click();
|
|
const ui = page.frameLocator("#ytdl-menu iframe");
|
|
try {
|
|
await Promise.race([
|
|
ui.locator("#download-form").waitFor({ state: "visible", timeout: 180000 }),
|
|
ui.locator('#alert:not([hidden])').waitFor({state:'visible',timeout:180000}).then(async()=>{ throw new Error(await ui.locator('#alert').innerText()); }),
|
|
]);
|
|
}
|
|
catch (error) {
|
|
console.log('Connection diagnostics:', await ui.locator('body').innerText());
|
|
console.log('Page errors:', errors);
|
|
await page.screenshot({path:path.join(artifacts,'browser-native-error.png'),animations:'disabled'});
|
|
throw error;
|
|
}
|
|
assert.match(await ui.locator("#hardware").textContent(), /RTX 5080/);
|
|
assert.equal(await ui.locator("#helper-status").textContent(), "Помощник работает");
|
|
assert.match(await ui.locator("#helper-message").textContent(), /Автономный комплект/);
|
|
assert.equal(await ui.locator('.dependency-row[data-status="installed"]').count(), 5);
|
|
await page.evaluate(() => document.documentElement.removeAttribute("dark"));
|
|
await ui.locator('html[data-theme="light"]').waitFor();
|
|
await page.evaluate(() => document.documentElement.setAttribute("dark", ""));
|
|
await ui.locator('html[data-theme="dark"]').waitFor();
|
|
const qualities = await ui.locator("#quality option").evaluateAll(nodes => nodes.map(n => ({ id: n.value, text: n.textContent })));
|
|
const separate = qualities.filter(q => q.id !== "best" && !q.text.includes("со звуком")).at(-1);
|
|
assert.ok(separate, "A real separate video stream must be available");
|
|
await ui.locator("#quality").selectOption(separate.id);
|
|
await ui.locator("#codec").selectOption("hevc");
|
|
await ui.locator(".advanced summary").click();
|
|
await ui.locator("#engine").selectOption("nvenc");
|
|
await ui.locator("#filename").fill("Browser-native-HEVC");
|
|
await ui.locator("#folder").fill(folder);
|
|
await ui.locator("#save-settings").click();
|
|
try { await page.locator("#ytdl-menu").waitFor({ state: "detached", timeout: 15000 }); }
|
|
catch (error) {
|
|
console.log("Menu alert:", await ui.locator("#alert").textContent());
|
|
console.log("Jobs:", await worker.evaluate(async () => (await chrome.storage.local.get("jobs")).jobs));
|
|
await page.screenshot({ path: path.join(artifacts, "browser-native-error.png"), animations: "disabled" });
|
|
throw error;
|
|
}
|
|
await page.locator("#ytdl-subscribe-download .download").click();
|
|
await page.locator("#ytdl-notifications .toast").waitFor();
|
|
console.log("Real extension connected; download started, on-page toast visible.");
|
|
await page.locator("#ytdl-notifications .toast.complete, #ytdl-notifications .toast.error").waitFor({ timeout: 240000 });
|
|
const saved = await worker.evaluate(async () => (await chrome.storage.local.get("jobs")).jobs);
|
|
const job = Object.values(saved).find(job => job.result?.path && job.url.includes(videoId));
|
|
assert.ok(job, JSON.stringify(saved));
|
|
assert.equal(job.status, "complete", job.error);
|
|
assert.equal(job.result.engine, "nvenc");
|
|
assert.equal(job.result.codec, "hevc");
|
|
const installedRoot = await installedHelperRoot();
|
|
const installedBundle = JSON.parse((await fs.readFile(path.join(installedRoot, "installation.json"), "utf8")).replace(/^\uFEFF/, ""));
|
|
const bundle = JSON.parse(await fs.readFile(path.join(installedBundle.current, "bundle.json"), "utf8"));
|
|
const probe = await promisify(execFile)(path.join(installedBundle.current, bundle.tools.ffprobe), ["-v", "error", "-show_streams", "-of", "json", job.result.path]);
|
|
const streams = JSON.parse(probe.stdout).streams;
|
|
assert.ok(streams.some(s => s.codec_type === "video" && s.codec_name === "hevc"));
|
|
assert.ok(streams.some(s => s.codec_type === "audio"));
|
|
assert.deepEqual(errors, []);
|
|
await page.screenshot({ path: path.join(artifacts, "browser-native-complete.png"), animations: "disabled" });
|
|
await fs.writeFile(path.join(artifacts, "browser-native.json"), JSON.stringify({ extensionId: new URL(worker.url()).hostname, job }, null, 2));
|
|
console.log("Browser/native checks passed: real MV3 extension, native messaging, separate audio, NVENC, saved file, live toast.");
|
|
} finally {
|
|
await context.close();
|
|
const resolved = path.resolve(profile);
|
|
if (path.dirname(resolved) === path.resolve(os.tmpdir()) && path.basename(resolved).startsWith("ytdl-browser-test-")) {
|
|
await fs.rm(resolved, { recursive: true, force: true });
|
|
}
|
|
}
|