mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-18 00:29:42 +03:00
feat(web-test): ловить диалог авторизации + чистая диагностика в start/run
Второй вход в ту же ловушку: публикация без пользователя (нет Usr= в vrd). Клиент показывает диалог «Пользователь/Пароль/Войти», ввод учётных данных движок не поддерживает — но вместо ошибки он ждал 67.7с, а затем closeModals() жал Escape и диалог ИСЧЕЗАЛ. Оставалась пустая страница, объявленная здоровым стартом, и дальше та же ложь про «режим отображения панели». Разведка изменила постановку в двух местах: 1. Улику затирал сам движок. Замерено: одного Escape достаточно, чтобы форма логина пропала. Поэтому детект обязан отработать ДО closeModals. 2. Комментарий «страница логина легитимна» оказался фикцией: войти руками через start было невозможно и раньше — движок сам уничтожал форму. Значит падать тут ничего не ломает, и развилка «в раннере падать, в интерактиве нет» отпадает. Комментарий переписан, чтобы следующий читатель не поверил ему больше, чем коду. Хуже, чем с лицензией: на диалоге авторизации сеанс 1С УСПЕВАЕТ создаться и держит лицензию впустую. Поэтому connect() освобождает его (disconnect) перед тем, как ошибка уйдёт наверх: cmdStart зовёт connect без catch, run.mjs не оборачивает команду, а убийство процесса лицензию не освобождает. - session.mjs (v1.19→v1.20): +1 якорь #authWindow в тот же предикат, своё сообщение с рецептом (-UserName → Usr=/Pwd=), очистка в connect(). - start.mjs (v1.0→v1.1), run.mjs (v1.0→v1.1): стартовый блокер — это диагноз, а не крах. Три строки и код 1 вместо стека, который указывал внутрь session.mjs и читался бы как поломка движка (модель пошла бы чинить не то). Проверено вживую: - шаг 0 (до кода): #authWindow не существует на здоровых стартах — ни в клиенте, ни при загрузке; проверено и на bpdemo с автологином (19.5с, опрос 100мс); - позитивный контроль на bpdemo-auth (BP_DEMO без Usr=): бросок за 2.7с вместо 67.7с, слот убран из реестра, лицензия вернулась — 6/6; - то же через connect(): сообщение, браузер закрыт, сеанс освобождён; - негативный контроль: полный регресс 25/25, ноль ложных срабатываний; start на bpdemo (автологин) поднимается штатно; - попутно на испорченном прогоне (сам съел лицензию соседней сессией) детект отработал в раннере на НАСТОЯЩЕМ дефиците: два мультиконтекстных теста упали с внятной причиной, остальные 23 прошли — прогон поехал дальше. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// web-test cli/commands/run v1.0 — autonomous connect → exec → disconnect (no server)
|
||||
// web-test cli/commands/run v1.1 — autonomous connect → exec → disconnect (no server)
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
@@ -13,7 +13,14 @@ export async function cmdRun(url, fileOrDash) {
|
||||
? await readStdin()
|
||||
: readFileSync(resolve(fileOrDash), 'utf-8');
|
||||
|
||||
await browser.connect(url);
|
||||
// Same as cmdStart: a startup blocker is a diagnosis, not a crash. connect() has already
|
||||
// released the seance and closed the browser; a stack trace pointing into session.mjs would
|
||||
// read as an engine bug and send the reader off to debug the wrong thing.
|
||||
try {
|
||||
await browser.connect(url);
|
||||
} catch (e) {
|
||||
die(e.message);
|
||||
}
|
||||
const result = await executeScript(code);
|
||||
await browser.disconnect();
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test cli/commands/start v1.0
|
||||
// web-test cli/commands/start v1.1
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import http from 'http';
|
||||
import { writeFileSync } from 'fs';
|
||||
@@ -10,7 +10,15 @@ import { handleRequest } from '../server.mjs';
|
||||
export async function cmdStart(url) {
|
||||
if (!url) die('Usage: node src/run.mjs start <url>');
|
||||
|
||||
const state = await browser.connect(url);
|
||||
// A startup blocker (no free 1C licence, publication asking for credentials) is a diagnosis,
|
||||
// not a crash — connect() already released the seance and closed the browser, so print the
|
||||
// message and leave instead of dumping a stack trace that reads like an engine failure.
|
||||
let state;
|
||||
try {
|
||||
state = await browser.connect(url);
|
||||
} catch (e) {
|
||||
die(e.message);
|
||||
}
|
||||
|
||||
const httpServer = http.createServer(handleRequest);
|
||||
httpServer.listen(0, '127.0.0.1', () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// web-test core/session v1.19 — Browser session lifecycle: connect/disconnect/attach/detach, multi-context registry.
|
||||
// web-test core/session v1.20 — Browser session lifecycle: connect/disconnect/attach/detach, multi-context registry.
|
||||
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import { chromium } from 'playwright';
|
||||
@@ -61,15 +61,23 @@ function findExtension(overridePath) {
|
||||
* session-less slot behind; the first engine call then produced a plainly wrong diagnosis
|
||||
* ("Section panel is in icon-only mode…"). Measured: 66s wasted, then a lie.
|
||||
*
|
||||
* Contract: throw ONLY on positive evidence of a startup dialog. A missing client marker is NOT
|
||||
* evidence — a login page legitimately never shows it (that is what the fallback below is for).
|
||||
* Two blockers are recognised, both by id:
|
||||
* #messageBoxText — the startup message box (no free licence, and any other startup error)
|
||||
* #authWindow — the login dialog: the publication wants credentials, which the engine
|
||||
* cannot supply. Landing here used to be treated as legitimate ("login
|
||||
* page"), but that was fiction: closeModals() presses Escape 5x right after
|
||||
* this wait, which dismisses the dialog and leaves a blank page reported as
|
||||
* a healthy start. Nobody could ever log in by hand either.
|
||||
*
|
||||
* Contract: throw ONLY on positive evidence — a visible dialog. A missing client marker is NOT
|
||||
* evidence (an unknown or merely slow start must keep its old behaviour), hence the fallback.
|
||||
*
|
||||
* Anchors are ids, never text: the platform's wording is locale-dependent and only gets quoted
|
||||
* into the message. Verified on this stand — on a healthy start #messageBoxText does not exist at
|
||||
* all, neither in the loaded client nor at any point while the shell boots (polled every 100ms).
|
||||
* The offsetWidth/text conjuncts guard the case where a future build pre-renders it hidden, the
|
||||
* way the shell already pre-renders its login form. offsetWidth (not offsetParent — that is null
|
||||
* for position:fixed, and #ps0win is an overlay) matches the convention in errors.mjs.
|
||||
* into the message. Verified on this stand — on a healthy start neither #messageBoxText nor
|
||||
* #authWindow exists at all, in the loaded client or at any point while the shell boots (polled
|
||||
* every 100ms, including bpdemo's 19.5s auto-login boot). The offsetWidth/text conjuncts guard a
|
||||
* future build that pre-renders them hidden. offsetWidth (not offsetParent — that is null for
|
||||
* position:fixed, and #ps0win is an overlay) matches the convention in errors.mjs.
|
||||
*/
|
||||
async function waitForClientOrStartupBlock(pg, url, timeout = INIT_TIMEOUT) {
|
||||
let outcome;
|
||||
@@ -78,6 +86,8 @@ async function waitForClientOrStartupBlock(pg, url, timeout = INIT_TIMEOUT) {
|
||||
if (document.querySelector('#themesCell_theme_0')) return 'client';
|
||||
const box = document.querySelector('#messageBoxText');
|
||||
if (box && box.offsetWidth > 0 && box.textContent.trim()) return 'blocked';
|
||||
const auth = document.querySelector('#authWindow');
|
||||
if (auth && auth.offsetWidth > 0) return 'auth';
|
||||
return false;
|
||||
}, null, { timeout }).then(h => h.jsonValue());
|
||||
} catch {
|
||||
@@ -96,11 +106,28 @@ async function waitForClientOrStartupBlock(pg, url, timeout = INIT_TIMEOUT) {
|
||||
const visibleText = (el) => (el && el.offsetWidth > 0) ? (el.innerText || '').trim() : '';
|
||||
if (document.querySelector('#themesCell_theme_0')) return null; // the client won after all
|
||||
const text = visibleText(document.querySelector('#messageBoxText'));
|
||||
return text ? { text, seances: visibleText(document.querySelector('#seancesToFinish')) } : null;
|
||||
if (text) return { kind: 'blocked', text, seances: visibleText(document.querySelector('#seancesToFinish')) };
|
||||
const auth = document.querySelector('#authWindow');
|
||||
if (auth && auth.offsetWidth > 0) return { kind: 'auth', text: visibleText(auth) };
|
||||
return null;
|
||||
}).catch(() => null);
|
||||
if (!evidence) return; // flicker — carry on exactly as before
|
||||
|
||||
const oneLine = (s) => s.replace(/\s+/g, ' ').trim().slice(0, 300);
|
||||
|
||||
if (evidence.kind === 'auth') {
|
||||
// The publication asks a human for credentials. The engine cannot answer, and until now it
|
||||
// did something worse than fail: it waited 66s and then closeModals()'s Escape dismissed the
|
||||
// dialog, leaving a blank page reported as a healthy start. Note a seance IS already created
|
||||
// here (unlike the licence case) and holds a licence — callers release it before rethrowing.
|
||||
throw new Error(
|
||||
`1C requires interactive login before the web client loads: "${oneLine(evidence.text)}"` +
|
||||
'\n Движок ввод учётных данных не поддерживает — опубликуйте базу с пользователем' +
|
||||
' (web-publish -UserName … → Usr=/Pwd= в vrd) либо укажите его в строке соединения.' +
|
||||
`\n URL: ${url}`
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`1C startup blocked before the web client loaded: "${oneLine(evidence.text)}"` +
|
||||
(evidence.seances ? `\n Сеансы, предложенные платформой к завершению: ${oneLine(evidence.seances)}` : '') +
|
||||
@@ -160,8 +187,18 @@ export async function connect(url, { extensionPath } = {}) {
|
||||
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: LOAD_TIMEOUT });
|
||||
}
|
||||
|
||||
// Wait for 1C to initialize — or fail fast if the startup shell blocks the client
|
||||
await waitForClientOrStartupBlock(page, url);
|
||||
// Wait for 1C to initialize — or fail fast if the startup shell blocks the client.
|
||||
// MUST run before closeModals(): that presses Escape 5x, which dismisses the auth dialog and
|
||||
// destroys the evidence (measured — one Escape blanks the page).
|
||||
try {
|
||||
await waitForClientOrStartupBlock(page, url);
|
||||
} catch (e) {
|
||||
// On the auth dialog a 1C seance already exists and holds a licence, and killing the process
|
||||
// does NOT release it. cmdStart has no catch and run.mjs does not wrap the command, so the
|
||||
// error escapes and the process dies — release the seance here, while we still can.
|
||||
await softDeadline(disconnect(), 20000, 'disconnect(startup-block)');
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Try to close startup modals (Путеводитель etc.)
|
||||
await closeModals();
|
||||
|
||||
Reference in New Issue
Block a user