import assert from "node:assert/strict"; import fs from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { chromium } from "playwright"; const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const artifacts = path.join(root, "test-artifacts"); await fs.mkdir(artifacts, { recursive: true }); const browser = await chromium.launch({ headless: true, executablePath: "C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" }); const context = await browser.newContext({ viewport: { width: 520, height: 820 }, reducedMotion: "reduce" }); await context.addInitScript({ path: path.join(root, "tests/ui-fixture.js") }); await context.route("https://ui.ytdl.test/**", async route => { const name = new URL(route.request().url()).pathname.slice(1); if (!["downloader.html", "downloader.js", "onboarding.js", "ui-connection.js", "theme-init.js", "styles.css", "shared.js", "session.js"].includes(name)) return route.abort(); return route.fulfill({ body: await fs.readFile(path.join(root, "extension", name)), contentType: name.endsWith(".html") ? "text/html" : name.endsWith(".css") ? "text/css" : "application/javascript" }); }); const errors = []; context.on("page", page => page.on("pageerror", error => errors.push(error.message))); try { const page = await context.newPage(); await page.addInitScript(() => { window.__firstRun = true; window.__helperMissing = true; }); await page.goto("https://ui.ytdl.test/downloader.html?theme=dark"); await page.locator("#setup[open]").waitFor(); await page.screenshot({ path: path.join(artifacts, "onboarding-welcome.png"), animations: "disabled" }); await page.locator("#setup-next").click(); await page.locator('#setup-helper[data-state="missing"]').waitFor(); assert.equal(await page.locator("#setup-next").isDisabled(), true); assert.equal(await page.locator("#setup-install").isVisible(), true); assert.equal(await page.locator("#setup-install").getAttribute("href"), "https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/latest"); assert.equal(await page.locator("#setup-install").getAttribute("target"), "_blank"); assert.equal(await page.locator("#setup-install").getAttribute("download"), null); assert.equal(await page.locator("#setup-install-steps li").count(), 3); await page.screenshot({ path: path.join(artifacts, "onboarding-install.png"), animations: "disabled" }); // A connected helper with a broken component is insufficient to continue. await page.evaluate(() => { window.__helperMissing = false; window.__dependencyMissing = true; }); await page.locator("#setup-check").click(); await page.locator("#setup-check:not([disabled])").waitFor(); assert.equal(await page.locator("#setup-next").isDisabled(), true); assert.match(await page.locator("#setup-install").textContent(), /восстановления/); await page.evaluate(() => { window.__dependencyMissing = false; }); await page.locator("#setup-check").click(); await page.locator("#setup-next:not([disabled])").waitFor(); assert.match(await page.locator("#setup-helper-message").textContent(), /5\/5/); assert.match(await page.locator("#setup-gpu").textContent(), /RTX 5080.*NVENC/); await page.locator("#setup-updates").uncheck(); await page.locator("#setup-next:not([disabled])").waitFor(); assert.equal(await page.locator("#setup-updates").isChecked(), false); await page.locator("#setup-next").click(); await page.locator("#setup-folder").fill(""); await page.locator("#setup-next").click(); assert.match(await page.locator("#setup-error").textContent(), /Выберите папку/); await page.locator("#setup-pick-folder").click(); await page.locator("#setup-pick-folder:not([disabled])").waitFor(); assert.equal(await page.locator("#setup-folder").inputValue(), "D:\\Videos\\YouTube"); await page.locator("#setup-pick-work-folder").click(); await page.locator("#setup-pick-work-folder:not([disabled])").waitFor(); assert.equal(await page.locator("#setup-work-folder").inputValue(), "D:\\Videos\\YouTube"); await page.locator("#setup-work-folder").fill("F:\\Scratch"); await page.locator("#setup-mode").selectOption("audio"); assert.equal(await page.locator("#setup-quality-field").isVisible(), false); await page.locator("#setup-format").selectOption("mp3"); await page.locator("#setup-mode").selectOption("video"); await page.locator("#setup-format").selectOption("mkv"); await page.locator("#setup-quality").selectOption("1080"); await page.screenshot({ path: path.join(artifacts, "onboarding-settings.png"), animations: "disabled" }); await page.locator("#setup-next").click(); await page.locator('[data-setup-step="3"]').waitFor({ state: "visible" }); assert.match(await page.locator("#setup-saved").textContent(), /D:\\Videos\\YouTube/); await page.locator("#setup-next").click(); await page.locator("#setup").waitFor({ state: "hidden" }); const result = await page.evaluate(() => window.__fixtureStorage); assert.equal(result.onboarding.state, "complete"); assert.equal(result.preferences.videoContainer, "mkv"); assert.equal(result.preferences.audioContainer, "mp3"); assert.equal(result.preferences.quality.height, 1080); assert.equal(result.preferences.workFolder, "F:\\Scratch"); assert.equal(result.preferences.engine, "auto"); await page.locator("#open-setup").click(); await page.locator("#setup[open]").waitFor(); await page.keyboard.press("Escape"); await page.locator("#setup").waitFor({ state: "hidden" }); assert.equal(await page.evaluate(() => window.__fixtureStorage.onboarding.state), "skipped"); await page.close(); const narrow = await context.newPage(); await narrow.setViewportSize({ width: 340, height: 560 }); await narrow.addInitScript(() => { window.__firstRun = true; }); await narrow.goto("https://ui.ytdl.test/downloader.html?theme=light"); await narrow.locator("#setup[open]").waitFor(); await narrow.locator("#setup-next").click(); await narrow.locator("#setup-next:not([disabled])").waitFor(); const box = await narrow.locator("#setup").boundingBox(); assert.ok(box.x >= 0 && box.x + box.width <= 340 && box.y >= 0 && box.y + box.height <= 560); assert.equal(await narrow.locator("#setup-next").isVisible(), true); await narrow.screenshot({ path: path.join(artifacts, "onboarding-light-narrow.png"), animations: "disabled" }); await narrow.locator("#setup-skip").click(); await narrow.locator("#setup").waitFor({ state: "hidden" }); assert.equal(await narrow.evaluate(() => window.__fixtureStorage.onboarding.state), "skipped"); await narrow.close(); const existing = await context.newPage(); await existing.addInitScript(() => { window.__firstRun = true; window.__fixtureStorage.preferences = { folder: "D:\\Existing", videoContainer: "webm" }; }); await existing.goto("https://ui.ytdl.test/downloader.html"); await existing.locator('#helper-panel[data-state="running"]').waitFor(); assert.equal(await existing.locator("#setup").evaluate(dialog => dialog.open), false, "Configured profiles are not interrupted by a new guide"); assert.equal(await existing.locator("#folder").inputValue(), "D:\\Existing"); assert.deepEqual(errors, []); console.log("Onboarding passed: first launch, missing/broken helper, real-state gating, update toggle, folder validation, video/audio defaults, skip, reopen, Escape, existing profiles and narrow light theme."); } finally { await browser.close(); }