From 618874972b6c6dcb5a04b6d4196348289ff8e7dd Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Tue, 29 Sep 2026 21:33:28 +0300 Subject: [PATCH] =?UTF-8?q?fix(web-test):=20=D0=BF=D1=80=D0=B8=D1=87=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D0=B1=D0=BE=D1=8F=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D0=BA=D0=B0=20Chromium=20=D0=B2=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BA=D1=81=D1=82=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= =?UTF-8?q?,=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=83=D0=B7=D0=B5=D1=80=D0=B0=20=D0=B2=20Set?= =?UTF-8?q?up=20(#113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit На Windows Chromium, который не может стартовать, сообщает только «spawn UNKNOWN». После сбоя запуска движок теперь проверяет side-by-side манифест рядом с chrome.exe и дописывает в первую строку ошибки установленный факт: манифеста нет (установка неполная) или он нечитаем для текущего пользователя (ограничение песочницы). Проверка только после сбоя: Windows кэширует контекст активации запускавшегося chrome.exe, и нечитаемый манифест сам по себе не означает, что запуск упадёт. Setup: `npm install` браузер не скачивает — добавлен `npx playwright install chromium`. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01W4Lg8jEvTAoYYEbgx2cc7i --- .claude/skills/web-test/SKILL.md | 4 +- .../web-test/scripts/engine/core/session.mjs | 61 ++++++++++++++++--- docs/web-test-guide.md | 2 +- docs/web-test-regression-guide.md | 2 +- 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/.claude/skills/web-test/SKILL.md b/.claude/skills/web-test/SKILL.md index d42389a62..e2b1a700b 100644 --- a/.claude/skills/web-test/SKILL.md +++ b/.claude/skills/web-test/SKILL.md @@ -35,10 +35,10 @@ SCRIPT ## Setup (first time) ```bash -cd "${CLAUDE_SKILL_DIR}/scripts" && npm install +cd "${CLAUDE_SKILL_DIR}/scripts" && npm install && npx playwright install chromium ``` -Requires Node.js 18+. `npm install` downloads Playwright and Chromium. +Requires Node.js 18+. `npm install` fetches Playwright; `npx playwright install chromium` downloads the browser. ## URL resolution diff --git a/.claude/skills/web-test/scripts/engine/core/session.mjs b/.claude/skills/web-test/scripts/engine/core/session.mjs index 9e3cdce24..dc9b85cb6 100644 --- a/.claude/skills/web-test/scripts/engine/core/session.mjs +++ b/.claude/skills/web-test/scripts/engine/core/session.mjs @@ -1,12 +1,12 @@ -// web-test core/session v1.21 — Browser session lifecycle: connect/disconnect/attach/detach, multi-context registry. +// web-test core/session v1.22 — Browser session lifecycle: connect/disconnect/attach/detach, multi-context registry. // Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import { chromium } from 'playwright'; import { softDeadline } from './deadline.mjs'; -import { statSync, mkdirSync, readdirSync } from 'fs'; -import { join as pathJoin } from 'path'; +import { statSync, mkdirSync, readdirSync, openSync, closeSync } from 'fs'; +import { join as pathJoin, dirname } from 'path'; import { removePathSync } from './fsutil.mjs'; -import { tmpdir } from 'os'; +import { tmpdir, userInfo } from 'os'; import { browser, page, sessionPrefix, seanceId, recorder, highlightMode, contexts, activeContextName, activeMode, persistentUserDataDir, @@ -52,6 +52,47 @@ function findExtension(overridePath) { return null; } +/** + * On Windows a Chromium that cannot start reports only "spawn UNKNOWN". The usual cause is the + * side-by-side manifest next to chrome.exe (.manifest): missing from the install, or not + * readable by the current user (agent sandboxes). Append what we can establish to the first line + * of the error — some callers print only that line. Silent when nothing is established. + * Only after a failure: Windows caches the activation context of a chrome.exe that already ran, + * so an unreadable manifest does not by itself mean a launch will fail. + */ +function explainLaunchFailure(e) { + try { + if (process.platform !== 'win32' || !/spawn UNKNOWN/.test(e?.message || '')) return e; + const dir = dirname(chromium.executablePath()); + const manifests = readdirSync(dir).filter(f => f.toLowerCase().endsWith('.manifest')); + let hint = null; + if (manifests.length === 0) { + hint = `Chromium side-by-side manifest (.manifest) is missing next to chrome.exe in ${dir} — ` + + 'the browser install is incomplete. Reinstall: npx playwright install --force chromium'; + } else { + for (const m of manifests) { + try { closeSync(openSync(pathJoin(dir, m), 'r')); } + catch (err) { + if (err.code !== 'EPERM' && err.code !== 'EACCES') continue; + hint = `${pathJoin(dir, m)} exists but is not readable by user ${userInfo().username} (${err.code}) — ` + + 'likely a sandbox/permissions restriction, not a broken install. Check the launch outside the sandbox'; + break; + } + } + } + if (hint) { + const nl = e.message.indexOf('\n'); + e.message = nl < 0 ? `${e.message} — ${hint}` : `${e.message.slice(0, nl)} — ${hint}${e.message.slice(nl)}`; + } + } catch {} + return e; +} + +async function launchWithDiagnosis(fn) { + try { return await fn(); } + catch (e) { throw explainLaunchFailure(e); } +} + /* isConnected moved to core/state.mjs */ /** @@ -151,7 +192,7 @@ export async function connect(url, { extensionPath } = {}) { // Launch with 1C browser extension via persistent context setPersistentUserDataDir(pathJoin(tmpdir(), 'pw-1c-ext-' + Date.now())); mkdirSync(persistentUserDataDir, { recursive: true }); - const context = await chromium.launchPersistentContext(persistentUserDataDir, { + const context = await launchWithDiagnosis(() => chromium.launchPersistentContext(persistentUserDataDir, { headless: false, args: [ '--start-maximized', @@ -160,12 +201,12 @@ export async function connect(url, { extensionPath } = {}) { ], viewport: null, permissions: ['clipboard-read', 'clipboard-write'], - }); + })); setBrowser(context); // persistent context IS the browser setPage(context.pages()[0] || await context.newPage()); } else { // Fallback: launch without extension - setBrowser(await chromium.launch({ headless: false, args: ['--start-maximized'] })); + setBrowser(await launchWithDiagnosis(() => chromium.launch({ headless: false, args: ['--start-maximized'] }))); const context = await browser.newContext({ viewport: null, permissions: ['clipboard-read', 'clipboard-write'], @@ -429,15 +470,15 @@ export async function createContext(name, url, { extensionPath, isolation = 'tab // Persistent context: extension loads reliably, one window with tabs per context setPersistentUserDataDir(pathJoin(tmpdir(), 'pw-1c-test-' + Date.now())); mkdirSync(persistentUserDataDir, { recursive: true }); - setBrowser(await chromium.launchPersistentContext(persistentUserDataDir, { + setBrowser(await launchWithDiagnosis(() => chromium.launchPersistentContext(persistentUserDataDir, { headless: false, args: launchArgs, viewport: null, permissions: ['clipboard-read', 'clipboard-write'], - })); + }))); } else { // Window mode: separate BrowserContext per slot, full cookie isolation - setBrowser(await chromium.launch({ headless: false, args: launchArgs })); + setBrowser(await launchWithDiagnosis(() => chromium.launch({ headless: false, args: launchArgs }))); } setActiveMode(isolation); } diff --git a/docs/web-test-guide.md b/docs/web-test-guide.md index 41501e557..c15239752 100644 --- a/docs/web-test-guide.md +++ b/docs/web-test-guide.md @@ -12,7 +12,7 @@ - База опубликована через Apache (`/web-publish`) - Node.js 18+ установлен -- Зависимости установлены: `cd .claude/skills/web-test/scripts && npm install` +- Зависимости и браузер установлены: `cd .claude/skills/web-test/scripts && npm install && npx playwright install chromium` ## Рабочий цикл diff --git a/docs/web-test-regression-guide.md b/docs/web-test-regression-guide.md index b49ddbe0f..f669555d1 100644 --- a/docs/web-test-regression-guide.md +++ b/docs/web-test-regression-guide.md @@ -11,7 +11,7 @@ ## Предусловия - База опубликована через Apache (`/web-publish`). -- Установлен Node.js 18+, зависимости подняты: `cd .claude/skills/web-test/scripts && npm install`. +- Установлен Node.js 18+, зависимости и браузер подняты: `cd .claude/skills/web-test/scripts && npm install && npx playwright install chromium`. - ffmpeg — нужен только если хотите видеозапись прогона как доказательство падения. Без него падения фиксируются скриншотами. Установка описана в [web-test-recording-guide.md](web-test-recording-guide.md). ## Как это устроено