sdlc-hub/web/e2e/agile.spec.ts
2026-10-25 21:12:09 +04:00

175 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { expect, test } from "@playwright/test";
import { login } from "./hub.ts";
// экраны доски: «Бэклог», «Спринт», «Kanban» (spec web-app «Спринты и бэклог»); данные — сид тестового хаба (R-27)
type Page = import("@playwright/test").Page;
const side = (page: Page) => page.getByRole("complementary", { name: "Панель приложения" });
// строка списка / карточка колонки по ключу задачи (ссылка с точным именем), без привязки к порядку в DOM
const itemOf = (page: Page, key: string) => page.getByRole("listitem").filter({ has: page.getByRole("link", { name: key, exact: true }) });
const colOf = (page: Page, name: string) => page.getByRole("region", { name, exact: true });
test("«Бэклог» AWPCA: секции, «Прошлые спринты (1)», щелчок открывает карточку", async ({ page }) => {
await login(page, "/app/jira/backlog?project=AWPCA");
await expect(page.getByRole("heading", { name: "AWPCA_Спринт 2" })).toBeVisible();
await expect(page.getByRole("heading", { name: "AWPCA_Спринт 3" })).toBeVisible();
await expect(page.getByRole("heading", { name: "Бэклог", exact: true, level: 2 })).toBeVisible();
const past = page.getByText("Прошлые спринты (1)", { exact: true });
await expect(past).toBeVisible();
await past.click();
const pastSprint = page.locator("details.agile-past__sprint").filter({ has: page.locator("summary", { hasText: "AWPCA_Спринт 1" }) });
await pastSprint.locator("summary").click();
const row = pastSprint.getByRole("listitem").filter({ has: page.getByRole("link", { name: "AURORA-9001", exact: true }) });
await expect(row).toBeVisible();
await row.click();
await expect(page).toHaveURL(/\/app\/jira\/issues\/AURORA-9001/);
await expect(page.getByRole("dialog")).toBeVisible();
await page.keyboard.press("Escape");
await expect(page).toHaveURL(/\/app\/jira\/backlog/);
await expect(page.getByRole("dialog")).toHaveCount(0);
});
test("«Спринт» AWPCA: колонки доски, «осталось 7 дн.», карточка окном", async ({ page }) => {
await login(page, "/app/jira/sprint?project=AWPCA");
await expect(page.getByRole("heading", { name: "AWPCA_Спринт 2" })).toBeVisible();
await expect(page.getByText(/осталось \d+ дн\./)).toBeVisible();
for (const name of ["К выполнению", "В работе", "Готово"]) {
await expect(page.getByRole("heading", { name, exact: true, level: 3 })).toBeVisible();
}
await itemOf(page, "AURORA-9001").click();
await expect(page).toHaveURL(/\/app\/jira\/issues\/AURORA-9001/);
await expect(page.getByRole("dialog")).toBeVisible();
await page.keyboard.press("Escape");
await expect(page).toHaveURL(/\/app\/jira\/sprint/);
});
test("«Kanban»: «Бэклог (1)», колонки «В работе» и «Готово», подпись «за 14 дней»", async ({ page }) => {
await login(page, "/app/jira/kanban?project=KANBAN_E2E");
await expect(page.getByText("Бэклог (1)", { exact: true })).toBeVisible();
await expect(colOf(page, "В работе")).toBeVisible();
await expect(colOf(page, "Готово")).toBeVisible();
await expect(colOf(page, "Бэклог")).toHaveCount(0);
await expect(page.getByText("за 14 дней", { exact: true })).toBeVisible();
await expect(colOf(page, "В работе").getByRole("link", { name: "AURORA-9102", exact: true })).toBeVisible();
await expect(colOf(page, "Готово").getByRole("link", { name: "AURORA-9103", exact: true })).toBeVisible();
});
test("без scrum-доски: /app/jira/backlog?project=KANBAN_E2E — «У проекта нет scrum-доски», ссылка «Связи»", async ({ page }) => {
await login(page, "/app/jira/backlog?project=KANBAN_E2E");
await expect(page.getByText("У проекта нет scrum-доски")).toBeVisible();
await page.getByRole("main").getByRole("link", { name: "Связи", exact: true }).click();
await expect(page).toHaveURL(/\/app\/jira\/links\?project=KANBAN_E2E/);
});
test("меню по проекту: у AWPCA «Бэклог» и «Спринт», у KANBAN_E2E — «Kanban»", async ({ page }) => {
await login(page, "/app/jira/links?project=AWPCA");
await expect(side(page).getByRole("link", { name: "Бэклог" })).toBeVisible();
await expect(side(page).getByRole("link", { name: "Спринт" })).toBeVisible();
await expect(side(page).getByRole("link", { name: "Kanban" })).toHaveCount(0);
await page.goto("/app/jira/links?project=KANBAN_E2E");
await expect(side(page).getByRole("link", { name: "Kanban" })).toBeVisible();
await expect(side(page).getByRole("link", { name: "Бэклог" })).toHaveCount(0);
await expect(side(page).getByRole("link", { name: "Спринт" })).toHaveCount(0);
});
test("900 px: колонки «Спринта» прокручиваются внутри доски, страница без горизонтальной прокрутки", async ({ page }) => {
await page.setViewportSize({ width: 900, height: 900 });
await login(page, "/app/jira/sprint?project=AWPCA");
await expect(colOf(page, "К выполнению")).toBeVisible();
const m = await page.evaluate(() => {
const d = document.documentElement, c = document.querySelector<HTMLElement>(".agile-columns")!;
return { doc: [d.scrollWidth, d.clientWidth], cols: [c.scrollWidth, c.clientWidth], bottom: c.getBoundingClientRect().bottom,
gutter: c.getBoundingClientRect().left - document.querySelector(".agile")!.getBoundingClientRect().left };
});
expect(m.gutter, "колонки не получают второй отступ внутри страницы (T10.1)").toBe(0);
expect(m.doc[0]).toBeLessThanOrEqual(m.doc[1]);
expect(m.cols[0]).toBeGreaterThan(m.cols[1]);
expect(m.bottom, "нижний край доски в окне").toBeLessThanOrEqual(900);
});
test("«Широкая доска» 2560×1300: колонки одной высоты, доска в окне, горизонтальная прокрутка внутри, «Без колонки» достижима", async ({ page }) => {
await page.setViewportSize({ width: 2560, height: 1300 });
await login(page, "/app/jira/sprint?project=WIDE_E2E");
await expect(colOf(page, "Колонка 1")).toBeVisible();
await expect(colOf(page, "Без колонки")).toHaveCount(1);
const heights = () => page.evaluate(() => [...document.querySelectorAll<HTMLElement>(".agile-col")].map((c) => c.getBoundingClientRect().height));
await expect.poll(async () => { const h = await heights(); return Math.max(...h) - Math.min(...h); }, { message: "высоты колонок равны" }).toBeLessThanOrEqual(1);
const m = await page.evaluate(() => {
const board = document.querySelector<HTMLElement>(".agile-columns")!;
const heights = [...document.querySelectorAll<HTMLElement>(".agile-col")].map((c) => c.getBoundingClientRect().height);
return { heights, bottom: board.getBoundingClientRect().bottom, inner: window.innerHeight, width: board.getBoundingClientRect().width,
sw: board.scrollWidth, cw: board.clientWidth, count: heights.length };
});
expect(m.count, "11 колонок и «Без колонки»").toBe(12);
expect(m.bottom, "нижний край доски в окне").toBeLessThanOrEqual(m.inner);
expect(m.sw, "доска прокручивается по горизонтали").toBeGreaterThan(m.cw);
expect(m.width, "доска шире 1440 px (нет max-width)").toBeGreaterThan(1440);
await page.evaluate(() => { const b = document.querySelector<HTMLElement>(".agile-columns")!; b.scrollLeft = b.scrollWidth; });
const extra = colOf(page, "Без колонки");
await expect(extra).toBeInViewport({ ratio: 1 });
const doc = await page.evaluate(() => [document.documentElement.scrollWidth, document.documentElement.clientWidth]);
expect(doc[0], "страница без горизонтальной прокрутки").toBeLessThanOrEqual(doc[1]);
});
test("узкий контейнер: на 900 px вместо панели «Эпики» — выбор «Эпик», на 1600 px — панель и кнопка", async ({ page }) => {
await page.setViewportSize({ width: 900, height: 900 });
await login(page, "/app/jira/backlog?project=AWPCA");
await expect(page.getByRole("heading", { name: "AWPCA_Спринт 2" })).toBeVisible();
await expect(page.getByRole("complementary", { name: "Эпики" })).toBeHidden();
await expect(page.getByRole("button", { name: "Эпики", exact: true })).toBeHidden();
await expect(page.getByRole("combobox", { name: "Эпик" })).toBeVisible();
await page.setViewportSize({ width: 1600, height: 900 });
await expect(page.getByRole("complementary", { name: "Эпики" })).toBeVisible();
await expect(page.getByRole("button", { name: "Эпики", exact: true })).toBeVisible();
await expect(page.getByRole("combobox", { name: "Эпик" })).toBeHidden();
});
test("«Бэклог» AWPCA: панель «Эпики», выбор эпика оставляет только его задачи, свёртка секции переживает перезагрузку", async ({ page }) => {
await login(page, "/app/jira/backlog?project=AWPCA");
const panel = page.getByRole("complementary", { name: "Эпики" });
await expect(panel).toBeVisible();
await expect(itemOf(page, "AURORA-9004")).toBeVisible();
await panel.getByRole("button", { name: /AURORA-9100/ }).click();
await expect(itemOf(page, "AURORA-9001")).toBeVisible();
await expect(itemOf(page, "AURORA-9004")).toHaveCount(0);
await expect(itemOf(page, "AURORA-9008")).toHaveCount(0);
await panel.getByRole("button", { name: "Все задачи" }).click();
await expect(itemOf(page, "AURORA-9004")).toBeVisible();
const toggle = page.getByRole("button", { name: "AWPCA_Спринт 3", exact: true });
await expect(toggle).toHaveAttribute("aria-expanded", "true");
await toggle.click();
await expect(toggle).toHaveAttribute("aria-expanded", "false");
await expect(itemOf(page, "AURORA-9004")).toHaveCount(0);
await page.reload();
await expect(page.getByRole("button", { name: "AWPCA_Спринт 3", exact: true })).toHaveAttribute("aria-expanded", "false");
await expect(itemOf(page, "AURORA-9004")).toHaveCount(0);
});
test("«Спринт» AWPCA: выбор двух спринтов пишет sprint=102,103 в адрес, скрытая колонка «Готово» переживает перезагрузку", async ({ page }) => {
await login(page, "/app/jira/sprint?project=AWPCA");
await expect(colOf(page, "Готово")).toBeVisible();
await page.getByRole("button", { name: "AWPCA_Спринт 2" }).click();
await page.getByRole("checkbox", { name: /AWPCA_Спринт 3/ }).click();
await expect(page).toHaveURL(/sprint=102(,|%2C)103/);
await expect(page.getByRole("heading", { name: "AWPCA_Спринт 3" })).toBeVisible();
await page.keyboard.press("Escape");
await page.getByRole("button", { name: "Колонки" }).click();
await page.getByRole("checkbox", { name: "Готово", exact: true }).click();
await expect(colOf(page, "Готово")).toHaveCount(0);
await page.reload();
await expect(colOf(page, "В работе")).toBeVisible();
await expect(colOf(page, "Готово")).toHaveCount(0);
});
test("390×800: документ без горизонтальной прокрутки, нижний край доски в окне (T8.1)", async ({ page }) => {
await page.setViewportSize({ width: 390, height: 800 });
await login(page, "/app/jira/sprint?project=WIDE_E2E");
await expect(colOf(page, "Колонка 1")).toBeVisible();
const m = await page.evaluate(() => {
const d = document.documentElement, b = document.querySelector<HTMLElement>(".agile-columns")!;
return { doc: [d.scrollWidth, d.clientWidth], bottom: b.getBoundingClientRect().bottom, inner: window.innerHeight, sw: b.scrollWidth, cw: b.clientWidth };
});
expect(m.doc[0], JSON.stringify(m)).toBeLessThanOrEqual(m.doc[1]);
expect(m.bottom, JSON.stringify(m)).toBeLessThanOrEqual(m.inner);
expect(m.sw).toBeGreaterThan(m.cw);
});