mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-29 21:45:54 +03:00
fix(web-test): причина сбоя запуска Chromium в тексте ошибки, установка браузера в Setup (#113)
На 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W4Lg8jEvTAoYYEbgx2cc7i
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
292b6c97de
commit
618874972b
@@ -35,10 +35,10 @@ SCRIPT
|
|||||||
## Setup (first time)
|
## Setup (first time)
|
||||||
|
|
||||||
```bash
|
```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
|
## URL resolution
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
import { softDeadline } from './deadline.mjs';
|
import { softDeadline } from './deadline.mjs';
|
||||||
import { statSync, mkdirSync, readdirSync } from 'fs';
|
import { statSync, mkdirSync, readdirSync, openSync, closeSync } from 'fs';
|
||||||
import { join as pathJoin } from 'path';
|
import { join as pathJoin, dirname } from 'path';
|
||||||
import { removePathSync } from './fsutil.mjs';
|
import { removePathSync } from './fsutil.mjs';
|
||||||
import { tmpdir } from 'os';
|
import { tmpdir, userInfo } from 'os';
|
||||||
import {
|
import {
|
||||||
browser, page, sessionPrefix, seanceId, recorder, highlightMode,
|
browser, page, sessionPrefix, seanceId, recorder, highlightMode,
|
||||||
contexts, activeContextName, activeMode, persistentUserDataDir,
|
contexts, activeContextName, activeMode, persistentUserDataDir,
|
||||||
@@ -52,6 +52,47 @@ function findExtension(overridePath) {
|
|||||||
return null;
|
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 (<version>.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 (<version>.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 */
|
/* isConnected moved to core/state.mjs */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,7 +192,7 @@ export async function connect(url, { extensionPath } = {}) {
|
|||||||
// Launch with 1C browser extension via persistent context
|
// Launch with 1C browser extension via persistent context
|
||||||
setPersistentUserDataDir(pathJoin(tmpdir(), 'pw-1c-ext-' + Date.now()));
|
setPersistentUserDataDir(pathJoin(tmpdir(), 'pw-1c-ext-' + Date.now()));
|
||||||
mkdirSync(persistentUserDataDir, { recursive: true });
|
mkdirSync(persistentUserDataDir, { recursive: true });
|
||||||
const context = await chromium.launchPersistentContext(persistentUserDataDir, {
|
const context = await launchWithDiagnosis(() => chromium.launchPersistentContext(persistentUserDataDir, {
|
||||||
headless: false,
|
headless: false,
|
||||||
args: [
|
args: [
|
||||||
'--start-maximized',
|
'--start-maximized',
|
||||||
@@ -160,12 +201,12 @@ export async function connect(url, { extensionPath } = {}) {
|
|||||||
],
|
],
|
||||||
viewport: null,
|
viewport: null,
|
||||||
permissions: ['clipboard-read', 'clipboard-write'],
|
permissions: ['clipboard-read', 'clipboard-write'],
|
||||||
});
|
}));
|
||||||
setBrowser(context); // persistent context IS the browser
|
setBrowser(context); // persistent context IS the browser
|
||||||
setPage(context.pages()[0] || await context.newPage());
|
setPage(context.pages()[0] || await context.newPage());
|
||||||
} else {
|
} else {
|
||||||
// Fallback: launch without extension
|
// 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({
|
const context = await browser.newContext({
|
||||||
viewport: null,
|
viewport: null,
|
||||||
permissions: ['clipboard-read', 'clipboard-write'],
|
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
|
// Persistent context: extension loads reliably, one window with tabs per context
|
||||||
setPersistentUserDataDir(pathJoin(tmpdir(), 'pw-1c-test-' + Date.now()));
|
setPersistentUserDataDir(pathJoin(tmpdir(), 'pw-1c-test-' + Date.now()));
|
||||||
mkdirSync(persistentUserDataDir, { recursive: true });
|
mkdirSync(persistentUserDataDir, { recursive: true });
|
||||||
setBrowser(await chromium.launchPersistentContext(persistentUserDataDir, {
|
setBrowser(await launchWithDiagnosis(() => chromium.launchPersistentContext(persistentUserDataDir, {
|
||||||
headless: false,
|
headless: false,
|
||||||
args: launchArgs,
|
args: launchArgs,
|
||||||
viewport: null,
|
viewport: null,
|
||||||
permissions: ['clipboard-read', 'clipboard-write'],
|
permissions: ['clipboard-read', 'clipboard-write'],
|
||||||
}));
|
})));
|
||||||
} else {
|
} else {
|
||||||
// Window mode: separate BrowserContext per slot, full cookie isolation
|
// 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);
|
setActiveMode(isolation);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
- База опубликована через Apache (`/web-publish`)
|
- База опубликована через Apache (`/web-publish`)
|
||||||
- Node.js 18+ установлен
|
- Node.js 18+ установлен
|
||||||
- Зависимости установлены: `cd .claude/skills/web-test/scripts && npm install`
|
- Зависимости и браузер установлены: `cd .claude/skills/web-test/scripts && npm install && npx playwright install chromium`
|
||||||
|
|
||||||
## Рабочий цикл
|
## Рабочий цикл
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
## Предусловия
|
## Предусловия
|
||||||
|
|
||||||
- База опубликована через Apache (`/web-publish`).
|
- База опубликована через 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).
|
- ffmpeg — нужен только если хотите видеозапись прогона как доказательство падения. Без него падения фиксируются скриншотами. Установка описана в [web-test-recording-guide.md](web-test-recording-guide.md).
|
||||||
|
|
||||||
## Как это устроено
|
## Как это устроено
|
||||||
|
|||||||
Reference in New Issue
Block a user