189 lines
13 KiB
TypeScript
189 lines
13 KiB
TypeScript
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);
|
||
});
|
||
|
||
test("панель фильтров на 1600 px: «Исполнитель» и поиск на одной строке, подпись видна (I-2, M-2)", async ({ page }) => {
|
||
await page.setViewportSize({ width: 1600, height: 900 });
|
||
await login(page, "/app/jira/sprint?project=AWPCA");
|
||
const search = page.getByRole("searchbox", { name: "Поиск по ключу или теме" });
|
||
const select = page.getByRole("combobox", { name: "Исполнитель" });
|
||
await expect(select).toBeVisible();
|
||
await expect(page.locator(".agile-toolbar__field").getByText("Исполнитель", { exact: true })).toBeVisible();
|
||
const mid = async (l: typeof search) => { const b = (await l.boundingBox())!; return b.y + b.height / 2; };
|
||
expect(Math.abs((await mid(search)) - (await mid(select))), "центры по вертикали").toBeLessThanOrEqual(2);
|
||
const bar = (await page.locator(".agile-toolbar").boundingBox())!;
|
||
const sel = (await select.boundingBox())!;
|
||
expect(sel.width, "выбор не растянут на строку").toBeLessThan(bar.width / 2);
|
||
});
|