Files
m 523a756f5c
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled
Wire IDE symbol navigation panel
2026-05-16 20:48:31 +03:00

316 lines
18 KiB
JavaScript

import { chromium } from "playwright-core";
import { existsSync } from "node:fs";
const baseUrl = process.env.SFERA_WEB_URL ?? "http://192.168.200.60:3000";
const projectId = process.env.SFERA_PROJECT_ID ?? "demo";
const uniqueSuffix = Date.now().toString(36);
const browserPath =
process.env.SFERA_BROWSER_PATH ??
[
"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
"C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe"
].find((path) => existsSync(path));
if (!browserPath) {
throw new Error("No installed Chrome or Edge browser found. Set SFERA_BROWSER_PATH.");
}
const runtimeErrors = [];
const browser = await chromium.launch({
executablePath: browserPath,
headless: true
});
function assertNoFatalRuntimeErrors(stage) {
const fatalErrors = runtimeErrors.filter((message) => {
return (
message.includes("Runtime TypeError") ||
message.includes("Cannot read properties") ||
message.includes("Unhandled Runtime Error")
);
});
if (fatalErrors.length > 0) {
throw new Error(`${stage} failed:\n${fatalErrors.join("\n")}`);
}
}
async function collapseBottomPanelIfOpen(page) {
if ((await page.locator("[data-bottom-tool-panel]").count()) > 0) {
await page.keyboard.press("Alt+3");
try {
await page.locator("[data-bottom-tool-panel]").waitFor({ state: "detached", timeout: 3000 });
} catch {
const bottomToggle = page.getByRole("button", { name: /Нижняя панель|Bottom panel/ });
if ((await bottomToggle.count()) > 0 && await bottomToggle.first().getAttribute("aria-pressed") === "true") {
await bottomToggle.first().click();
}
await page.locator("[data-bottom-tool-panel]").waitFor({ state: "detached", timeout: 15000 });
}
}
}
try {
const page = await browser.newPage();
await page.setViewportSize({ width: 1680, height: 1050 });
page.on("pageerror", (error) => {
runtimeErrors.push(`pageerror: ${error.message}`);
});
page.on("console", (message) => {
if (message.type() === "error") {
runtimeErrors.push(`console: ${message.text()}`);
}
});
const response = await page.goto(`${baseUrl}/editor?lang=ru&project=${encodeURIComponent(projectId)}&mode=module`, {
waitUntil: "load",
timeout: 60000
});
if (!response?.ok()) {
throw new Error(`editor runtime smoke: ${response?.status()} ${response?.statusText()}`);
}
await page.locator('[data-active-mode="module"]').waitFor({ state: "visible", timeout: 15000 });
await page.getByRole("heading", { name: "Семантический diff" }).waitFor({ state: "visible", timeout: 15000 });
for (const marker of ["snapshot", "agent", "parser", "diagnostics", "active-task", "privacy", "ai-tokens", "current-user"]) {
await page.locator(`[data-status-item="${marker}"]`).waitFor({ state: "visible", timeout: 15000 });
}
await page.locator("[data-global-search-input]").waitFor({ state: "visible", timeout: 15000 });
await page.keyboard.press("Control+K");
const globalSearchFocused = await page.locator("[data-global-search-input]").evaluate((input) => document.activeElement === input);
if (!globalSearchFocused) {
throw new Error("Ctrl+K did not focus global search");
}
await page.evaluate(() => {
window.dispatchEvent(new KeyboardEvent("keydown", { key: "F5", bubbles: true, cancelable: true }));
});
await page.locator("[data-run-check-status]").waitFor({ state: "visible", timeout: 15000 });
await page.locator("[data-left-navigation-panel]").waitFor({ state: "visible", timeout: 15000 });
const hasVirtualScrollContainer =
(await page.locator("[data-virtual-scroll]").count()) > 0 ||
(await page.locator("[data-fallback-tree-scroll]").count()) > 0;
if (!hasVirtualScrollContainer) {
throw new Error("Left navigation panel has no virtual/fallback scroll container marker");
}
await page.locator("[data-right-context-inspector]").waitFor({ state: "visible", timeout: 15000 });
if ((await page.locator("[data-bottom-tool-panel]").count()) === 0) {
await page.getByRole("button", { name: /Нижняя панель|Bottom panel/ }).first().click();
}
await page.locator("[data-bottom-tool-panel]").waitFor({ state: "visible", timeout: 15000 });
for (const tabName of ["Проблемы", "Семантический diff", "Вывод", "История", "Тесты", "AI"]) {
await page.locator("[data-bottom-tool-panel]").getByText(tabName).first().waitFor({ state: "visible", timeout: 15000 });
}
await page.locator("[data-bottom-tool-panel] [data-guard-summary]").waitFor({ state: "visible", timeout: 15000 });
await page.locator("[data-fallback-tree-search-input]").fill("SFERA");
await page.getByText("SFERA", { exact: true }).first().waitFor({ state: "visible", timeout: 15000 });
await page.locator("[data-fallback-tree-search-input]").fill("");
await page.locator("[data-fallback-tree-filter='sfera']").click();
await page.getByText("SFERA", { exact: true }).first().waitFor({ state: "visible", timeout: 15000 });
await page.locator("[data-fallback-tree-filter='all']").click();
const lazyFilterCount = await page.locator("[data-lazy-tree-filter]").count();
if (lazyFilterCount > 0) {
await page.locator("[data-lazy-tree-filter='sfera']").click();
await page.getByText("SFERA", { exact: true }).first().waitFor({ state: "visible", timeout: 15000 });
await page.locator("[data-lazy-tree-filter='all']").click();
}
await page.keyboard.press("Alt+1");
await page.locator("[data-left-navigation-panel]").waitFor({ state: "detached", timeout: 15000 });
await page.locator("[data-panel-rail]").first().click();
await page.locator("[data-left-navigation-panel]").waitFor({ state: "visible", timeout: 15000 });
await page.keyboard.press("Alt+2");
await page.locator("[data-right-context-inspector]").waitFor({ state: "detached", timeout: 15000 });
await page.locator("[data-panel-rail]").last().click();
await page.locator("[data-right-context-inspector]").waitFor({ state: "visible", timeout: 15000 });
await page.keyboard.press("Alt+3");
await page.locator("[data-bottom-tool-panel]").waitFor({ state: "detached", timeout: 15000 });
await page.getByRole("button", { name: "Нижняя панель" }).click();
await page.locator("[data-bottom-tool-panel]").waitFor({ state: "visible", timeout: 15000 });
await page.keyboard.press("Alt+3");
await page.locator("[data-bottom-tool-panel]").waitFor({ state: "detached", timeout: 15000 });
const activeDocumentBeforeNext = await page.locator('[data-open-document][aria-pressed="true"]').first().getAttribute("data-open-document");
const openDocumentCount = await page.locator("[data-open-document]").count();
if (openDocumentCount < 2) {
throw new Error("Open Objects Bar has fewer than two switchable objects");
}
await page.evaluate(() => {
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", ctrlKey: true, bubbles: true, cancelable: true }));
});
const activeDocumentAfterNext = await page.locator('[data-open-document][aria-pressed="true"]').first().getAttribute("data-open-document");
if (!activeDocumentAfterNext || activeDocumentAfterNext === activeDocumentBeforeNext) {
throw new Error("Ctrl+Tab did not switch the active open object");
}
await page.evaluate(() => {
window.dispatchEvent(new KeyboardEvent("keydown", { key: "Tab", ctrlKey: true, shiftKey: true, bubbles: true, cancelable: true }));
});
await page.locator(`[data-open-document="${activeDocumentBeforeNext}"][aria-pressed="true"]`).waitFor({ state: "visible", timeout: 15000 });
await page.locator(`[data-open-document-pin="${activeDocumentAfterNext}"]`).click();
const pinnedCloseButton = page.locator(`[data-open-document-close="${activeDocumentAfterNext}"]`);
if (!(await pinnedCloseButton.isDisabled())) {
throw new Error("Pinned open object close button is not disabled");
}
await page.locator(`[data-open-document-pin="${activeDocumentAfterNext}"]`).click();
await pinnedCloseButton.click();
await page.locator(`[data-open-document="${activeDocumentAfterNext}"]`).waitFor({ state: "detached", timeout: 15000 });
const ctrlWCandidate = await page.evaluate(() => {
const buttons = Array.from(document.querySelectorAll<HTMLElement>("[data-open-document]"));
const candidate = buttons.find((button) => button.getAttribute("data-open-document-mode") !== "module");
return candidate?.getAttribute("data-open-document") ?? null;
});
if (ctrlWCandidate) {
const targetTab = page.locator(`[data-open-document="${ctrlWCandidate}"]`);
await targetTab.click();
await page.evaluate(() => {
window.dispatchEvent(new KeyboardEvent("keydown", { key: "w", ctrlKey: true, bubbles: true, cancelable: true }));
});
await targetTab.waitFor({ state: "detached", timeout: 15000 });
const moduleTabs = page.locator('[data-open-document-mode="module"]');
const moduleTabCount = await moduleTabs.count();
if (moduleTabCount > 0) {
await moduleTabs.first().click();
}
}
const openObjectsState = await page.evaluate((storageKey) => window.localStorage.getItem(storageKey), `sfera.open-objects.${projectId}`);
if (!openObjectsState?.includes(activeDocumentAfterNext)) {
throw new Error("Open Objects Bar state was not persisted");
}
await page.reload({ waitUntil: "load", timeout: 60000 });
await page.locator('[data-active-mode="module"]').waitFor({ state: "visible", timeout: 15000 });
const restoredState = await page.evaluate((storageKey) => window.localStorage.getItem(storageKey), `sfera.open-objects.${projectId}`);
const restoredOpenDocumentCount = await page.locator("[data-open-document]").count();
if (!restoredState && restoredOpenDocumentCount === 0) {
throw new Error("Open Objects Bar state was not restored after reload");
}
await page.locator(`[data-open-document="${activeDocumentAfterNext}"]`).waitFor({ state: "detached", timeout: 15000 });
const restoredModuleTabs = page.locator('[data-open-document-mode="module"]');
if ((await restoredModuleTabs.count()) > 0) {
await restoredModuleTabs.first().click();
}
await page.locator("[data-fast-bsl-editor]").waitFor({ state: "visible", timeout: 15000 });
await page.locator("[data-symbol-navigation-panel]").waitFor({ state: "visible", timeout: 15000 });
const symbolSearchInput = page.locator("#symbol-search-input");
await symbolSearchInput.fill("demo");
await symbolSearchInput.press("Enter");
await page.locator("[data-symbol-result]").first().waitFor({ state: "visible", timeout: 15000 });
await page.locator('button[data-editor-action="symbol-definition-row"]').first().click();
await page.locator("[data-symbol-definition]").waitFor({ state: "visible", timeout: 15000 });
const findUsagesButton = page.locator('button[data-editor-action="find-usages"]');
if ((await findUsagesButton.count()) > 0 && await findUsagesButton.isEnabled()) {
await findUsagesButton.click();
await page.locator("[data-symbol-references]").waitFor({ state: "visible", timeout: 5000 }).catch(() => {});
}
const applyButton = page.locator('button[data-editor-action="apply-to-sfera"]');
await applyButton.waitFor({ state: "visible", timeout: 15000 });
if (await applyButton.isEnabled()) {
await applyButton.click();
await page.getByText("Записано в SFERA").waitFor({ state: "visible", timeout: 15000 });
}
assertNoFatalRuntimeErrors("editor module runtime smoke");
const versionsResponse = await page.goto(`${baseUrl}/editor?lang=ru&project=${encodeURIComponent(projectId)}&mode=versions`, {
waitUntil: "load",
timeout: 60000
});
if (!versionsResponse?.ok()) {
throw new Error(`editor versions runtime smoke: ${versionsResponse?.status()} ${versionsResponse?.statusText()}`);
}
await page.getByRole("heading", { name: "Версии" }).waitFor({ state: "visible", timeout: 15000 });
await collapseBottomPanelIfOpen(page);
await page.getByText("Линия версий").first().waitFor({ state: "visible", timeout: 15000 });
const versionButtons = page.locator("button[data-version-id]");
const versionButtonCount = await versionButtons.count();
if (versionButtonCount > 0) {
await versionButtons.first().click();
await page.getByText("Полные данные").waitFor({ state: "visible", timeout: 15000 });
}
assertNoFatalRuntimeErrors("editor versions runtime smoke");
const rollbackButtons = page.locator('button[data-editor-action="rollback-plan"]');
const rollbackButtonCount = await rollbackButtons.count();
if (rollbackButtonCount > 0) {
await rollbackButtons.first().click();
await page.locator("[data-rollback-preview]").waitFor({ state: "visible", timeout: 15000 });
const applyRollbackButton = page.locator('button[data-editor-action="apply-rollback"]');
await applyRollbackButton.waitFor({ state: "visible", timeout: 15000 });
if (await applyRollbackButton.isEnabled()) {
await applyRollbackButton.click();
await page.locator("[data-rollback-apply-message]").waitFor({ state: "visible", timeout: 15000 });
}
}
assertNoFatalRuntimeErrors("editor rollback runtime smoke");
const metadataResponse = await page.goto(`${baseUrl}/editor?lang=ru&project=${encodeURIComponent(projectId)}&mode=properties`, {
waitUntil: "load",
timeout: 60000
});
if (!metadataResponse?.ok()) {
throw new Error(`editor metadata runtime smoke: ${metadataResponse?.status()} ${metadataResponse?.statusText()}`);
}
await page.getByRole("heading", { name: "Свойства" }).waitFor({ state: "visible", timeout: 15000 });
await collapseBottomPanelIfOpen(page);
await page.getByLabel("Имя объекта").fill(`АвтоОбъект${uniqueSuffix}`);
await page.getByLabel("Синоним").fill(`Авто объект ${uniqueSuffix}`);
const addAttributeButton = page.locator('button[data-editor-action="add-metadata-attribute"]');
await addAttributeButton.waitFor({ state: "visible", timeout: 15000 });
await addAttributeButton.click();
await page.getByRole("textbox", { name: "Имя реквизита" }).nth(1).fill("Комментарий");
await page.getByRole("textbox", { name: "Тип" }).nth(1).fill("Строка250");
const addFormButton = page.locator('button[data-editor-action="add-metadata-form"]');
await addFormButton.waitFor({ state: "visible", timeout: 15000 });
await addFormButton.click();
await page.getByRole("textbox", { name: "Имя формы" }).last().fill(`ФормаВыбора${uniqueSuffix}`);
const addCommandButton = page.locator('button[data-editor-action="add-metadata-command"]');
await addCommandButton.waitFor({ state: "visible", timeout: 15000 });
await addCommandButton.click();
await page.getByRole("textbox", { name: "Имя команды" }).last().fill(`Отправить${uniqueSuffix}`);
await page.getByRole("textbox", { name: "Обработчик" }).last().fill(`Отправить${uniqueSuffix}Команда`);
const metadataButton = page.locator('button[data-editor-action="apply-metadata-object"]');
await metadataButton.waitFor({ state: "visible", timeout: 15000 });
if (await metadataButton.isEnabled()) {
await metadataButton.click();
const draftMessage = page.locator("[data-metadata-draft-message]");
await draftMessage.waitFor({ state: "visible", timeout: 15000 });
try {
await page.locator("[data-metadata-draft-preview]").waitFor({ state: "visible", timeout: 15000 });
} catch (error) {
throw new Error(`metadata draft preview did not appear: ${await draftMessage.textContent()}`);
}
}
assertNoFatalRuntimeErrors("editor metadata runtime smoke");
const englishResponse = await page.goto(`${baseUrl}/editor?lang=en&project=${encodeURIComponent(projectId)}&mode=module`, {
waitUntil: "load",
timeout: 60000
});
if (!englishResponse?.ok()) {
throw new Error(`editor english runtime smoke: ${englishResponse?.status()} ${englishResponse?.statusText()}`);
}
await page.locator('[data-active-mode="module"]').waitFor({ state: "visible", timeout: 15000 });
await page.locator("[data-top-project-bar]").waitFor({ state: "visible", timeout: 15000 });
for (const selector of ["workspace", "project", "environment", "active-task"]) {
await page.locator(`[data-top-bar-selector="${selector}"]`).waitFor({ state: "visible", timeout: 15000 });
}
for (const action of ["project-settings", "create-project"]) {
await page.locator(`[data-top-bar-action="${action}"]`).waitFor({ state: "visible", timeout: 15000 });
}
for (const badge of ["api-status", "agent-status"]) {
await page.locator(`[data-top-bar-badge="${badge}"]`).waitFor({ state: "visible", timeout: 15000 });
}
await page.locator("[data-top-bar-language]").waitFor({ state: "visible", timeout: 15000 });
await page.locator('[data-top-bar-button="profile"]').waitFor({ state: "visible", timeout: 15000 });
if ((await page.locator("[data-bottom-tool-panel]").count()) === 0) {
await page.getByRole("button", { name: /Нижняя панель|Bottom panel/ }).first().click();
}
await page.locator("[data-bottom-tool-panel]").waitFor({ state: "visible", timeout: 15000 });
for (const tabName of ["Problems", "Semantic diff", "Output", "Change history", "Tests", "AI"]) {
await page.locator("[data-bottom-tool-panel]").getByText(tabName).first().waitFor({ state: "visible", timeout: 15000 });
}
for (const statusMarker of ["snapshot", "agent", "parser", "diagnostics", "active-task", "privacy", "ai-tokens", "current-user"]) {
await page.locator(`[data-status-item="${statusMarker}"]`).waitFor({ state: "visible", timeout: 15000 });
}
assertNoFatalRuntimeErrors("editor english runtime smoke");
console.log("ok editor runtime");
} finally {
await browser.close();
}