docs(web-modules): ARCHITECTURE, CLAUDE.md, README, e2e под модули
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
77ff12d54d
commit
e2c925bff2
@ -89,25 +89,32 @@
|
||||
общие части (`web/src/ui/` — компоненты дизайн-системы AURA, `web/src/styles/tokens.css` — токены и единственное место с
|
||||
литералами цвета, `web/src/api/` — клиент HTTP, `web/src/auth/`, `web/src/projects/` — проект в адресе `?project=<метка>`,
|
||||
выпадающий список, окно «Новый проект») и модули `web/src/modules/<имя>/`. Контракт веб-модуля — `HubModule` в
|
||||
`web/src/modules/types.ts` (`pages`, `nav`, `Provider`, `SidebarStatus`, `TopbarNotice`, `useOverlay`, `useOnProjectCreated`);
|
||||
`web/src/modules/types.ts` (`name`, `title`, `pages`, `nav`, `Provider`, `status`, `Summary`, `useOverlay`, `projectFields`);
|
||||
список модулей — константа `MODULES` в `web/src/modules/index.ts` (хуки вызываются в постоянном порядке).
|
||||
|
||||
Состояние модуля — данные, а не компонент: `status` (`ModuleStatus`) описывает агента (`agent`), охват (`scope`: `all`/`project`), свежесть
|
||||
(`freshness(project)`), действие обновления (`refresh`: подпись, `start`, `isOwn`) и перечитывание после пакета (`invalidate`). Рисует и ведёт
|
||||
его ядро: трекер пакетов и агентов — `web/src/shell/activity/` (`ActivityProvider`: подхват идущего пакета и `GET /agents` раз в 60 с,
|
||||
опрос пакета каждые 3 с, `useModuleActivity(module)`, тексты `events.ts`/`agentText.ts`), блок состояния панели — `ModuleStatusBlock`, а модуль
|
||||
даёт только `Summary` — карточку на «Обзоре» (`OverviewPage`: карточки модулей и «Последние пакеты проекта»). Адреса: «Обзор» — `/app/`,
|
||||
страницы модуля — `/app/<модуль>/…` (`/app/jira/board`, `/app/jira/issues[/{key}]`, `/app/jira/feed`, `/app/wiki`); прежние `/app/board`,
|
||||
`/app/issues`, `/app/issues/{key}`, `/app/feed` перенаправляются с сохранением `search` и `replace`.
|
||||
|
||||
Модуль `wiki` (`web/src/modules/wiki/index.tsx`): страница «Вики» (`/app/wiki`): дерево (`Tree`) с поиском (`Search`), страница
|
||||
(`WikiPage`, `PageView` — `react-markdown` + `remark-gfm`, без `rehype-raw`; ссылки `page:` ведут на страницу вики, `attachment:`
|
||||
скачивают файл через `download.ts`, `urlTransform.ts` пропускает только эти схемы и http(s)), форма корня без привязки (`RootForm`),
|
||||
плашка синхронизации в боковой панели (`SyncStatus`); поле «Корень вики» в окне «Новый проект» — через `projectFields`.
|
||||
сводка для «Обзора» (`Summary`) и `status` для ядра; поле «Корень вики» в окне «Новый проект» — через `projectFields`.
|
||||
|
||||
Модуль `jira` (`web/src/modules/jira/index.tsx`): страницы «Доска связей» (`/app/board`), «Задачи» (`/app/issues`), «Лента»
|
||||
(`/app/feed`), карточка `/app/issues/{key}` окном поверх страницы (`issues/overlay.tsx`, `IssueModal.tsx`; оболочка рисует
|
||||
Модуль `jira` (`web/src/modules/jira/index.tsx`): страницы «Доска связей» (`/app/jira/board`), «Задачи» (`/app/jira/issues`), «Лента»
|
||||
(`/app/jira/feed`), карточка `/app/jira/issues/{key}` окном поверх страницы (`issues/overlay.tsx`, `IssueModal.tsx`; оболочка рисует
|
||||
страницу по `location.state.background`). Внутри: `board/` (чистые `model`, `analysis`, `layout`, `storage.ts` — ключи
|
||||
`localStorage` прежней страницы, у проекта AWPCA прежние, у остальных с суффиксом `@<метка>`; отрисовка `components/`;
|
||||
golden-эталоны `__fixtures__/`), `issues/` (список, карточка, `wiki/` — разбор вики-разметки, `attachments/` — список,
|
||||
плашки и скачивание: `POST /attachments/{id}/fetch`, опрос `GET /attachments/{id}`, затем `api.blob` на `/content`),
|
||||
`feed/` (период, виды, группировка событий, `EventList`, общий с блоком «История» карточки), `refresh/` (кнопка и плашка
|
||||
обновления данных из Jira в боковой панели, отслеживание пакета: `RefreshProvider`, `useJiraRefresh`), `api/queries.ts`.
|
||||
`feed/` (период, виды, группировка событий, `EventList`, общий с блоком «История» карточки), `RefreshButton.tsx` (кнопка «Обновить из Jira» в шапках страниц), `Summary`/`status` для ядра, `api/queries.ts`.
|
||||
|
||||
Собирается `make web` в `src/sdlc_hub/web/app/`; `core/api` отдаёт сборку без токена (`/app/assets/*` — файлы, прочие
|
||||
`/app…` — `index.html`, 503 без сборки), `/` и `/links` перенаправляет на `/app/board`. Данные — только через HTTP API с
|
||||
`/app…` — `index.html`, 503 без сборки), `/` и `/links` перенаправляет на `/app/` («Обзор»). Данные — только через HTTP API с
|
||||
клиентским токеном; `mirror.board` отдаёт зеркало без преобразований, перевод в модель доски — в `board/model`.
|
||||
|
||||
Цепочки зависимостей (Python):
|
||||
|
||||
@ -18,7 +18,7 @@ SDLC-хаб с модулями jira и wiki. Мост Jira для АРМ КА:
|
||||
- `src/sdlc_hub/modules/wiki/` — модуль wiki: `schema`, `settings`, `paths`, `projects` (поле `wiki_root`), `commands` (`wiki_sync`, `wiki_fetch` и хуки очереди), `convert` (HTML → Markdown), `stage`, `sync`, `search` (FTS5), `attachments`, `tick`, `api`, `mcp`. Входная точка — `MODULE` в `modules/wiki/__init__.py`.
|
||||
- Граница: ядро не импортирует модули (кроме ленивого импорта в `registry.py` и шагах миграций `db.py`), типы команд и термины модулей (jira, wiki, eva) живут в модуле, новые имена в ядре — без названий модулей; проверяют `test_core_imports`, `test_core_has_no_jira_terms`, `test_core_has_no_module_terms` в `tests/test_registry.py`. Меток-исключений нет.
|
||||
- Цепочка ядра: `db` ← `settings`, `projects` ← `queue` ← `api` ← `cli`; `files` ← `queue`, `api`; реле `relay` — поток в `serve`; `backup` вызывается из `api` и из `db`. Модуль jira: `hooks → commands/mirror/history/refresh/attachments/fetch → core`.
|
||||
- Веб: оболочка и общее — `web/src/{shell,ui,api,projects,auth,styles}`, модули — `web/src/modules/<имя>/` (контракт `HubModule` в `modules/types.ts`, список — `modules/index.ts`); модуль jira — страницы доски, задач и ленты; модуль wiki (`web/src/modules/wiki/`) — страница «Вики» (`/app/wiki?project=<метка>`).
|
||||
- Веб: оболочка и общее — `web/src/{shell,ui,api,projects,auth,styles}`, модули — `web/src/modules/<имя>/` (контракт `HubModule` в `modules/types.ts`, список — `modules/index.ts`); состояние модулей (агент, свежесть, обновление) ведёт ядро в `web/src/shell/activity/`, модуль даёт `status` и `Summary` для «Обзора»; модуль jira — страницы доски, задач и ленты; модуль wiki (`web/src/modules/wiki/`) — страница «Вики» (`/app/wiki?project=<метка>`).
|
||||
- Подробная карта модулей и границ — `ARCHITECTURE.md`.
|
||||
|
||||
## Команды
|
||||
@ -26,7 +26,7 @@ SDLC-хаб с модулями jira и wiki. Мост Jira для АРМ КА:
|
||||
- `uv sync` — установка окружения.
|
||||
- `uv run sdlc-hub init --config PATH [--force]` — настройки проекта из `config.example.json`, создание токенов.
|
||||
- `uv run sdlc-hub serve [--host] [--port]` — локальный запуск хаба.
|
||||
- Веб-приложение — `http://127.0.0.1:8790/app/` (доска — `/app/board?project=<метка>`, задачи — `/app/issues?project=<метка>`, лента изменений — `/app/feed?project=<метка>`; вход по клиентскому токену); `/` и `/links` перенаправляют на доску.
|
||||
- Веб-приложение — `http://127.0.0.1:8790/app/` («Обзор» — `/app/?project=<метка>`, доска — `/app/jira/board?project=<метка>`, задачи — `/app/jira/issues?project=<метка>`, лента изменений — `/app/jira/feed?project=<метка>`, вики — `/app/wiki?project=<метка>`; вход по клиентскому токену); `/` и `/links` перенаправляют на «Обзор», прежние `/app/board`, `/app/issues`, `/app/feed` — на страницы модуля Jira.
|
||||
- `make web` — сборка приложения (после `git pull` на Mac mini, до перезапуска хаба); `make check` — pytest + lint и тесты фронта; `cd web && npm run dev` — разработка с прокси на тестовый хаб `bash web/scripts/test-hub.sh` (:8791); `cd web && npm run e2e` — Playwright; `cd web && npm run golden` — перегенерировать эталоны доски из `links.html@3931a88` (только при смене фикстур).
|
||||
- `uv run sdlc-hub import FOLDER` — импорт CSV-пакета `admin/jira-sync`; `uv run sdlc-hub mcp <модуль> [--project <метка>]` — MCP модуля по stdio (модуль обязателен: `jira` — сервер `sdlc-jira`, `wiki` — сервер `sdlc-wiki`; с привязкой сессии к проекту); `uv run sdlc-hub tokens` — показать токены (клиентский и по агенту на модуль).
|
||||
- `uv run sdlc-hub agent publish [--module jira|wiki] PATH` — опубликовать версию агента модуля для самообновления (только вверх; по умолчанию `jira`; публикация лежит в `release/<модуль>/`).
|
||||
|
||||
@ -217,7 +217,7 @@ uv run sdlc-hub agent publish --module wiki ../eva-agent/src/agent.py
|
||||
| `GET /issues/{key}` | клиент | задача целиком, с комментариями и журналом изменений `changes` (новые сверху) |
|
||||
| `GET /changes` | клиент | лента изменений: `since`, `until` (ISO со смещением или `Z`, иначе 422; по умолчанию — последние 24 ч), `project` (только свои задачи), `key`, `kinds`, `limit` (до 2000) → `{since, until, truncated, changes}` |
|
||||
| `GET /board` | клиент | `?project=<метка>` (неизвестная — 404): снимок для доски — свои задачи проекта, соседи (связи в обе стороны) и эпики своих задач, с `fact_status`/`work_kind`; связи — те, у которых хотя бы один конец — своя задача проекта (без `project` — все задачи и связи), `last_sync` — последний успешный полный прогон |
|
||||
| `GET /`, `GET /links` | — | перенаправление на `/app/board` |
|
||||
| `GET /`, `GET /links` | — | перенаправление на `/app/` («Обзор») |
|
||||
| `PUT /issues/{key}/note` | клиент | заметки (частично) |
|
||||
| `POST /batches`, `GET /batches`, `GET /batches/{id}` | клиент | пакеты команд; `project` в теле `POST /batches` — пакет проекта (проверки метки, ограничение задач проектом), `?project=` у `GET /batches` — только пакеты проекта |
|
||||
| `POST /batches/{id}/cancel` | клиент | отмена пакета |
|
||||
@ -234,8 +234,8 @@ curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:8790/issues?sprint=
|
||||
|
||||
## Веб-приложение
|
||||
|
||||
Адреса: `http://127.0.0.1:8790/app/` — доска `/app/board?project=<метка>`, задачи `/app/issues?project=<метка>`, лента изменений `/app/feed?project=<метка>` (период и виды событий, история — и в карточке задачи), карточка `/app/issues/{key}` — окно поверх списка или доски (закрытие — ✕, Esc, щелчок по фону; прямой адрес открывает окно поверх списка задач); `/` и
|
||||
`/links` перенаправляют на доску. Вход по клиентскому токену (`uv run sdlc-hub tokens`), токен хранится в браузере;
|
||||
Адреса: `http://127.0.0.1:8790/app/` — «Обзор» `/app/?project=<метка>` (карточки модулей Jira и «Вики» с состоянием агента, свежестью данных и обновлением, последние пакеты проекта), доска `/app/jira/board?project=<метка>`, задачи `/app/jira/issues?project=<метка>`, лента изменений `/app/jira/feed?project=<метка>`, вики `/app/wiki?project=<метка>` (период и виды событий, история — и в карточке задачи), карточка `/app/jira/issues/{key}` — окно поверх списка или доски (закрытие — ✕, Esc, щелчок по фону; прямой адрес открывает окно поверх списка задач); `/` и
|
||||
`/links` перенаправляют на «Обзор»; прежние адреса `/app/board`, `/app/issues`, `/app/feed` ведут на страницы модуля Jira. Вход по клиентскому токену (`uv run sdlc-hub tokens`), токен хранится в браузере;
|
||||
`localhost` и `127.0.0.1` — разные сайты для браузера, токен и раскладка у каждого свои. Выкатка на Mac mini:
|
||||
|
||||
```
|
||||
|
||||
@ -154,3 +154,6 @@ type HubModule = {
|
||||
| 50 | 24.10.2026 | Task 7, fix T7.I1: обёртка таблицы «Задач» при высоте окна ≥ 700 px получает `max-height: calc(100vh - 220px)` и `overflow-y:auto`, шапка липнет к обёртке; на низких окнах ограничения нет — шапка уходит со страницей | при `overflow-x:auto` обёртка — скроллер, и `position:sticky` шапки внутри неё без собственной вертикальной прокрутки не работает (решение 47 ошибочно считало, что шапка липнет к панели); на длинных списках шапка должна оставаться на виду; цена — вложенная вертикальная прокрутка (на телефонах высотой < 700 px её нет) | снять sticky/z-index и принять уходящую шапку |
|
||||
| 51 | 24.10.2026 | Task 7, fix T7.I2: порядок «страница/дерево» в `WikiPage` определяется `ResizeObserver` на `.wiki__body` с порогом `≤ 759.98` px — тем же, что `@container page` в `wiki.css`; `matchMedia` убран | окно и контейнер страницы (рядом боковая панель) расходятся, на окне ≈ 760–1050 px CSS уже в одну колонку, а DOM ещё нет; до первого замера (и без `ResizeObserver`) — широкая раскладка; ключи панелей сохраняют состояние дерева | вернуть `matchMedia` с запасом по ширине панели |
|
||||
| 52 | 24.10.2026 | Task 7, fix T7.I2: `useWikiDownload` возвращает объект через `useMemo` | перерисовка `WikiPage` (замер ширины) пересоздавала `{start,status}` → набор компонентов Markdown → ссылки-вложения перемонтировались (тест M-15 обнаружил); так же ломали бы их ввод в поиск и опрос статуса | не трогать хук и убрать состояние замера |
|
||||
| 53 | 24.10.2026 | Task 8: проверка «без горизонтальной прокрутки» на 1440 px — `document.documentElement.scrollWidth <= window.innerWidth`, а не `===` | при классической полосе прокрутки Chromium `scrollWidth` равен ширине без полосы (1425 против 1440), равенство невозможно; смысл проверки — отсутствие переполнения — сохранён | ложное «зелёное» только если переполнение скрыто тем же зазором; на 390 px (оверлейные полосы) проверка прежняя и строгая |
|
||||
| 54 | 24.10.2026 | Task 8, дефект: у `.issue-table-wrap` добавлен `position: relative` | скрытый текст «, просрочено» (`.visually-hidden`, `position:absolute`) не имел позиционированного предка внутри обёртки и выходил за её прокрутку: на 390 px `scrollWidth` страницы 778 при окне 390 | без правки на узком экране у «Задач» появлялась горизонтальная прокрутка всей страницы |
|
||||
| 55 | 24.10.2026 | Task 8: «Последние пакеты проекта» проверяются пакетом «Обновить вики» (пакет проекта), а не «Обновить из Jira»; для событий пакета Jira в e2e — хелпер `jiraEvent` (строка модуля в панели / полоса на узком экране) | пакет «Обновить из Jira» не привязан к проекту (охват «все проекты») и в список проекта не попадает; на странице несколько `role=status`, прежний `getByRole("status")` неоднозначен | только тесты; список пакетов проекта и так фильтруется хабом по `project` |
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { CW } from "../src/modules/jira/board/constants.ts";
|
||||
import { agentServeRefresh, login } from "./hub.ts";
|
||||
import { agentServeRefresh, jiraEvent, login } from "./hub.ts";
|
||||
|
||||
test.beforeEach(async ({ page }) => { await login(page); });
|
||||
|
||||
@ -42,20 +42,21 @@ test("«по датам» — колонки дат, подписи и стре
|
||||
});
|
||||
|
||||
test("«Обновить из Jira» с учебным агентом доходит до конца", async ({ page }) => {
|
||||
const btn = page.getByRole("button", { name: "Обновить из Jira" });
|
||||
// на странице две кнопки: значок в панели («— все проекты») и кнопка в шапке доски
|
||||
const btn = page.getByTestId("page-board").getByRole("button", { name: "Обновить из Jira", exact: true });
|
||||
await btn.click();
|
||||
await expect(btn).toBeDisabled();
|
||||
await expect(page.getByRole("status")).toContainText(/пакет #\d+/);
|
||||
await expect(jiraEvent(page)).toContainText(/пакет #\d+/);
|
||||
await agentServeRefresh();
|
||||
await expect(page.getByRole("status")).toContainText(/пакет #\d+ выполнен/, { timeout: 15_000 });
|
||||
await expect(jiraEvent(page)).toContainText(/пакет #\d+ выполнен/, { timeout: 15_000 });
|
||||
await expect(btn).toBeEnabled();
|
||||
await expect(page.getByText(/^данные на /)).toBeVisible();
|
||||
await expect(page.getByText(/^данные на .* · все проекты$/)).toBeVisible();
|
||||
await expect(page.locator(".board .card[data-key]")).not.toHaveCount(0);
|
||||
});
|
||||
|
||||
test("ⓘ открывает карточку окном поверх доски", async ({ page }) => {
|
||||
await page.locator('.board .card[data-key="AURORA-9001"]').getByRole("link", { name: "Открыть карточку AURORA-9001" }).click();
|
||||
await expect(page).toHaveURL(/\/app\/issues\/AURORA-9001$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\/AURORA-9001$/);
|
||||
await expect(page.getByRole("dialog").getByRole("heading", { name: "Схема свода" })).toBeVisible();
|
||||
await expect(page.getByTestId("page-board")).toBeAttached();
|
||||
await expect(page.getByRole("link", { name: "Доска связей", exact: true })).toHaveAttribute("aria-current", "page");
|
||||
@ -79,12 +80,12 @@ test("M-3: прокрутка и данные доски после окна к
|
||||
await page.locator(`.board .card[data-key="${key}"] a.open-card`).click();
|
||||
await expect(page.getByRole("dialog")).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page).toHaveURL(/\/app\/board\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/board\?project=AWPCA$/);
|
||||
expect(await page.evaluate(() => window.scrollY)).toBeGreaterThan(y - 2);
|
||||
expect(boardLoads).toBe(0); // доска не перезагружалась
|
||||
// боковая панель закреплена (#69): ссылки разделов видны и после прокрутки, щелчок не двигает доску
|
||||
await page.getByRole("link", { name: "Задачи", exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/app\/issues\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\?project=AWPCA$/);
|
||||
await page.getByRole("link", { name: "Доска связей", exact: true }).click();
|
||||
await expect.poll(() => page.evaluate(() => window.scrollY)).toBeGreaterThan(y - 2);
|
||||
});
|
||||
|
||||
@ -18,7 +18,7 @@ async function seedHistory() {
|
||||
|
||||
test("лента: событие за сегодня, карточка окном поверх ленты, история в карточке", async ({ page }) => {
|
||||
await seedHistory();
|
||||
await login(page, "/app/feed?project=AWPCA");
|
||||
await login(page, "/app/jira/feed?project=AWPCA");
|
||||
const group = page.getByTestId("feed-group").filter({ has: page.getByRole("link", { name: "AURORA-9002", exact: true }) });
|
||||
await expect(group.getByText("статус «В работе» → «Готово»").first()).toBeVisible();
|
||||
const other = page.getByRole("checkbox", { name: "Прочие поля" });
|
||||
@ -39,7 +39,7 @@ test("лента: событие за сегодня, карточка окно
|
||||
test("лента на 390 px: нет горизонтальной прокрутки при длинных значениях", async ({ page }) => {
|
||||
await seedHistory();
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await login(page, "/app/feed?project=AWPCA&kinds=status,other");
|
||||
await login(page, "/app/jira/feed?project=AWPCA&kinds=status,other");
|
||||
await expect(page.getByTestId("feed-group").first()).toBeVisible();
|
||||
const overflow = await page.evaluate(() => document.documentElement.scrollWidth - window.innerWidth);
|
||||
expect(overflow).toBeLessThanOrEqual(0);
|
||||
|
||||
@ -5,12 +5,12 @@ import { HUB_HOME, HUB_URL } from "../playwright.config.ts";
|
||||
export const tokens = () => JSON.parse(readFileSync(`${HUB_HOME}/tokens.json`, "utf8")) as { client: string; agents: Record<string, string> };
|
||||
const BASE = HUB_URL;
|
||||
|
||||
export async function login(page: Page, path = "/app/board") {
|
||||
export async function login(page: Page, path = "/app/jira/board") {
|
||||
await page.goto(path);
|
||||
await page.getByLabel("Клиентский токен хаба").fill(tokens().client);
|
||||
await page.getByRole("button", { name: "Войти" }).click();
|
||||
// оболочка: на широком экране — ссылки разделов в боковой панели, на узком — кнопка «Меню» (#69)
|
||||
await expect(page.getByRole("link", { name: "Доска связей" }).or(page.getByRole("button", { name: "Меню" }))).toBeVisible();
|
||||
await expect(page.getByRole("complementary", { name: "Панель приложения" }).getByRole("link", { name: "Доска связей" }).or(page.getByRole("button", { name: "Меню" }))).toBeVisible();
|
||||
}
|
||||
|
||||
async function call(path: string, body: unknown) {
|
||||
@ -27,3 +27,8 @@ export async function agentServeRefresh() {
|
||||
await call(`/agent/commands/${command.id}/result`, { status: "done", result: { issues: 0 }, jira_calls: 1, rate_limited: 0 });
|
||||
return command.id as number;
|
||||
}
|
||||
|
||||
// событие пакета Jira: на широком экране — строка модуля в боковой панели, на узком — полоса под верхней строкой
|
||||
export const jiraEvent = (page: Page) =>
|
||||
page.getByRole("complementary", { name: "Панель приложения" }).locator('[data-module="jira"]').getByRole("status")
|
||||
.or(page.getByTestId("topbar-events"));
|
||||
|
||||
@ -2,7 +2,7 @@ import { expect, test } from "@playwright/test";
|
||||
import { login } from "./hub.ts";
|
||||
|
||||
test("фильтр → карточка окном → заметка сохраняется и переживает перезагрузку", async ({ page }) => {
|
||||
await login(page, "/app/issues");
|
||||
await login(page, "/app/jira/issues");
|
||||
await page.getByRole("button", { name: /Фильтры/ }).click();
|
||||
await page.getByRole("combobox", { name: "Исполнитель" }).selectOption("Петров Пётр Петрович");
|
||||
await expect(page).toHaveURL(/assignee=/);
|
||||
@ -18,12 +18,12 @@ test("фильтр → карточка окном → заметка сохра
|
||||
await expect(page.getByRole("dialog").getByText("e2e: дописать тесты")).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0);
|
||||
await expect(page).toHaveURL(/\/app\/issues\?project=AWPCA&assignee=/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\?project=AWPCA&assignee=/);
|
||||
await expect(page.getByRole("combobox", { name: "Исполнитель" })).toHaveValue("Петров Пётр Петрович"); // панель осталась раскрытой (sessionStorage)
|
||||
});
|
||||
|
||||
test("фильтр в адресе в новой вкладке: панель свёрнута, плашка и счётчик видны, плашка снимает фильтр", async ({ page }) => {
|
||||
await login(page, "/app/issues?project=AWPCA&assignee=" + encodeURIComponent("Петров Пётр Петрович"));
|
||||
await login(page, "/app/jira/issues?project=AWPCA&assignee=" + encodeURIComponent("Петров Пётр Петрович"));
|
||||
await page.evaluate(() => sessionStorage.clear());
|
||||
await page.reload();
|
||||
const btn = page.getByRole("button", { name: /Фильтры/ });
|
||||
@ -37,15 +37,15 @@ test("фильтр в адресе в новой вкладке: панель с
|
||||
});
|
||||
|
||||
test("щелчок по ячейке строки открывает карточку, по выделенному тексту — нет", async ({ page }) => {
|
||||
await login(page, "/app/issues");
|
||||
await login(page, "/app/jira/issues");
|
||||
await page.getByRole("cell", { name: "Схема свода" }).click();
|
||||
await expect(page).toHaveURL(/\/app\/issues\/AURORA-9001/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\/AURORA-9001/);
|
||||
await expect(page.getByRole("dialog").getByRole("heading", { name: "Схема свода" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("окно поверх списка: прокрутка списка на месте при открытии и после закрытия", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 320 });
|
||||
await login(page, "/app/issues");
|
||||
await login(page, "/app/jira/issues");
|
||||
await expect(page.locator("table.issue-table tbody tr")).toHaveCount(9); // AURORA-9006 «Готово» скрыта
|
||||
const y = await page.evaluate(() => { window.scrollTo(0, document.documentElement.scrollHeight); return window.scrollY; });
|
||||
expect(y).toBeGreaterThan(50);
|
||||
@ -63,37 +63,37 @@ test("переход по связи и «назад»; ✕ и щелчок по
|
||||
await page.getByRole("link", { name: "AURORA-9001", exact: true }).click();
|
||||
const d = page.getByRole("dialog");
|
||||
await d.locator(".links-sec").getByRole("link", { name: "AURORA-9002", exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/app\/issues\/AURORA-9002$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\/AURORA-9002$/);
|
||||
await expect(d.getByRole("heading", { name: "API свода" })).toBeVisible();
|
||||
await page.goBack();
|
||||
await expect(d.getByRole("heading", { name: "Схема свода" })).toBeVisible();
|
||||
await d.locator(".links-sec").getByRole("link", { name: "AURORA-9002", exact: true }).click();
|
||||
await d.getByRole("button", { name: "Закрыть" }).click();
|
||||
await expect(page).toHaveURL(/\/app\/issues\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\?project=AWPCA$/);
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0);
|
||||
await page.getByRole("link", { name: "AURORA-9003", exact: true }).click();
|
||||
await expect(page.getByRole("dialog")).toBeVisible();
|
||||
await page.mouse.click(5, 300); // фон слева от окна
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0);
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL(/\/app\/board\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/board\?project=AWPCA$/);
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("прямой адрес и перезагрузка: окно поверх списка, ✕ ведёт в список", async ({ page }) => {
|
||||
await login(page, "/app/issues/AURORA-9001");
|
||||
await login(page, "/app/jira/issues/AURORA-9001");
|
||||
const d = page.getByRole("dialog");
|
||||
await expect(d.getByRole("heading", { name: "Схема свода" })).toBeVisible();
|
||||
await expect(page.getByTestId("page-issues")).toBeAttached();
|
||||
await page.reload();
|
||||
await expect(page.getByRole("dialog").getByRole("heading", { name: "Схема свода" })).toBeVisible();
|
||||
await page.getByRole("dialog").getByRole("button", { name: "Закрыть" }).click();
|
||||
await expect(page).toHaveURL(/\/app\/issues\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\?project=AWPCA$/);
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("карточка: описание свёрнуто, комментарии новые сверху и скрываются с запоминанием, команды свёрнуты", async ({ page }) => {
|
||||
await login(page, "/app/issues/AURORA-9001");
|
||||
await login(page, "/app/jira/issues/AURORA-9001");
|
||||
const d = page.getByRole("dialog");
|
||||
await expect(d.getByRole("heading", { name: "Комментарии (3)" })).toBeVisible();
|
||||
await expect(d.getByTestId("comments").getByRole("listitem").first()).toContainText("[мост] служебный комментарий");
|
||||
@ -106,7 +106,7 @@ test("карточка: описание свёрнуто, комментари
|
||||
expect((await d.locator("div.description").boundingBox())!.height).toBeGreaterThan(hClamped);
|
||||
await expect(d.getByText("Конец описания.")).toBeVisible();
|
||||
await expect(d.getByRole("cell", { name: "ensure_fields" })).toBeHidden();
|
||||
await d.getByText("Команды моста (2)").click();
|
||||
await d.getByText("Команды хаба (2)").click();
|
||||
await expect(d.getByRole("cell", { name: "ensure_fields" })).toBeVisible();
|
||||
await d.getByRole("button", { name: "Скрыть" }).click();
|
||||
await expect(d.getByText("Первый комментарий e2e")).toHaveCount(0);
|
||||
@ -116,7 +116,7 @@ test("карточка: описание свёрнуто, комментари
|
||||
});
|
||||
|
||||
test("разметка Jira: описание и комментарии отрендерены, «исходник» у каждого блока, ключ задачи — ссылка", async ({ page }) => {
|
||||
await login(page, "/app/issues/AURORA-9001");
|
||||
await login(page, "/app/jira/issues/AURORA-9001");
|
||||
const d = page.getByRole("dialog");
|
||||
const desc = d.locator("section", { has: page.getByRole("heading", { name: "Описание" }) });
|
||||
await expect(desc.getByRole("heading", { level: 3, name: "Контекст" })).toBeVisible();
|
||||
@ -144,7 +144,7 @@ test("разметка Jira: описание и комментарии отре
|
||||
});
|
||||
|
||||
test("C-1: повторное открытие из кэша — длинное описание свёрнуто с кнопкой «развернуть»", async ({ page }) => {
|
||||
await login(page, "/app/issues");
|
||||
await login(page, "/app/jira/issues");
|
||||
await page.getByRole("link", { name: "AURORA-9001", exact: true }).click();
|
||||
const d = page.getByRole("dialog");
|
||||
await expect(d.getByRole("button", { name: "развернуть" })).toBeVisible();
|
||||
@ -156,7 +156,7 @@ test("C-1: повторное открытие из кэша — длинное
|
||||
|
||||
test("I-1: телефон 375 px — таблица описания прокручивается внутри, заголовки таблицы без чужого стиля", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 800 });
|
||||
await login(page, "/app/issues/AURORA-9001");
|
||||
await login(page, "/app/jira/issues/AURORA-9001");
|
||||
const d = page.getByRole("dialog");
|
||||
await d.getByRole("button", { name: "развернуть" }).click();
|
||||
const th = d.getByRole("columnheader", { name: "Поле", exact: true });
|
||||
@ -167,7 +167,7 @@ test("I-1: телефон 375 px — таблица описания прокр
|
||||
});
|
||||
|
||||
test("несохранённая заметка: Esc спрашивает, «Остаться» оставляет окно, «Уйти» закрывает", async ({ page }) => {
|
||||
await login(page, "/app/issues");
|
||||
await login(page, "/app/jira/issues");
|
||||
await page.getByRole("link", { name: "AURORA-9003", exact: true }).click();
|
||||
const d = page.getByRole("dialog");
|
||||
await d.getByRole("button", { name: "Править" }).click();
|
||||
@ -179,12 +179,12 @@ test("несохранённая заметка: Esc спрашивает, «О
|
||||
await page.keyboard.press("Escape");
|
||||
await d.getByRole("button", { name: "Уйти" }).click();
|
||||
await expect(page.getByRole("dialog")).toHaveCount(0);
|
||||
await expect(page).toHaveURL(/\/app\/issues\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\?project=AWPCA$/);
|
||||
});
|
||||
|
||||
test("узкий экран: окно на всю ширину, заметки выше связей, без горизонтальной прокрутки", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 600, height: 800 });
|
||||
await login(page, "/app/issues/AURORA-9001");
|
||||
await login(page, "/app/jira/issues/AURORA-9001");
|
||||
const d = page.getByRole("dialog");
|
||||
await expect(d.getByRole("heading", { name: "Схема свода" })).toBeVisible();
|
||||
expect((await d.boundingBox())!.width).toBeGreaterThan(590);
|
||||
@ -198,7 +198,7 @@ test("узкий экран: окно на всю ширину, заметки
|
||||
});
|
||||
|
||||
test("закрытые скрыты по умолчанию, «Все» показывает их", async ({ page }) => {
|
||||
await login(page, "/app/issues");
|
||||
await login(page, "/app/jira/issues");
|
||||
await expect(page.locator("table.issue-table tbody tr")).toHaveCount(9);
|
||||
await expect(page.getByRole("link", { name: "AURORA-9006", exact: true })).toHaveCount(0);
|
||||
await page.getByRole("radio", { name: "Все" }).click();
|
||||
|
||||
@ -24,7 +24,7 @@ test("свёрнутая панель переживает перезагруз
|
||||
await expect(page.getByRole("button", { name: "Развернуть панель", exact: true })).toBeVisible();
|
||||
await expect.poll(async () => (await wrap.boundingBox())!.width).toBeGreaterThan(before + 150);
|
||||
await page.getByRole("link", { name: "Задачи" }).click();
|
||||
await expect(page).toHaveURL(/\/app\/issues\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\?project=AWPCA$/);
|
||||
await page.getByRole("button", { name: "Развернуть панель", exact: true }).click();
|
||||
await expect(page.getByRole("button", { name: "Свернуть панель" })).toBeVisible();
|
||||
});
|
||||
@ -37,7 +37,7 @@ test("390 px: «Меню» открывает панель, «Задачи» з
|
||||
const side = page.getByRole("complementary", { name: "Панель приложения" });
|
||||
await expect(side).toBeVisible();
|
||||
await side.getByRole("link", { name: "Задачи" }).click();
|
||||
await expect(page).toHaveURL(/\/app\/issues\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\?project=AWPCA$/);
|
||||
await expect(side).toHaveCount(0);
|
||||
await expect(page.getByTestId("page-issues")).toBeVisible();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
@ -45,7 +45,7 @@ test("390 px: «Меню» открывает панель, «Задачи» з
|
||||
|
||||
test("C-1: на средних ширинах страница не шире окна — «Задачи» на 1280, доска на 1000 прокручивается сама", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 800 });
|
||||
await login(page, "/app/issues");
|
||||
await login(page, "/app/jira/issues");
|
||||
await expect(page.locator("table.issue-table tbody tr").first()).toBeVisible();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(1280);
|
||||
await page.setViewportSize({ width: 1000, height: 800 });
|
||||
@ -58,3 +58,35 @@ test("C-1: на средних ширинах страница не шире о
|
||||
const wrap = page.locator(".wrap");
|
||||
expect(await wrap.evaluate((el) => el.scrollWidth > el.clientWidth)).toBe(true); // доска шире своей рамки — прокрутка внутри
|
||||
});
|
||||
|
||||
test("1440 px: «Задачи» не шире окна, h1 «Вики» — 20px", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await login(page, "/app/jira/issues");
|
||||
await expect(page.locator("table.issue-table tbody tr").first()).toBeVisible();
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(true);
|
||||
await page.getByRole("complementary", { name: "Панель приложения" }).getByRole("link", { name: "Вики" }).click();
|
||||
await expect(page).toHaveURL(/\/app\/wiki\?project=AWPCA$/);
|
||||
const h1 = page.getByRole("heading", { level: 1, name: "Вики" });
|
||||
await expect(h1).toBeVisible();
|
||||
expect(await h1.evaluate((el) => getComputedStyle(el).fontSize)).toBe("20px");
|
||||
});
|
||||
|
||||
test("/app/board → /app/jira/board с сохранением search", async ({ page }) => {
|
||||
await login(page);
|
||||
await page.goto("/app/board?project=AWPCA");
|
||||
await expect(page).toHaveURL(/\/app\/jira\/board\?project=AWPCA$/);
|
||||
});
|
||||
|
||||
test("390 px: метка проекта в верхней строке, «Меню» модально", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 800 });
|
||||
await login(page);
|
||||
const bar = page.getByRole("banner", { name: "Строка приложения" });
|
||||
await expect(bar.getByText("AWPCA", { exact: true })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Меню" }).click();
|
||||
await expect(page.getByRole("complementary", { name: "Панель приложения" })).toBeVisible();
|
||||
await expect(bar).toHaveAttribute("inert", "");
|
||||
await expect(page.locator("main")).toHaveAttribute("inert", "");
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(page.getByRole("complementary", { name: "Панель приложения" })).toHaveCount(0);
|
||||
await expect(bar).not.toHaveAttribute("inert", "");
|
||||
});
|
||||
|
||||
@ -2,7 +2,7 @@ import { expect, test } from "@playwright/test";
|
||||
import { login } from "./hub.ts";
|
||||
|
||||
test("неверный токен — пояснение; верный — доска", async ({ page }) => {
|
||||
await page.goto("/app/board");
|
||||
await page.goto("/app/jira/board");
|
||||
await page.getByLabel("Клиентский токен хаба").fill("wrong-token");
|
||||
await page.getByRole("button", { name: "Войти" }).click();
|
||||
await expect(page.getByText(/Токен не подошёл/)).toBeVisible();
|
||||
@ -10,8 +10,13 @@ test("неверный токен — пояснение; верный — до
|
||||
await expect(page.locator(".card[data-key]").first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("/ и /app ведут на доску", async ({ page }) => {
|
||||
test("прежние адреса ведут на страницы модулей, /app — на «Обзор»", async ({ page }) => {
|
||||
await login(page);
|
||||
await page.goto("/app");
|
||||
await expect(page).toHaveURL(/\/app\/board\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/?\?project=AWPCA$/);
|
||||
await expect(page.getByTestId("page-overview")).toBeVisible();
|
||||
await page.goto("/app/board?project=AWPCA");
|
||||
await expect(page).toHaveURL(/\/app\/jira\/board\?project=AWPCA$/);
|
||||
await page.goto("/app/issues/AURORA-9001?project=AWPCA");
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\/AURORA-9001\?project=AWPCA$/);
|
||||
});
|
||||
|
||||
34
web/e2e/overview.spec.ts
Normal file
34
web/e2e/overview.spec.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { agentServeRefresh, login } from "./hub.ts";
|
||||
|
||||
test("«Обзор»: / → /app/?project=…, две карточки модулей, список пакетов", async ({ page }) => {
|
||||
await login(page, "/app/");
|
||||
await expect(page).toHaveURL(/\/app\/?\?project=AWPCA$/);
|
||||
await expect(page.getByTestId("page-overview")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { level: 1, name: "Обзор" })).toBeVisible();
|
||||
const cards = page.locator(".overview-card");
|
||||
await expect(cards).toHaveCount(2);
|
||||
await expect(page.getByRole("region", { name: "Jira" })).toBeVisible();
|
||||
await expect(page.getByRole("region", { name: "Вики" })).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Последние пакеты проекта" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("«Обзор»: «Обновить из Jira» ставит пакет и доводит до конца", async ({ page }) => {
|
||||
await login(page, "/app/");
|
||||
const card = page.getByRole("region", { name: "Jira" });
|
||||
await card.getByRole("button", { name: "Обновить из Jira" }).click();
|
||||
await expect(card.getByRole("status")).toContainText(/пакет #\d+/);
|
||||
await agentServeRefresh();
|
||||
await expect(card.getByRole("status")).toContainText(/выполнен/, { timeout: 15_000 });
|
||||
});
|
||||
|
||||
test("«Обзор»: пакет проекта («Обновить вики») появляется в «Последних пакетах проекта»", async ({ page }) => {
|
||||
await login(page, "/app/");
|
||||
await expect(page.getByText("Пакетов пока не было.")).toBeVisible();
|
||||
await page.getByRole("region", { name: "Вики" }).getByRole("button", { name: "Обновить вики" }).click();
|
||||
// список пакетов опрашивается раз в 15 с; агента wiki в тестовом хабе нет — пакет остаётся в очереди
|
||||
const row = page.locator("table.overview-table tbody tr").first();
|
||||
await expect(row).toBeVisible({ timeout: 25_000 });
|
||||
await expect(row).toContainText("Вики");
|
||||
await expect(row).toContainText("в очереди");
|
||||
});
|
||||
@ -5,7 +5,7 @@ import { agentServeRefresh, login } from "./hub.ts";
|
||||
test.describe.serial("проекты", () => {
|
||||
test("первый вход: проект AWPCA в адресе, на доске задача проекта", async ({ page }) => {
|
||||
await login(page);
|
||||
await expect(page).toHaveURL(/\/app\/board\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/board\?project=AWPCA$/);
|
||||
await expect(page.locator('.board .card[data-key="AURORA-9001"]')).toBeVisible();
|
||||
});
|
||||
|
||||
@ -35,15 +35,15 @@ test.describe.serial("проекты", () => {
|
||||
});
|
||||
|
||||
test("переключение проекта и раздела", async ({ page }) => {
|
||||
await login(page, "/app/board?project=STOCKS_DRRK");
|
||||
await login(page, "/app/jira/board?project=STOCKS_DRRK");
|
||||
await page.getByLabel("Проект", { exact: true }).selectOption({ label: "AWPCA — АРМ КА" });
|
||||
await expect(page.locator('.board .card[data-key="AURORA-9001"]')).toBeVisible();
|
||||
await page.getByRole("link", { name: "Задачи", exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/app\/issues\?project=AWPCA$/);
|
||||
await expect(page).toHaveURL(/\/app\/jira\/issues\?project=AWPCA$/);
|
||||
});
|
||||
|
||||
test("неизвестный проект в адресе", async ({ page }) => {
|
||||
await login(page, "/app/issues?project=NOPE");
|
||||
await login(page, "/app/jira/issues?project=NOPE");
|
||||
await expect(page.getByText("проект NOPE не найден")).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { expect, test, type Page } from "@playwright/test";
|
||||
import { mkdirSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { agentServeRefresh, login } from "./hub.ts";
|
||||
import { agentServeRefresh, jiraEvent, login } from "./hub.ts";
|
||||
|
||||
// Снимки экранов для проверки вида (запуск: SCREENS=1 npx playwright test e2e/screens.spec.ts).
|
||||
// Результат — web/e2e-screens/<тема>-<ширина>-<экран>.png, в git не попадает.
|
||||
@ -26,12 +26,20 @@ for (const theme of ["light", "dark"] as const) {
|
||||
};
|
||||
|
||||
test("login", async ({ page }) => {
|
||||
await page.goto("/app/board");
|
||||
await page.goto("/app/jira/board");
|
||||
await expect(page.getByLabel("Клиентский токен хаба")).toBeVisible();
|
||||
await settle(page);
|
||||
await shot(page, "login");
|
||||
});
|
||||
|
||||
test("overview", async ({ page }) => {
|
||||
await login(page, "/app/");
|
||||
await expect(page.getByTestId("page-overview")).toBeVisible();
|
||||
await expect(page.locator(".overview-card")).toHaveCount(2);
|
||||
await settle(page);
|
||||
await shot(page, "overview");
|
||||
});
|
||||
|
||||
test("board", async ({ page }) => {
|
||||
await login(page);
|
||||
await expect(page.locator(".board .card[data-key]").first()).toBeVisible();
|
||||
@ -56,14 +64,14 @@ for (const theme of ["light", "dark"] as const) {
|
||||
});
|
||||
|
||||
test("issues", async ({ page }) => {
|
||||
await login(page, "/app/issues");
|
||||
await login(page, "/app/jira/issues");
|
||||
await expect(page.locator("table.issue-table tbody tr").first()).toBeVisible();
|
||||
await settle(page);
|
||||
await shot(page, "issues");
|
||||
});
|
||||
|
||||
test("issues-filters", async ({ page }) => {
|
||||
await login(page, "/app/issues");
|
||||
await login(page, "/app/jira/issues");
|
||||
await page.getByRole("button", { name: /Фильтры/ }).click();
|
||||
await page.getByRole("combobox", { name: "Исполнитель" }).selectOption("Петров Пётр Петрович");
|
||||
await expect(page).toHaveURL(/assignee=/);
|
||||
@ -72,7 +80,7 @@ for (const theme of ["light", "dark"] as const) {
|
||||
});
|
||||
|
||||
test("card", async ({ page }) => {
|
||||
await login(page, "/app/issues/AURORA-9001");
|
||||
await login(page, "/app/jira/issues/AURORA-9001");
|
||||
await expect(page.getByRole("dialog").getByRole("heading", { name: "Схема свода" })).toBeVisible();
|
||||
await settle(page);
|
||||
await shot(page, "card");
|
||||
@ -105,14 +113,23 @@ for (const theme of ["light", "dark"] as const) {
|
||||
await page.screenshot({ path: `${OUT}/${theme}-${w}-menu.png` });
|
||||
});
|
||||
|
||||
test("sidebar", async ({ page }) => {
|
||||
await login(page, "/app/");
|
||||
if (w < 640) await page.getByRole("button", { name: "Меню" }).click();
|
||||
const side = page.getByRole("complementary", { name: "Панель приложения" });
|
||||
await expect(side).toBeVisible();
|
||||
await settle(page);
|
||||
await side.screenshot({ path: `${OUT}/${theme}-${w}-sidebar.png` });
|
||||
});
|
||||
|
||||
test("refresh", async ({ page }) => {
|
||||
await login(page);
|
||||
await page.getByRole("button", { name: "Обновить из Jira" }).click();
|
||||
await expect(page.getByRole("status")).toContainText(/пакет #\d+/);
|
||||
await page.getByTestId("page-board").getByRole("button", { name: "Обновить из Jira", exact: true }).click();
|
||||
await expect(jiraEvent(page)).toContainText(/пакет #\d+/);
|
||||
await settle(page);
|
||||
await shot(page, "refresh");
|
||||
await agentServeRefresh(); // не оставляем пакет в очереди тестового хаба
|
||||
await expect(page.getByRole("status")).toContainText(/выполнен/, { timeout: 15_000 });
|
||||
await expect(jiraEvent(page)).toContainText(/выполнен/, { timeout: 15_000 });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
/* таблица прокручивается внутри своей панели на любой ширине (девять столбцов шире контейнера и на 1440 px); обёртка не шире контейнера страницы.
|
||||
Обёртка — скроллер, поэтому липкая шапка липнет к ней, а не к окну: чтобы шапка оставалась на виду, высота обёртки ограничена высотой окна
|
||||
и длинный список прокручивается внутри панели (на низких окнах — меньше 700 px — ограничения нет, шапка уходит вместе со страницей) */
|
||||
.issue-table-wrap{overflow-x:auto;max-width:100%;min-width:0}
|
||||
.issue-table-wrap{position:relative;overflow-x:auto;max-width:100%;min-width:0}
|
||||
@media (min-height:700px){.issue-table-wrap{max-height:calc(100vh - 220px);overflow-y:auto}}
|
||||
.issue-table th:first-child{border-top-left-radius:calc(var(--radius-xl) - 1px)}
|
||||
.issue-table th:last-child{border-top-right-radius:calc(var(--radius-xl) - 1px)}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user