import assert from "node:assert/strict"; import fs from "node:fs/promises"; import path from "node:path"; import os from "node:os"; import { createHash } from "node:crypto"; 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"; const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); const artifacts = path.join(root, "test-artifacts"); const installedRoot = await installedHelperRoot(); await fs.mkdir(artifacts, { recursive: true }); const profile = await fs.mkdtemp(path.join(os.tmpdir(), "ytdl-lifecycle-test-")); const context = await chromium.launchPersistentContext(profile, { headless: true, acceptDownloads: true, executablePath: "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 { id } = await cdp.send("Extensions.loadUnpacked", { path: path.join(root, "extension") }); const worker = context.serviceWorkers()[0] || await context.waitForEvent("serviceworker"); const page = await context.newPage(); await page.goto(`chrome-extension://${id}/downloader.html`); await page.locator('#helper-panel[data-state="running"]').waitFor({ timeout: 90000 }); await page.locator('#setup[open]').waitFor(); await page.locator('#setup-next').click(); await page.locator('#setup-next:not([disabled])').waitFor(); assert.match(await page.locator('#setup-helper-status').textContent(), /компоненты готовы/); assert.match(await page.locator('#setup-gpu').textContent(), /RTX 5080/); await page.locator('#setup-next').click(); assert.ok(await page.locator('#setup-folder').inputValue()); await page.locator('#setup-quality').selectOption('1080'); await page.locator('#setup-next').click(); await page.locator('[data-setup-step="3"]').waitFor({state:'visible'}); await page.screenshot({path:path.join(artifacts,'onboarding-native.png'),animations:'disabled'}); await page.locator('#setup-next').click(); await page.locator('#setup').waitFor({state:'hidden'}); const firstRun = await worker.evaluate(() => chrome.storage.local.get(['preferences','onboarding'])); assert.equal(firstRun.onboarding.state,'complete'); assert.equal(firstRun.preferences.quality.height,1080); await page.reload(); await page.locator('#helper-panel[data-state="running"]').waitFor({timeout:90000}); assert.equal(await page.locator('#setup').evaluate(dialog => dialog.open),false); await page.locator("#helper-panel > summary").click(); assert.equal(await page.locator('.dependency-row[data-status="installed"]').count(), 5); await context.route("https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/latest", route => route.fulfill({ contentType: "text/html", body: "

релизов проекта test destination

" })); const releaseTab = page.waitForEvent("popup"); await page.locator("#helper-install").click(); const popup = await releaseTab; await popup.waitForURL("https://git.lonarpgproxy.space/OwnerLink/YouTubeDL/releases/latest"); assert.equal(await page.locator("#helper-install").getAttribute("download"), null); await popup.close(); const installerPath = path.join(artifacts, "YouTubeDL-Helper-Test.exe"); await fs.copyFile(path.join(root, "dist/YouTubeDL-Helper-Setup.exe"), installerPath); const downloaded = await fs.readFile(installerPath); assert.equal(downloaded.subarray(0, 2).toString(), "MZ"); assert.equal(createHash("sha256").update(downloaded).digest("hex"), createHash("sha256").update(await fs.readFile(path.join(root,"dist/YouTubeDL-Helper-Setup.exe"))).digest("hex")); console.log("Extension opens the external release page; installer comes from the separate release asset."); await page.locator("#helper-remove").click(); await page.locator('#helper-panel[data-state="missing"]').waitFor(); assert.equal(await page.locator('.dependency-row[data-status="installed"]').count(), 0); await page.locator("#helper-start").click(); await page.locator('#helper-panel[data-state="missing"]').waitFor(); const deadline = Date.now() + 40000; while (Date.now() < deadline) { try { await fs.access(path.join(installedRoot,"installation.json")); } catch { break; } await new Promise(resolve => setTimeout(resolve, 100)); } await assert.rejects(fs.access(path.join(installedRoot,"installation.json"))); console.log("Uninstall verified; missing helper cannot report Running."); const installed = await promisify(execFile)("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", path.join(root, "tools/run-installer-test.ps1"), "-InstallerPath", installerPath], { timeout: 600000 }); console.log(installed.stdout.trim()); await page.locator("#helper-start").click(); await page.locator('#helper-panel[data-state="running"]').waitFor({ timeout: 90000 }); assert.equal(await page.locator('.dependency-row[data-status="installed"]').count(), 5); assert.equal(await page.locator('.dependency-row button').count(), 0); assert.match(await page.locator('#helper-message').textContent(), /Автономный комплект/); assert.match(await page.locator('[data-dependency="python"] .dependency-version').textContent(), /3\.13\./); const restoredRoot = await installedHelperRoot(); const restoredMarker = JSON.parse((await fs.readFile(path.join(restoredRoot,'installation.json'),'utf8')).replace(/^\uFEFF/,'')); assert.equal(restoredMarker.version,'1.3.0'); await fs.access(path.join(restoredRoot,'YouTubeDL-Manager.exe')); assert.equal(path.dirname(path.dirname(path.resolve(restoredMarker.current))),path.resolve(restoredRoot),'Manager, marker and registered runtime must share the same physical installation'); if (!await page.locator('#helper-panel').evaluate(panel => panel.open)) await page.locator('#helper-panel > summary').click(); await page.locator('.dependencies summary').click(); await page.screenshot({ path: path.join(artifacts, "helper-components.png"), animations: "disabled" }); await fs.writeFile(path.join(artifacts, "helper-lifecycle.json"), JSON.stringify({ installer: "YouTubeDL-Helper-Setup.exe", externalReleaseLink: "passed", installerSHA256: createHash("sha256").update(downloaded).digest("hex"), firstRun: "passed", firstRunPersistence: "passed", uninstall: "passed", manualStart: "passed", autonomousBundle: "passed" }, null, 2)); console.log("Helper lifecycle passed: external release link, scoped uninstall, missing state, offline installation, manual start and bundled components."); } finally { await context.close(); if (path.dirname(path.resolve(profile)) === path.resolve(os.tmpdir()) && path.basename(profile).startsWith("ytdl-lifecycle-test-")) await fs.rm(profile, { recursive: true, force: true }); }